JVM-内存结构

✅作者简介:热爱Java后端开发的一名学习者,大家可以跟我一起讨论各种问题喔。
🍎个人主页:Hhzzy99
🍊个人信条:坚持就是胜利!
💞当前专栏:JVM
🥭本文内容:JVM的内存结构!


文章目录

    • 内存结构
      • 程序计数器(Program Counter)
      • 虚拟机栈(Virtual Machine Stack)
        • 栈内存溢出
        • 线程运行诊断
      • 本地方法栈(Native Method Stack)
      • 堆(Heap)
        • 堆内存溢出
        • 堆内存诊断
      • 方法区(Method Area)
        • 方法区内存溢出
        • 二进制字节码
        • 常量池
        • 运行时常量池
        • 串池
        • StringTable特性
        • StringTable垃圾回收
        • StringTable调优
      • 直接内存
  • 结语

内存结构

程序计数器(Program Counter)

1

JVM(Java虚拟机)中的程序计数器(Program Counter)是一种比较简单的数据结构,它可以看作是当前线程所执行的字节码的行号指示器或者下一条指令的地址

每个线程都有一个独立的程序计数器,它是线程私有的,不会被线程之间共享。在任何给定时间点,一个线程只会执行一个方法,即当前线程的程序计数器指向正在执行的方法的字节码指令。

程序计数器在JVM中的作用主要有以下两个方面:

  1. 字节码解释器工作:程序计数器指示了字节码解释器下一条要执行的指令。字节码解释器通过不断地改变程序计数器的值来获取下一条指令,并解释执行。(记住下一条jvm指令的执行地址
  2. 线程切换恢复:由于线程切换时,需要记录当前线程的执行位置以便于恢复执行,程序计数器也起到了这个作用。当线程被切换回来时,JVM会根据程序计数器的值来恢复线程的执行位置,从上一次中断的地方继续执行。

需要注意的是,程序计数器是一个较小的内存区域,它是线程私有的,且没有OutOfMemoryError的风险。而且,由于程序计数器只记录了线程执行的位置信息,不会执行任何垃圾回收和内存分配等操作,所以它不会对垃圾回收和内存管理产生影响。

总结一下:

程序计数器在JVM中用于记住下一条jvm执行的执行地址,它在字节码解释器工作和线程切换恢复中发挥着重要的作用。

虚拟机栈(Virtual Machine Stack)

2

虚拟机栈(Virtual Machine Stack)是Java虚拟机(JVM)在运行时创建的线程私有的内存区域,用于存储方法的局部变量、操作数栈、动态链接、方法出口等信息。下面是对虚拟机栈的详细解释:

  1. 概念:每个线程在执行Java程序时,都会在虚拟机栈上创建一个对应的栈帧(Stack Frame)。栈帧用于存储方法的运行时数据,每个方法对应一个栈帧。
  2. 栈帧结构:栈帧由三部分组成:局部变量表(Local Variable Table)、操作数栈(Operand Stack)和帧数据区(Frame Data)。局部变量表用于存储方法中的局部变量和参数,操作数栈用于执行方法的操作数和中间结果,帧数据区包含方法返回地址和异常处理相关信息。
  3. 栈的特点:虚拟机栈是一个后进先出(LIFO)的数据结构,它的大小是固定的,栈的大小在JVM启动时就可以设置。当线程调用一个方法时,JVM会为该方法创建一个栈帧并压入虚拟机栈,方法执行完毕后,栈帧会出栈。
  4. 栈的作用:虚拟机栈的主要作用是支持方法的调用和执行。每个线程独立拥有一个虚拟机栈,线程之间的方法调用和数据传递通过虚拟机栈来完成。
  5. 栈的异常:虚拟机栈的大小是有限的,当方法调用的栈帧数量超过了虚拟机栈的最大限制时,就会抛出"StackOverflowError"异常。另外,如果虚拟机栈无法动态扩展,或者申请扩展时无法分配到足够的内存,就会抛出"OutOfMemoryError"异常。

总而言之,虚拟机栈是Java虚拟机在运行时为每个线程创建的一个内存区域,用于支持方法的调用和执行。它存储了方法的局部变量、操作数栈和方法出口等信息,是Java程序运行时的重要组成部分。

栈内存溢出

  • 栈帧过多导致栈内存溢出

设置栈内存参数:-Xss256k

private static int count;
    public static void main(String[] args) {
        try{
            method1();
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(count);
        }
    }
     private static void method1(){
        count++;
        method1();
     }

java.lang.StackOverflowError

  • 栈帧过大导致栈内存溢出
public class StackOverflowExample {
    public static void main(String[] args) {
        try {
            recursiveMethod(1);
        } catch (StackOverflowError e) {
            System.out.println("StackOverflowError: " + e.getMessage());
        }
    }

    public static void recursiveMethod(int value) {
        byte[] data = new byte[1 * 1024 * 1024]; // 创建一个1MB大小的数组
        recursiveMethod(value + 1); // 递归调用自身
    }
}

在上述代码中,recursiveMethod() 方法在每次递归调用时都会创建一个1MB大小的数组。随着递归的深入,栈帧中的局部变量 data 占用的内存也会不断增加,当栈帧中的数组大小超过了虚拟机栈的容量限制时,就会导致栈内存溢出。

当我们运行上述代码时,会抛出 StackOverflowError 异常,提示栈溢出。这是因为每次递归调用都会占用大量的栈内存,栈帧中的数组越来越大,最终超过了虚拟机栈的容量限制。

线程运行诊断

cpu占用过多

在linux下,用top命令监测定位哪个应用程序对cpu占用高

ps H -eo pid,tid,%cpu | grep 进程id(用ps命令进一步定位是哪个线程引起的cpu占用高)

jstack 进程id

​ 可以根据线程id(linux查找到的是十进制,jstack显示出的是十六进制,可以转换一下再对比)找到有问题的线程,进一步定位到问题代码的源码行号。

程序运行很长时间没有结果

jstack 进程id
发现发生了死锁
3

本地方法栈(Native Method Stack)

4

本地方法栈(Native Method Stack) 是Java虚拟机为执行本地方法(Native Method)而提供的一个栈空间。本地方法是使用其他编程语言(如C、C++)编写的方法,通过Java的本地接口(JNI)与Java代码进行交互。

本地方法栈的作用类似于虚拟机栈,但是它不存储Java方法的调用和执行信息,而是用于执行本地方法的相关操作。本地方法栈的大小是由虚拟机在启动时设定的,可以通过命令行参数进行调整。

与虚拟机栈类似,本地方法栈也是线程私有的,每个线程都有自己的本地方法栈。在执行本地方法时,虚拟机会将本地方法的参数和局部变量等信息存储在本地方法栈中,并提供给本地方法使用。

本地方法栈的溢出会导致本地方法栈溢出异常(StackOverflowError)。与虚拟机栈溢出异常类似,本地方法栈溢出异常通常是由于递归调用或本地方法内部存在过多的栈帧导致的。

需要注意的是,本地方法栈与虚拟机栈在实现上可能是相互交叉的,也可能是独立的,具体取决于虚拟机的实现。在一些虚拟机实现中,虚拟机栈和本地方法栈可能合二为一,共用同一块内存空间。而在另一些实现中,虚拟机栈和本地方法栈是独立的,分别使用不同的内存空间。

堆(Heap)

5

堆(Heap)是Java虚拟机管理的内存区域之一,用于存储对象实例和数组。在Java程序运行时,所有动态分配的对象都存储在堆中。

堆是一个运行时数据区,被 所有线程共享 。它在虚拟机启动时被创建,并且是Java虚拟机管理的最大的一块内存区域。堆被划分为更小的区域,称为堆块(Heap Chunk)或堆页(Heap Page)。每个堆块可以被分配给一个对象,如果对象太大无法放入单个堆块中,那么它将被划分为多个连续的堆块。

堆的大小可以通过命令行参数进行调整,其中包括初始堆大小(-Xms)最大堆大小(-Xmx)。堆的大小直接影响到应用程序可以创建的对象数量和程序的性能。

Java堆的特点包括:

  • 对象的动态分配:所有在Java程序中创建的对象都存储在堆中,并由垃圾收集器进行管理。
  • 对象的自动释放:Java虚拟机的垃圾收集器会自动回收不再使用的对象内存,释放给堆重新分配。
  • 对象的可扩展性:堆可以根据应用程序的需要进行动态扩展,以容纳更多的对象实例。

需要注意的是,堆的内存分配是线程共享的,因此在多线程环境下需要考虑线程安全性。此外,堆内存的回收是通过垃圾收集器来完成的,垃圾收集过程可能会导致一定的停顿时间,需要合理配置堆大小和选择适当的垃圾收集器以平衡内存使用和应用程序的响应性能。

堆内存溢出

public static void main(String[] args) {
        int i = 0;
        try{
            List<String> list = new ArrayList<>();
            String a = "hello";
            while (true){
                list.add(a);
                a = a + a;
                i++;
            }
        }catch (Throwable e){
            e.printStackTrace();
            System.out.println(i);
        }
    }

java.lang.OutOfMemoryError: Java heap space

堆内存诊断

//用到的代码
public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(30000);
        byte[] array = new byte[1024 * 1024 * 10];//10M
        System.out.println("2...");
        Thread.sleep(20000);
        array = null;
        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000000L);
    }
  1. jps工具

    查看当前系统中有哪些java进程

    6

  2. jmap工具

    查看堆内存占用情况

    7

  3. jconsole工具(控制台输入jconsole打开)

    图形界面,多功能的监测工具,可以连续监测

8

  1. jvisualvm工具(好用)(控制台输入jvisualvm打开)

9

方法区(Method Area)

10

方法区(Method Area)是Java虚拟机管理的内存区域之一,用于存储类的结构信息、静态变量、常量以及编译器编译后的代码等数据。

方法区属于共享区域,被所有线程共享。它在虚拟机启动时被创建,用于存储加载的类信息、常量、静态变量、即时编译器编译后的代码等。方法区的大小可以通过命令行参数进行调整,如设置最大方法区大小(-XX:MaxMetaspaceSize)。

官方文档(JDK1.8版本的)Chapter 2. The Structure of the Java Virtual Machine (oracle.com)

11

方法区的特点包括:

  • 存储类的结构信息:方法区存储了类的完整结构信息,包括类的名称、父类的名称、字段、方法、接口等。
  • 存储静态变量和常量:方法区存储了类的静态变量和常量,这些变量在类的生命周期内始终存在,并且可以被所有实例共享。
  • 存储编译后的代码:方法区存储了编译器编译后的代码,包括方法的字节码指令等。
  • 执行运行时常量池:方法区中的运行时常量池存储了类、接口、方法中使用的常量数据。

需要注意的是,方法区的内存分配是线程共享的,因此在多线程环境下需要考虑线程安全性。在早期的Java虚拟机版本中,方法区的实现是永久代(Permanent Generation),但在JDK 8及之后的版本中,方法区的实现已经改为元空间(Metaspace)。元空间使用的是本地内存而不是虚拟机内存,因此避免了传统方法区的一些限制和问题。

12

13

需要注意的是,方法区的内存分配是线程共享的,因此在多线程环境下需要考虑线程安全性。此外,方法区的内存回收主要是通过垃圾收集器来回收无用的类和常量数据,具体的回收策略和机制因虚拟机而异。

方法区内存溢出

代码(JDK1.8版本的)

运行前添加参数-XX:MaxMetaspaceSize=64m将元空间内存设置小一点便于观察报错

public class MetaSpaceOverflow extends ClassLoader{//可以用来加载类的二进制字节码
    public static void main(String[] args) {
        int i = 0;
        try{
            MetaSpaceOverflow test = new MetaSpaceOverflow();
            for (int j = 0;; j++,i++) {
                //ClassWriter 作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                //参数意思:版本号,public,类名,包名,父类,接口
                cw.visit(Opcodes.V1_8,Opcodes.ACC_PUBLIC,"Class"+j,null,"java/lang/Object",null);
                //返回生成类的二进制字节码byte[]
                byte[] code = cw.toByteArray();
                //执行类的加载
                test.defineClass("Class" + j,code,0,code.length);
            }
        }finally {
            System.out.println(i);
        }
    }
}

14

运行这段代码,当超出我们预定的64m元空间限制,就会抛出java.lang.OutOfMemoryError: Metaspace异常。

1.8以前的版本会抛出java.lang.OutOfMemoryError: PermGen space异常

二进制字节码

//二进制字节码包含(类基本信息,常量池,类方法定义,包含了虚拟机指令)
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}

通过javac -g 类名.java将Java代码编译成字节码文件

再通过javap -v 类名.class 反编译字节码文件

类基本信息:

15

常量池:

16

类方法的定义:

17

虚拟机指令:

18

常量池

常量池是Java中的一种数据结构,用于存储在编译时期生成的字面量和符号引用。它在类加载过程中被创建,并与每个类相关联。

常量池包含了各种常量,例如字符串常量、数值常量、类和接口的全限定名、字段和方法的符号引用等。它的主要目的是减少重复的数据,提高内存利用率和运行效率。

在Java中,常量池分为两部分:编译时常量池和运行时常量池。

  1. 编译时常量池:在编译阶段,Java编译器会将源代码中的字面量和符号引用存储在编译时常量池中。它是每个类文件中的一部分,用于存储类的常量信息。
  2. 运行时常量池:在类加载过程中,编译时常量池的内容会被复制到运行时常量池中。运行时常量池是方法区的一部分,用于存储类加载后的常量数据。

常量池具有以下特点:

  • 常量池中的常量是唯一的,不会重复存储相同的字面量或符号引用。
  • 常量池中的常量可以被类的方法直接引用,以及在运行时进行动态链接、解析和初始化。
  • 常量池中的常量可以在运行时被修改(例如使用反射机制修改常量值),但不推荐这样做。

常量池在Java中扮演了重要的角色,它为类的加载、链接和初始化过程提供了必要的数据支持,同时也为字符串常量的共享和节省内存提供了机制。

通俗来说,常量池就是一张常量表,虚拟机根据这张常量表找到要执行的类名、方法名、参数类型、字面量(像上面的"hello world"、整数、boolean类型,都称之为字面量)等信息。

运行时常量池

运行时常量池是Java虚拟机在类加载过程中所创建的一块内存区域,用于存储每个类或接口的常量数据。

运行时常量池是方法区的一部分,它在类加载时被创建,并且与每个类相关联。它的主要作用是存储编译时期生成的字面量和符号引用,以及在运行时进行动态链接、解析和初始化所需要的各种常量信息。

运行时常量池与编译时常量池有一定的关联关系,编译时常量池中的内容在类加载过程中会被复制到运行时常量池中。但是运行时常量池相比于编译时常量池具有更大的灵活性,可以进行动态修改。

运行时常量池中存储的常量类型包括:

  1. 字符串常量:包括直接使用双引号括起来的字符串字面量。
  2. 类和接口的全限定名:用于符号引用。
  3. 字段和方法的符号引用:用于动态链接和解析。
  4. 数值常量:包括整数、浮点数和布尔值等。

运行时常量池具有以下特点:

  1. 常量池中的常量是唯一的,不会重复存储相同的字面量或符号引用。
  2. 运行时常量池可以通过Java的反射机制进行访问和修改。
  3. 运行时常量池是方法区的一部分,与每个类或接口相关联,它在内存中的位置是固定的。

需要注意的是,运行时常量池的容量是有限的,当常量池中的常量数量超过容量限制时,会抛出java.lang.OutOfMemoryError异常。在Java 7之前,运行时常量池的容量受到方法区大小的限制;而在Java 7及以后的版本中,运行时常量池被移至堆内存中,受到堆大小的限制。

通俗来讲,常量池是存在于 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

串池

串池(String Pool)是Java中的字符串常量池,它是一个位于堆内存中的特殊存储区域,用于存储字符串对象。

在Java中,字符串是不可变的,即创建后不能被修改。为了提高字符串的重用性和效率,Java使用了字符串常量池的机制。字符串常量池中存储了所有字符串字面量(直接使用双引号括起来的字符串)的引用,相同的字符串字面量在常量池中只会保存一份,这样可以节省内存空间。

当创建一个字符串对象时,首先会检查字符串常量池中是否已经存在相同内容的字符串,如果存在,则直接返回常量池中的引用;如果不存在,则在常量池中创建一个新的字符串对象,并返回该引用。

例如:

String s1 = "Hello"; // 字符串常量池中创建了一个"Hello"对象,并将引用赋给s1
String s2 = "Hello"; // 直接使用常量池中的引用,无需创建新的对象
String s3 = new String("Hello"); // 创建了一个新的字符串对象,并在堆内存中存储,不在常量池中

串池的特点包括:

  1. 字符串常量池中的字符串对象是不可变的,一旦创建就不能被修改。
  2. 字符串常量池中的字符串对象是唯一的,相同内容的字符串只会在常量池中保存一份。
  3. 字符串常量池的对象可以通过字符串字面量直接访问,不需要显式调用构造函数。
  4. 字符串常量池位于堆内存中,与堆中的其他对象一起进行垃圾回收。

需要注意的是,通过new String()方式创建的字符串对象不会被放入字符串常量池,而是在堆内存中单独创建一个新的对象。如果需要将这样的字符串对象放入字符串常量池中,可以使用intern()方法手动将其加入常量池。

String s4 = new String("Hello").intern(); // 将字符串对象放入字符串常量池

StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回

StringTable位置

在1.8之前它就随着常量池存储在永久代(PermGen)里面

在1.8之后它存在于堆里面

19

原因: 永久代的内存回收效率太低,永久代是需要FullGC的时候才会触发,而FullGC只有在整个老年代空间不足才会触发,触发时机太晚,间接的导致StringTable回收效率不高。但是StringTable使用的非常的频繁,大量的字符串常量对象都会分配到StringTable中,如果他的回收效率不高,就会占用大量的内存,容易造成永久代的内存不足。所以在1.7开始,就将StringTable转移到了堆里,堆里面的StringTable只需要minorGC就可以触发垃圾回收。

StringTable垃圾回收

StringTable(字符串常量池)在垃圾回收中有一些特殊的处理方式。在早期的JVM版本中,StringTable是位于永久代(PermGen)中的,而在JDK 7及以后的版本中,StringTable被移至堆内存的一部分,也就是元空间(Metaspace)。

在垃圾回收过程中,StringTable的垃圾回收机制与其他对象的垃圾回收机制略有不同。在经典的垃圾回收算法中,只有不再被任何对象引用的对象才会被回收。但是,StringTable中的字符串对象是具有特殊性的,它们通常是常量或者被大量使用的字符串。由于这些字符串在运行时频繁地被引用,所以垃圾回收器并不会过早地回收这些字符串对象。

然而,当一个类被加载器卸载或者被垃圾回收时,相关的字符串常量也会被移除。这意味着在某些情况下,StringTable中的字符串对象可能会被回收,但通常情况下,它们会一直存在于整个应用程序的生命周期中。

需要注意的是,随着JDK的不同版本和垃圾回收机制的演进,StringTable的实现细节也可能会有所变化。因此,在具体的JVM实现中,StringTable的垃圾回收策略可能有所不同。但总体而言,StringTable中的字符串对象通常具有一定的持久性,并不会被过早地回收。

StringTable调优

设置桶个数

StringTable底层是一个Hash表,可以用参数-XX:StringTableSize 设置桶个数

一般可以调大一点,降低Hash碰撞,降低链表的长度,对速度的提升很明显。

测试

/***
 *@Description: StringTable调优
 *@author: Hhzzy99
 *@date:2023/5/13
 * -XX:StringTableSize=200000 设置桶个数
 * -XX:+PrintStringTableStatistics 打印字符串常量池的统计信息
 **/
public class StringTableBatter {
    public static void main(String[] args) throws IOException {
        try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), StandardCharsets.UTF_8))){
            String line = null;
            long start = System.nanoTime();
            while (true){
                line = reader.readLine();
                if (line == null){
                    break;
                }
                line.intern();
            }
            System.out.println("cost:" + (System.nanoTime() - start)/1000000);
        }
    }
}

将桶的数量设置为480000个-XX:StringTableSize=480000

20

480000个单词只用了0.144秒

考虑入池

修改一下代码

public class StringTableBatter {
    public static void main(String[] args) throws IOException {

        List<String> list = new ArrayList<>();
        System.in.read();
        for (int i = 0; i < 10; i++) {
            try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), StandardCharsets.UTF_8))){
                String line = null;
                long start = System.nanoTime();
                while (true){
                    line = reader.readLine();
                    if (line == null){
                        break;
                    }
                    list.add(line);
                }
                System.out.println("cost:" + (System.nanoTime() - start)/1000000);
            }
        }
        System.in.read();
    }
}

用Jvisualvm工具

发现大约4800000个单词,一共占用了300M左右的内存(char[]String)

21

将上面代码中的list.add(line)改为list.add(line.intern())发现占用的内存就30多M不到40M

22

如果你的

应用里有大量的字符串,而且还可能存在重复,那我们可以让字符串入池,来减少字符串个数,节约内存的使用。

直接内存

  • 常见于NIO操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受JVM内存回收管理

JVM直接内存(Direct Memory)是Java虚拟机在堆外(Off-Heap)分配的一块内存区域,它不受Java堆大小的限制,通常用于存储大量的数据或进行高性能的IO操作。

JVM直接内存与Java堆内存的区别在于分配和管理方式。Java堆内存通过垃圾回收器自动管理,而JVM直接内存则是由开发人员手动分配和释放。

JVM直接内存的优点包括:

  1. 直接访问:JVM直接内存可以直接在操作系统的内存空间中分配,避免了Java堆内存与操作系统之间的数据拷贝,提高了IO操作的性能。
  2. 较少的垃圾回收压力:JVM直接内存不受垃圾回收器的管理,因此不会影响垃圾回收的效率和延迟。
  3. 大内存支持:由于JVM直接内存不受Java堆大小的限制,可以分配更大的内存空间,适用于处理大规模数据和高并发场景。

然而,使用JVM直接内存也存在一些注意事项:

  1. 手动管理:开发人员需要手动分配和释放JVM直接内存,需要确保正确的内存使用和释放,避免内存泄漏或越界访问的问题。
  2. 内存溢出:JVM直接内存的分配不受Java堆大小限制,可能导致系统整体内存不足而产生OutOfMemoryError。
  3. 调优难度:由于JVM直接内存不受垃圾回收器的管理,需要开发人员自行进行性能调优和内存管理,包括合理控制内存分配和及时释放。

总之,JVM直接内存是Java虚拟机提供的一种可选的内存分配方式,适用于对性能和内存控制要求较高的场景。在使用JVM直接内存时,开发人员需要仔细考虑内存的分配和释放,并进行性能优化和监控,以确保系统的稳定性和性能表现。

用反射的方法获得一个Unsafe类

public static Unsafe getUnsafe(){
        try{
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe = (Unsafe)f.get(null);
            return unsafe;
        }catch (NoSuchFieldException | IllegalAccessException e){
            throw new RuntimeException(e);
        }
    }
public class MemoryManager {
    static int _1GB = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        //直接内存
        Unsafe unsafe = getUnsafe();
        long base = unsafe.allocateMemory(_1GB);
        unsafe.setMemory(base,_1GB,(byte)0);
        System.in.read();

        unsafe.freeMemory(base);
        System.in.read();

    }
}

分配内存前的内存占用

23

分配1G内存

24

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1GB);
System.out.println("分配完毕....");
System.in.read();
System.out.println("开始释放");
byteBuffer = null;
System.gc();
System.in.read();

此方法类似。

分配和回收原理

使用了Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法

ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存


结语

本文展示了JVM中的内存结构,希望对大家有所帮助!大家如果感兴趣可以点点赞,关注一下,你们的支持是我最强大的动力,非常感谢您的阅读(❁´◡`❁)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/19272.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis一级缓存详解

目录 一级缓存 一级缓存的组织 一级缓存的生命周期 一级缓存的工作流程 Cache接口的设计以及CacheKey的定义 一级缓存的性能分析 一级缓存与Spring 事务一级缓存存在的弊端 官方文档分析 Spring通过Mybatis调用数据库的过程 一级缓存 对于会话&#xff08;Session&am…

chanmama响应数据解析

0x00目标url aHR0cHM6Ly93d3cuY2hhbm1hbWEuY29tL2F1dGhvckRldGFpbC85OTI0MjExODcxOC9wcm9tb3Rpb24 0x01接口分析 简单的get 但是返回数据被加密了 这里我们就来想想怎么解密这些数据。首先后端发来的数据是加密的&#xff0c;但是我们在前端看到的可不是加密后的数据。前端…

什么是零拷贝?

零拷贝 什么是零拷贝 零拷贝指的是&#xff0c;从一个存储区域到另一个存储区域的copy任务无需CPU参与就可完成。零拷贝的底层是 通过DMA总线技术实现的。零拷贝与具体的编程语言无关&#xff0c;完全依赖于OS&#xff0c;OS支持就可使用&#xff0c;不支持 设置了也不起作用…

大厂视频面试,因为截屏作废

大厂视频面试现在这么严格了么&#xff1f;无意间按到截屏直接显示面试作废&#xff0c;好在最后和HR解释了下&#xff0c;再约时间重新面。 作为一个面试过3、4家大厂&#xff0c;现在在鹅厂工作的过来人来说&#xff0c;上面遇到的这个问题是AI面&#xff0c;不用太担心&…

笔记本电脑开机黑屏没反应怎么办?

笔记本电脑开机黑屏没反应怎么办&#xff1f;有用户电脑开机之后&#xff0c;桌面会变成黑屏显示。而且是常常都会出现这样的问题&#xff0c;非常影响自己的电脑使用体验。那么遇到这个问题要怎么去进行问题的解决呢&#xff1f;来看看以下的解决方法吧。 准备工作&#xff1a…

玩游戏时突然弹出”显示器驱动程序已停止响应并且已恢复”怎么办

随着3A游戏大作不断面市&#xff0c;用户也不断地提升着自己的硬件设备。但是硬件更上了&#xff0c;却还会出现一些突如其来的情况&#xff0c;比如正准备开启某款游戏时&#xff0c;电脑右下角突然出现“显示器驱动程序已停止响应并且已恢复”。遇事不慌&#xff0c;驱动人生…

java lambda表达式详解

一、Lambda初识 我们知道&#xff0c;在Java中&#xff0c;接口是不能实例化的&#xff0c;但是接口对象可以指向它的实现类对象。如果接口连实现对象都没有呢&#xff1f;那还可以使用匿名类的方式&#xff0c;如下: public class JavaTest { public static void main(Strin…

某程序员哀叹:二本计算机,4年开发,年包才40多万。二本真的不如985/211吗?

前段时间&#xff0c;某职场论坛上有人发了一个帖子&#xff0c;发帖人问&#xff1a;为什么大家工资那么高&#xff0c;三五年都六七十万了&#xff1f;我二本计算机专业&#xff0c;四年前端开发&#xff0c;找个年包40万多点就顶头了。 原贴如下&#xff1a; 有网友表示楼主…

2022年平均工资出炉,IT行业又是第一

根据5月9日国家统计局最新资料显示&#xff0c;2022年&#xff0c;全国城镇非私营单位就业人员年平均工资为114029元&#xff0c;比上年增长6.7%&#xff0c;扣除通胀后实际增长4.6%。其中&#xff0c;行业间的差距相当明显。根据资料显示&#xff0c;2022年无论是在私营单位还…

Android---bitmap优化

目录 Bitmap 占用内存大小计算 Bitmap | Drawable | InputStream | Byte[] 之间进行转换 Bitmap 相关方法 BitmapFactory 工厂类 Bitmap 占用内存大小计算 Bitmap 作为位图&#xff0c;需要读入一张图片中每一个像素点的数据&#xff0c;其主要占用内存的地方也正是这些像…

python进阶--月考二

python进阶--月考二 &#xff08;一&#xff09;装饰器&#xff08;二&#xff09;创建名为express.py文件&#xff0c;编写以下推导式&#xff08;25分&#xff09;&#xff08;三&#xff09;创建名为process_test.py的文件&#xff0c;计算1-3000之间的水仙花数&#xff08;…

QT MD4 MD5 Sha1等几种加密方式

QT MD4 MD5 Sha1等几种加密方式 [1] QT MD4 MD5 Sha1等几种加密方式[2] qt MD5 和AES 加密一 、MD5 加密二、AES 加密和解密 [3] QT中sqlite数据库数据加密/混淆---MD5/SHA1/SHA2/SHA3&#xff08;1&#xff09;创建一个加密对象&#xff08;2&#xff09;放入要加密的数据&…

目前可用的ChatGPT网站

本文意在整理可用gpt-3.5、gpt-4.0等网站。 本文主要是方便自己翻阅&#xff0c;如对您也有所帮助&#xff0c;不胜荣幸~ 文章目录 chatgpt.qdymys.cngpttalkchatgpt-cn.cobing.comchat机器人wuguokai.cn总结 chatgpt.qdymys.cn 网址&#xff1a;https://chatgpt.qdymys.cn/限…

SpringCloud —— eureka

目录 1.认识微服务 1.0.学习目标 1.1.单体架构 1.2.分布式架构 1.3.微服务 1.4.SpringCloud 1.5.总结 2.服务拆分和远程调用 2.1.服务拆分原则 2.2.服务拆分示例 2.2.1.导入Sql语句 2.2.2.导入demo工程 2.3.实现远程调用案例 2.3.1.案例需求&#xff1a; 2.3.2.注…

mysql进阶-查询优化-慢查询日志

文章目录 一、什么是慢查询日志二、慢查询日志能干什么2.1 性能分析和优化2.2 诊断和排查问题2.3 数据分析和探索 三、慢查询日志实战3.1 永久开启开启慢查询日志3.2 临时开启慢查询日志3.4 常用命令 四、如何分析慢查询日志五、优化慢查询语句五、总结 一、什么是慢查询日志 …

观察者设计模式(Observer Design Pattern)[论点:概念、组成角色、相关图示、示例代码、框架中的运用、适用场景]

文章目录 概念组成角色相关图示示例代码框架中的运用适用场景 概念 观察者设计模式&#xff08;Observer Design Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一种对象间的一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象&#xff0c;当…

SpringBoot 配置文件

前言&#xff1a; 本篇主要介绍两种配置文件格式&#xff0c;分别为properties与yml(yaml)。 需要注意的是&#xff1a; 两个配置文件从功能上来讲是可以同时存在的&#xff0c;但是企业中通常会规定使用某一种格式的配置文件。如果同一个配置出现在两种格式的配置文件中的话&a…

微波方向有哪些SCI期刊推荐? - 易智编译EaseEditing

微波方向的SCI期刊推荐包括&#xff1a; IEEE Transactions on Microwave Theory and Technology&#xff1a; 该期刊是电磁场与微波技术领域的著名期刊&#xff0c;被世界上许多研究机构和大学广泛引用。 IEEE Transactions on Antennas and Propagation&#xff1a; 该期刊…

English Learning - L3 作业打卡 Lesson1 Day5 2023.5.9 周二

English Learning - L3 作业打卡 Lesson1 Day5 2023.5.9 周二 引言&#x1f349;句1: Fast loud music is popular with many people .成分划分爆破语调 &#x1f349;句2: They may say the music is red hot, especially the kind called Dixieland jazz.成分划分爆破语调 &a…

AI孙燕姿项目实现

最近在b站刷到很多关于ai孙笑川唱的歌曲&#xff0c;加上最近大火的ai孙燕姿&#xff0c; 这下“冷门歌手”整成热门歌手了 于是写下一篇文章&#xff0c; 如何实现属于的ai歌手。 注意滥用ai&#xff0c;侵犯他人的名誉是要承担法律责任的 下面是一些所需的文件链接&#xff…