JVM学习之 内存结构


目录
  • JVM规范-方法区定义

    5.2组成

    在这里插入图片描述

    5.3方法区内存溢出

    • 1.8 以前会导致永久代内存溢出
      演示永久代内存溢出 java.lang.OutOfMemoryError: PermGen space
      -XX:MaxPermSize=8m

    • 1.8 之后会导致元空间内存溢出
      演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace
      -XX:MaxMetaspaceSize=8m
      场景:

          spring
          mybatis
    

    5.4 运行时常量池

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

    然后使用 javap -v Test.class 命令反编译查看结果:
    在这里插入图片描述
    每条指令都会对应常量池表中一个地址,常量池表中的地址可能对应着一个类名、方法名、参数类型等信息。
    在这里插入图片描述

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

    5.5 StringTable

    面试题:

            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";
    
    // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢
            System.out.println(x1 == x2);
                    // jdk1.6:
            // String x1 = "cd";            x2.intern();
            // x2.intern();  false          String x1 = "cd"; ture
    
            // jdk1.8:
            // String x1 = "cd";            x2.intern();
            // x2.intern();  false          String x1 = "cd"; ture
    

    练习:

    // StringTable [ "a", "b" ,"ab" ]  hashtable 结构,不能扩容
    public class Demo1_22 {
        // 常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
        // ldc #2 会把 a 符号变为 "a" 字符串对象
        // ldc #3 会把 b 符号变为 "b" 字符串对象
        // ldc #4 会把 ab 符号变为 "ab" 字符串对象
    
        public static void main(String[] args) {
            String s1 = "a"; // 懒惰的
            String s2 = "b";
            String s3 = "ab";
            String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()  new String("ab")
            String s5 = "a" + "b";  // javac 在编译期间的优化,结果已经在编译期确定为ab
            System.out.println(s3 == s4);//s3是在串池中的,而s4则是在堆中,所有不相等
    
            System.out.println(s3 == s5);// true
        }
    }
    

    使用javap -v Demo1_22.class命令

    在这里插入图片描述

    5.6 StringTable的特性

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

    5.7 StringTable 位置

    jdk1.6 StringTable 位置是在永久代中,1.8 StringTable 位置是在堆中。
    在这里插入图片描述

    5.8 StringTable 垃圾回收

    -Xmx10m 指定堆内存大小
    -XX:+PrintStringTableStatistics 打印字符串常量池信息
    -XX:+PrintGCDetails
    -verbose:gc 打印 gc 的次数,耗费时间等信息

    演示StingTable垃圾回收:

    public static void main(String[] args) throws InterruptedException {
            int i = 0;
            try {
                for (int j = 0; j < 100000; j++) { // j=100, j=10000
                    String.valueOf(j).intern();
                    i++;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            } finally {
                System.out.println(i);
            }
    
        }
    

    在这里插入图片描述

    5.9 StringTable 性能调优

    调整 -XX:StringTableSize=桶个数

    考虑将字符串对象是否入池

    6. 直接内存

    6.1 定义

    Direct Memory

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

    文件读写过程(IO):
    在这里插入图片描述

    因为 java 不能直接操作文件管理,需要切换到内核态,使用本地方法进行操作,然后读取磁盘文件,会在系统内存中创建一个缓冲区,将数据读到系统缓冲区, 然后在将系统缓冲区数据,复制到 java 堆内存中。缺点是数据存储了两份,在系统内存中有一份,java 堆中有一份,造成了不必要的复制。

    使用了 DirectBuffer 文件读取流程:
    在这里插入图片描述
    直接内存是操作系统和 Java 代码都可以访问的一块区域,无需将代码从系统内存复制到 Java 堆内存,从而提高了效率。

    6.2 分配和回收原理

    • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
    • ByteBu?er 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBu?er 对象,一但ByteBu?er 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory方法 来释放直接内存。