JVM----①内存结构

什么是JVM?

在这里插入图片描述

在这里插入图片描述

JVM简图
在这里插入图片描述
在这里插入图片描述

内存结构

1. 程序计数器
2. 虚拟机栈
3. 本地方法栈
4. 堆
5. 方法区

①程序计数器

Program Counter Register 程序计数器(寄存器)
在这里插入图片描述

  • 作用,是记住下一条jvm指令的执行地址;当多个线程执行代码时,需要获取cpu的执行时间片才可以执行,程序计数器作用就是记住下一次从哪里开始执行的位置。
  • 特点
    • 是线程私有的
    • 唯一一个不会存在内存溢出

②虚拟机栈

Java Virtual Machine Stacks (Java 虚拟机栈)

在这里插入图片描述
虚拟机栈类似于子弹夹,一个压子弹的过程,先压的后出来,先进后出;一个子弹就类似于一个栈帧,一个栈帧就对应一个方法的调用,每个方法在执行的时候都会创建一个栈帧,栈帧包含存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型(局部变量表编译器完成,运行期间不会变化)
在这里插入图片描述
在这里插入图片描述

  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

虚拟机栈的演示:
在这里插入图片描述
在这里插入图片描述
栈帧的出栈时会返回到调用该栈帧的返回地址,活动栈帧就是正在执行的栈帧,也就是正在执行的某一个方法;

问题辨析

  1. 垃圾回收是否涉及栈内存
    不会,栈内存就是一次次方法调用,产生是栈帧内存,当该方法执行后对应的栈帧也就被弹出栈,自动的就被回收了,不用垃圾回收

  2. 栈内存分配越大越好吗
    在这里插入图片描述
    栈内存在jvm启动时可以设置;栈内存设置越大,线程数就会越少,一般栈默认是1M大小;

  3. 方法内的局部变量是否线程安全

    • 如果方法内局部变量没有逃离方法的作用访问,它是线程安全的
    • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全问题

栈内存溢出

  • 栈帧过多导致栈内存溢出(递归调用)
  • 栈帧过大导致栈内存溢出
/**
 * 演示栈内存溢出 java.lang.StackOverflowError
 * 设置栈内存大小: -Xss256k
 */
public class Demo1_2 {
    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();
    }
}

线程运行诊断

案例1: cpu 占用过多定位

  • 1.用top定位哪个进程对cpu的占用过高
    在这里插入图片描述

  • 2.ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高) H输出进程内容 -eo输出感兴趣的内容
    在这里插入图片描述

  • 3.jstack 进程id; 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号
    在这里插入图片描述
    jstack 32655 ,会将java进程ID是32655 内部所有的线程都会打印出来,32665线程Id需要转为16进制7f99,因为jstack 命令输出的线程id是16进制表示的;

案例2:程序运行很长时间没有结果
演示死锁案例:

/**
 * 演示线程死锁
 */
class A{};
class B{};
public class Demo1_3 {
    static A a = new A();
    static B b = new B();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (a) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (b) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start();
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (b) {
                synchronized (a) {
                    System.out.println("我获得了 a 和 b");
                }
            }
        }).start();
    }
}

执行jstack pid
在这里插入图片描述

③本地方法栈

在这里插入图片描述
用C,C++语言编写的本地方法(native)与操作系统底层api打交道;给本地方法的运行提供内存空间。
在这里插入图片描述

④堆

在这里插入图片描述

Heap 堆

  • 通过 new 关键字,创建对象都会使用堆内存
  • 特点
    • 它是线程共享的,堆中对象都需要考虑线程安全的问题
    • 有垃圾回收机制

堆内存溢出

/**
 * 演示堆内存溢出 java.lang.OutOfMemoryError: Java heap space
 * 堆内存内存设置参数:-Xmx8m
 */
public class Demo1_5 {

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

堆内存诊断

  1. jps 工具 : 查看当前系统中有哪些 java 进程

  2. jmap 工具: 查看堆内存占用情况 jmap -heap 进程id (只能查询某一个时刻堆内存使用情况)

/**
 * 演示堆内存
 */
public class Demo1_4 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("1...");
        Thread.sleep(30000);
        byte[] array = new byte[1024 * 1024 * 10]; // 10 Mb
        System.out.println("2...");
        Thread.sleep(20000);
        array = null;
        System.gc();
        System.out.println("3...");
        Thread.sleep(1000000L);
    }
}
------>>>>>执行Jps命令
D:\other\jvm\>jps
11648 Jps
5600 Demo1_4
8400
10052 Launcher
12196 RemoteMavenServer
------>>>>>第一次执行jmap -heap 5600 命令
Heap Usage:
PS Young Generation
Eden Space: >>>>>新创建的对象都先会分配到Eden 区
   capacity = 34078720 (32.5MB)
   used     = 4783456 (4.561859130859375MB) #使用了4M
   free     = 29295264 (27.938140869140625MB)
   14.036489633413462% used
------>>>>>第二次执行jmap -heap 5600 命令
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 34078720 (32.5MB)
   used     = 15269232 (14.561874389648438MB) #使用了14M
   free     = 18809488 (17.938125610351562MB)
   44.80576735276442% used
------>>>>>第三次执行jmap -heap 5600 命令  # 调用System.gc();  
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 34078720 (32.5MB)
   used     = 681592 (0.6500167846679688MB) #gc后明显减少了
   free     = 33397128 (31.84998321533203MB)
   2.0000516451322117% used
   
  1. jconsole 工具: 图形界面的,多功能的监测工具,可以连续监测

在这里插入图片描述
案例:垃圾回收后,内存占用仍然很高;说明GC后并没有回收垃圾,还存在引用。

/**
 * 演示查看对象个数 堆转储 dump
 */
public class Demo1_13 {

    public static void main(String[] args) throws InterruptedException {
    //students 一直有强引用,不会被垃圾回收
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            students.add(new Student());
//            Student student = new Student();
        }
        Thread.sleep(1000000000L);
    }
}
class Student {
    private byte[] big = new byte[1024*1024];
}

内存诊断_jvisualvm
在这里插入图片描述
在这里插入图片描述

⑤方法区

在这里插入图片描述
最权威的定义:
在这里插入图片描述
在这里插入图片描述
在jdk1.6版本中方法区是概念上的划分,用永久代作为了方法区的实现,存储了类元信息,类加载器,运行时常量池(StringTable),占用jvm堆内存;
到jdk1.8版本中方法区还是概念上的东西,用元空间作为了方法区的实现,也是存储了类元信息,类加载器,常量池;但是他不在占有堆内存了,不在是jvm来管理它的结构了,移到操作系统的本地内存了,但运行时常量池(StringTable)仍然分配在堆中。运行时常量池下面会重点分析。

方法区内存溢出

/**
 * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
 * -XX:MaxMetaspaceSize=8m;默认情况下是系统的内存,演示需要加这个参数
 */
public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码
    public static void main(String[] args) {
        int j = 0;
        try {
            Demo1_8 test = new Demo1_8();
            for (int i = 0; i < 10000; i++, j++) {
                // ClassWriter 作用是生成类的二进制字节码
                ClassWriter cw = new ClassWriter(0);
                // 版本号, public, 类名, 包名, 父类, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 返回 byte[]
                byte[] code = cw.toByteArray();
                // 执行了类的加载;但是 连接,初始化阶段未执行
                test.defineClass("Class" + i, code, 0, code.length); // Class 对象
            }
        } finally {
            System.out.println(j);
        }
    }
}
//在jdk1.8异常 java.lang.OutOfMemoryError: Metaspace   对用jvm参数是:-XX:MaxMetaspaceSize=8m
//jdk1.6异常 java.lang.OutOfMemoryError: PermGen space 对用jvm参数是:-XX:MaxPermSize=8m

  • 1.8 以前会导致永久代内存溢出
    演示永久代内存溢出 java.lang.OutOfMemoryError:PermGenspace
    -XX:MaxPermSize=8m
  • 1.8 之后会导致元空间内存溢出
    演示元空间内存溢出java.lang.OutOfMemoryError:Metaspace
    -XX:MaxMetaspaceSize=8m

场景
动态生成类是很正常的,在spring,mybatis中都有使用,cglib中asm包就是动态生成class字节码的技术封装。

运行时常量池

字节码的基本组成分类
1.类基本信息,2.常量池,3.类方法定义,包含了虚拟机指令

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

使用 javap -v HelloWorld.class 命令对class文件进行反编译, -v 显示详细信息
在这里插入图片描述

 Last modified 2020-3-13; size 567 bytes
  MD5 checksum 8efebdac91aa496515fa1c161184e354
  Compiled from "HelloWorld.java"
public class cn.jvm.t5.HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
  ---------------------以上是 1.类基本信息-------------------
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // cn/jvm/t5/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/itcast/jvm/t5/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               cn/itcast/jvm/t5/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
  ---------------------以上是 2.常量池-------------------
{
  public cn.itcast.jvm.t5.HelloWorld(); 构造方法
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/itcast/jvm/t5/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
    -------下面对应虚拟机指令---------
      stack=2, locals=1, args_size=1
         0: getstatic     #2   获取静态变量    // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3   加载参数        // String hello world
         5: invokevirtual #4   执行虚方法调用 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return             方法执行结束
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
---------------------以上是 3.类方法定义,包含了虚拟机指令-------------------
SourceFile: "HelloWorld.java"

  • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池(加载到堆中被使用),并把里面的符号地址变为真实地址

案例:

public static void main(String[] args) {
        String s1 = "a"; 
        String s2 = "b";
        String s3 = "ab";
 }
 对应的字节码:
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1 //4个局部变量表,其中包含this的传参
         0: ldc           #2   加载常量池2号的常量     // String a
         2: astore_1           储存到下面 LocalVariableTable Slot为1的地方
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: return
      LineNumberTable:
        line 11: 0
        line 12: 3
        line 13: 6
        line 19: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1    s1   Ljava/lang/String;
            6       4     2    s2   Ljava/lang/String;
            9       1     3    s3   Ljava/lang/String;
}
当该类被加载,它的常量池信息就会放入运行时常量池,但是注意这时 a b ab 都是常量池中的符号,
还没有变为 java 字符串对象,只有第一次使用了才会在运行时常量池真正的生成java的对象。
比如执行到 String s1 = "a" 这行代码时,执行 ldc #2 会把 a 符号变为 "a" 字符串对象,
在运行时常量池里面有一个 StringTable 对象(数据结构是hash表)找有没有"a" 字符串对象,
刚开始里面是空的,没有对象。他就会在StringTable里面找有没有字符串为"a"的值,第一次是没有的,
它就会把生成的 "a" 放入到StringTable,并返回StringTable里面的 "a" 值了;
同样,执行到String s2 = "b"这行代码时,在StringTable 数据里面又多了"b"这个对象,
执行到String s3 = "ab" 这行代码时,在StringTable 数据里面又多了"ab"这个对象,
这样StringTable 就从开始的没有对象就有了这3个对象 "a" "b" "ab"了。

注意,并不是每个字符串对象事先就放到StringTable里面,而是执行的用到它的这行代码
才开始创建这个字符串对象并放到StringTable,以后就可以复用这个字符串对象了,这个行为是一个懒惰的行为。 

当执行 String s4 = s1 + s2; 这行代码时

        String s1 = "a"; // 懒惰的
        String s2 = "b";
        String s3 = "ab";
        //分析得到就是 new String("ab") new了一个对象
        String s4 = s1 + s2;// new StringBuilder().append("a").append("b").toString()  new String("ab")
        System.out.println(s3 == s4); //false

在这里插入图片描述
在这里插入图片描述
当执行 String s5 = “a” + “b”; 这行代码时

String s1 = "a"; // 懒惰的
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2; 
        String s5 = "a" + "b"; 
        System.out.println(s3 == s4); //false
        System.out.println(s3 == s5); //true

在这里插入图片描述
测试字符串加载时延时的特性案例:

/**
 * 演示字符串字面量也是【延迟】成为对象的
 */
public class TestString {
    public static void main(String[] args) {
        int x = args.length;
        System.out.println(); // 字符串个数 2275

        System.out.print("1");// 字符串个数 2276
        System.out.print("2");// 字符串个数 2277
        System.out.print("3");// 字符串个数 2278
        System.out.print("4");// 字符串个数 2279
        System.out.print("5");// 字符串个数 2280
        System.out.print("6");// 字符串个数 2281
        System.out.print("7");// 字符串个数 2282
        System.out.print("8");// 字符串个数 2283
        System.out.print("9");// 字符串个数 2284
        System.out.print("0");// 字符串个数 2285
        System.out.print("1");// 字符串个数 2285
        System.out.print("2");
        System.out.print("3");
        System.out.print("4");
        System.out.print("5");
        System.out.print("6");
        System.out.print("7");
        System.out.print("8");
        System.out.print("9");
        System.out.print("0");// 字符串个数 2285
        System.out.print(x); // 字符串个数
    }
}

StringTable 特性

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

测试:

public class Demo1_23 { // jdk 1.8 测试

    //  ["ab", "a", "b"]
    public static void main(String[] args) {

        String x = "ab";
        //下面new String("ab")得到s,但是s不会放入到常量池中,可以通过intern方法将其放入到常量池中
        String s = new String("a") + new String("b");

        // 堆  new String("a")   new String("b") new String("ab")
        String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回

        System.out.println( s2 == x);//true
        System.out.println( s == x );//false
    }
}

字符串相关面试题

/**
 * 演示字符串相关面试题
 */
public class Demo1_21 {

    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b"; // ab
        String s4 = s1 + s2;   // new String("ab") 堆中的对象
        String s5 = "ab";
        String s6 = s4.intern();

// 问
        System.out.println(s3 == s4); // false
        System.out.println(s3 == s5); // true
        System.out.println(s3 == s6); // true

        String x2 = new String("c") + new String("d"); // new String("cd")  堆中的对象
        x2.intern();//如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
        String x1 = "cd";

// 问,如果调换了【最后两行代码】的位置呢
        System.out.println(x1 == x2);//true
    }
}

StringTable 位置

在这里插入图片描述
StringTable之前在永久代,而永久代在FullGc的时候才会发生垃圾回收,而字符串使用很频繁,1.8移动到堆中,只需要minorgc就会发生垃圾回收,垃圾回收效率得到提高

/**
 * 演示 StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit    java.lang.OutOfMemoryError: Java heap space
 * 在jdk6下设置 -XX:MaxPermSize=10m    java.lang.OutOfMemoryError: PermGen space
 */
public class Demo1_6 {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            for (int j = 0; j < 260000; j++) {
                list.add(String.valueOf(j).intern());
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

StringTable 垃圾回收

/**
 * 演示 StringTable 垃圾回收
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class Demo1_7 {
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
            for (int j = 0; j < 100; j++) { // j=0, j=100, j=10000
                String.valueOf(j).intern();
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

StringTable内部是hashtable,数组 + 链表实现
j=0时:
在这里插入图片描述
j=100时:没有发生垃圾回收
在这里插入图片描述

j=10000时:发生了垃圾回收,10M内存存不下10000个字符串,发生了gc
在这里插入图片描述
在这里插入图片描述

StringTable 性能调优

/**
 * 演示串池大小对性能的影响
 * -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
 */
public class Demo1_24 {

    public static void main(String[] args) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "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);
        }
    }
}

默认桶的个数是60013个,耗时331ms
在这里插入图片描述
改变桶的大小 -XX:StringTableSize=1009,这样hash碰撞概率就会增加,耗时8224ms
在这里插入图片描述
当字符串比较多并且很多重复的字符串的的时候,可以适当的增加StringTableSize,减少hash碰撞的几率;同时需要考虑将字符串对象是否入池。

/**
 * 演示 intern 减少内存占用
 * -XX:StringTableSize=200000 -XX:+PrintStringTableStatistics
 * -Xsx500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=200000
 */
public class Demo1_25 {
    public static void main(String[] args) throws IOException {

        //防止垃圾回收,测试方便
        List<String> address = new ArrayList<>();
        System.in.read();
        for (int i = 0; i < 10; i++) {
        //linux.words 文件有480万个字符串,但是只有48万个相同的,其余都是重复的
            try (BufferedReader reader = 
            new BufferedReader(new InputStreamReader(new FileInputStream("linux.words"), "utf-8"))) {
                String line = null;
                long start = System.nanoTime();
                while (true) {
                    line = reader.readLine();
                    if(line == null) {
                        break;
                    }
                    address.add(line);//test1
                    address.add(line.intern());//test2

                }
                System.out.println("cost:" +(System.nanoTime()-start)/1000000);
            }
        }
        System.in.read();
    }
}

address.add(line) //test1
在这里插入图片描述

address.add(line.intern()) //test2
在这里插入图片描述
很明显在很多字符串重复的情况下,加到StringTable是可以提高效率的,当然,经常被使用的字符串是最好的了。

直接内存

定义 Direct Memory

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

在这里插入图片描述
在这里插入图片描述
演示直接内存溢出

在这里插入图片描述

直接内存分配的底层原理

/**
 * 直接内存分配的底层原理:Unsafe
 */
public class Demo1_27 {
    static int _1Gb = 1024 * 1024 * 1024;

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

        // 释放内存  直接内存分配方法
        unsafe.freeMemory(base);
        System.in.read();
    }

    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);
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分配和回收原理

  • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
  • ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存
发布了138 篇原创文章 · 获赞 3 · 访问量 7250

猜你喜欢

转载自blog.csdn.net/weixin_43719015/article/details/104848458