美文网首页jvm
图解jvm--(一)jvm内存结构

图解jvm--(一)jvm内存结构

作者: 韩who | 来源:发表于2020-02-06 22:47 被阅读0次

    jvm内存结构

    jvm内存结构

    1.程序计数器

    1.1 定义

    Program Counter Register 程序计数器(寄存器)

    • 作用,记住下一条jvm指令的执行地址
    • 特点
      • 是线程私有的
      • (唯一)不会存在内存溢出

    1.2 作用

    二进制字节码 jvm指令

     public int add();
        Code:
           0: iconst_1    // 把1压入操作数栈中
           1: istore_1    //将int类型值存入局部变量1,这个局部变量1指局部变量表中的第一个数
           2: iconst_2        
           3: istore_2
           4: iload_1     //从局部变量1中装载int类型值,这里的局部变量指第一个数,即a
           5: iload_2
           6: iadd         //执行相加
           7: istore_3    //存储c
           8: iload_3     //装载c
           9: ireturn     //返回
    }
    
    image

    实现:

    通过寄存器实现,把cup的寄存器当做程序计数器

    2.虚拟机栈

    image

    2.1定义

    java Virtual Machine Stacks (java 虚拟机栈)

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

    问题:

    1. 垃圾回收是否涉及栈内存?方法调用完会自动弹出,回收内存,垃圾回收不涉及栈内存
    2. 栈内存分配越大越好?栈内存划得越大,线程数会越少,因为内存有限,栈内存是线程独享
    3. 方法内的局部变量是否线程安全? 线程栈的线程私有的,是线程安全的
      • 如果方法内局部变量,没有逃离方法的作用范围,它是线程安全的
      • 如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全
    /**
     * 演示栈帧
     */
    public class Demo1_1 {
        public static void main(String[] args) throws InterruptedException {
            method1();
        }
    
        private static void method1() {
            method2(1, 2);
        }
    
        private static int method2(int a, int b) {
            int c =  a + b;
            return c;
        }
    }
    
    

    通过上面的代码,设置断点,进行debug,可以观察method1以及method2的方法栈出入情况

    image

    2.2栈内存溢出

    image
    • 栈帧过多,导致栈内存溢出
    • 栈帧过大,导致栈内存溢出

    2.3线程运行诊断

    案例1: cpu占用过多

    定位

    Linux 的 nohup java 命令 可以后台执行java代码

    ps 命令可以 查看进程与线程的对应cpu占用情况

    • 用top定位哪个进程对cpu的占用过高
    • ps H -eo pid,tid,%cpu |grep 进程 id (用ps命令进一步定位是哪个线程引起的cpu占用过高)
    • jstack 进程id (列出java线程)
      • 可以根据线程id找到有问题的线程,进一步定位到问题代码的源码行号(进程号需要转为16进制进行查找)

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

    死锁

    使用 jstack 看死锁

    3.本地方法栈

    本地方法栈发挥的作用与虚拟机栈的作用类似,用于native修饰的方法

    4.堆

    4.1 定义

    Heap 堆

    • 通过 new 关键字,创建对象都会使用堆内存

    特点

    • 它是线程共享的,堆中对象都需要考虑线程安全问题
    • 有垃圾回收机制

    4.2 堆内存溢出

    /**
     * 演示堆内存溢出 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);
            }
        }
    }
    

    下图可以调整堆内存大小

    image

    4.3 堆内存诊断

    1. jps工具

      • 查看当前系统中有哪些java进程
    2. jmap工具

      • 查看堆内存占用情况 jmap
    3. jconsole工具

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

      4.jvisualvm(推荐使用)

    package cn.itcast.jvm.t1.heap;
    
    /**
     * 演示堆内存
     */
    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);
        }
    }
    
    

    打开cmd 输入 jps命令查看当前java线程

    使用 jmap -heap 线程号 可以查看当前线程的堆的使用情况

    在代码中的1,2,3步骤中分别执行3次,可以得到3个结果

    image image

    案例:

    • 垃圾回收后,内存占用仍然很高

      /**
       * 演示查看对象个数 堆转储 dump
       */
      public class Demo1_13 {
      
          public static void main(String[] args) throws InterruptedException {
              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 命令进行诊断

      使用 堆Dunp 进行对当前线程内存进行转储快照,进而分析为什么垃圾回收后内存还很高

      image

    找出占用内存最大的一个数组,查看

    image

    找到原来时数组里面对象太多,占用了很多内存

    image

    5.方法区

    5.1 定义

    权威定义

    5.2 组成

    jdk 1.6 对方法区的实现称为永久代

    jdk 1.8 对方法区的实现称为元空间

    jdk1.6

    image

    jdk1.8以前,方法区是在jvm内存上面,jdk1.8以后,方法区是一个逻辑分区,在计算机本地内存上面

    jdk1.8

    image

    5.3 方法区内存溢出

    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 < 20000; i++, j++) {
                    //ClassWriter 作用是生成类的二进制字节码
                    ClassWriter cw = new ClassWriter(0);
                    //版本号,pubblic,类名,包名,父类
                    cw.visit(Opcodes.V1_6, 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);
            }
        }
    }
    
    • 1.8以前(1.6)会导致永久代内存溢出

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

      演示元空间内存溢出  java.lang.OutOfMemoryError: Metaspace
      -XX:MaxMetaspaceSize=8m
      

    场景:框架会产生很多运行时的类,容易导致内存溢出

    • spring

    • mybatis

      都用到cglib

    5.3 运行时常量池

    • 常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息
    • 运行时常量池,常量池是*.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
    // 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
    public class Demo1_22 {
    
        public static void main(String[] args) {
            String s1 = "a"; //懒惰
            String s2 = "b";
            String s3 = "ab";
        }
    
    

    执行以下命令对代码进行反编译

    javap -v Demo1_22.class
    
    
    Classfile /D:/IDEAworkplace/jvm/out/production/jvm/cn/itcast/jvm/t1/stringtable/Demo1_22.class
      Last modified 2020-1-30; size 534 bytes
      MD5 checksum 5c4213b2f1defff2bb24bf7cbd5ff183
      Compiled from "Demo1_22.java"
    public class cn.itcast.jvm.t1.stringtable.Demo1_22
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
          //常量池
    Constant pool:
       #1 = Methodref          #6.#24         // java/lang/Object."<init>":()V
       #2 = String             #25            // a
       #3 = String             #26            // b
       #4 = String             #27            // ab
       #5 = Class              #28            // cn/itcast/jvm/t1/stringtable/Demo1_22
       #6 = Class              #29            // 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/t1/stringtable/Demo1_22;
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               args
      #17 = Utf8               [Ljava/lang/String;
      #18 = Utf8               s1
      #19 = Utf8               Ljava/lang/String;
      #20 = Utf8               s2
      #21 = Utf8               s3
      #22 = Utf8               SourceFile
      #23 = Utf8               Demo1_22.java
      #24 = NameAndType        #7:#8          // "<init>":()V
      #25 = Utf8               a
      #26 = Utf8               b
      #27 = Utf8               ab
      #28 = Utf8               cn/itcast/jvm/t1/stringtable/Demo1_22
      #29 = Utf8               java/lang/Object
    {
      public cn.itcast.jvm.t1.stringtable.Demo1_22();
        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/t1/stringtable/Demo1_22;
    
      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
             0: ldc           #2                  // String a
             2: astore_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 26: 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;
    }
    SourceFile: "Demo1_22.java"
    
    

    5.4 StringTable(串池)

    StringTable 是运行时常量池中的一个东西

    // 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 == s5);
    
        }
    }
    
    //结果
    s4 不等于 s3 //s3是串池中的,s4是通过new对象生成的,其值存在堆中
    s3 等于 s5 //
        
        
    
    

    对于 单独的赋值,是对数据的直接到StringTable中取找,如果没有,则创建,有则直接取

    对于 s4 = s1+s2 则是使用StringBuilder(),方法进行拼接,结果是一个新的对象,该对象存在堆上面

    使用 javap -v 命令后编译结果如下

    public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=3, locals=6, args_size=1
             0: ldc           #2                  // String a
             2: astore_1
             3: ldc           #3                  // String b
             5: astore_2
             6: ldc           #4                  // String ab
             8: astore_3
             9: new           #5                  // class java/lang/StringBuilder
            12: dup
            13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            16: aload_1
            17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            20: aload_2
            21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            27: astore        4
            29: ldc           #4                  // String ab
            31: astore        5
            33: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
            36: aload_3
            37: aload         5
            39: if_acmpne     46
            42: iconst_1
            43: goto          47
            46: iconst_0
            47: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
            50: return
    
    

    常量池内容

    Constant pool:
       #1 = Methodref          #12.#36        // java/lang/Object."<init>":()V
       #2 = String             #37            // a
       #3 = String             #38            // b
       #4 = String             #39            // ab
       #5 = Class              #40            // java/lang/StringBuilder
       #6 = Methodref          #5.#36         // java/lang/StringBuilder."<init>":()V
       #7 = Methodref          #5.#41         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       #8 = Methodref          #5.#42         // java/lang/StringBuilder.toString:()Ljava/lang/String;
       #9 = Fieldref           #43.#44        // java/lang/System.out:Ljava/io/PrintStream;
      #10 = Methodref          #45.#46        // java/io/PrintStream.println:(Z)V
      #11 = Class              #47            // cn/itcast/jvm/t1/stringtable/Demo1_22
      #12 = Class              #48            // java/lang/Object
      #13 = Utf8               <init>
      #14 = Utf8               ()V
      #15 = Utf8               Code
      #16 = Utf8               LineNumberTable
      #17 = Utf8               LocalVariableTable
      #18 = Utf8               this
      #19 = Utf8               Lcn/itcast/jvm/t1/stringtable/Demo1_22;
      #20 = Utf8               main
      #21 = Utf8               ([Ljava/lang/String;)V
      #22 = Utf8               args
      #23 = Utf8               [Ljava/lang/String;
      #24 = Utf8               s1
      #25 = Utf8               Ljava/lang/String;
      #26 = Utf8               s2
      #27 = Utf8               s3
      #28 = Utf8               s4
      #29 = Utf8               s5
      #30 = Utf8               StackMapTable
      #31 = Class              #23            // "[Ljava/lang/String;"
      #32 = Class              #49            // java/lang/String
      #33 = Class              #50            // java/io/PrintStream
      #34 = Utf8               SourceFile
      #35 = Utf8               Demo1_22.java
      #36 = NameAndType        #13:#14        // "<init>":()V
      #37 = Utf8               a
      #38 = Utf8               b
      #39 = Utf8               ab
      #40 = Utf8               java/lang/StringBuilder
      #41 = NameAndType        #51:#52        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #42 = NameAndType        #53:#54        // toString:()Ljava/lang/String;
      #43 = Class              #55            // java/lang/System
      #44 = NameAndType        #56:#57        // out:Ljava/io/PrintStream;
      #45 = Class              #50            // java/io/PrintStream
      #46 = NameAndType        #58:#59        // println:(Z)V
      #47 = Utf8               cn/itcast/jvm/t1/stringtable/Demo1_22
      #48 = Utf8               java/lang/Object
      #49 = Utf8               java/lang/String
      #50 = Utf8               java/io/PrintStream
      #51 = Utf8               append
      #52 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #53 = Utf8               toString
      #54 = Utf8               ()Ljava/lang/String;
      #55 = Utf8               java/lang/System
      #56 = Utf8               out
      #57 = Utf8               Ljava/io/PrintStream;
      #58 = Utf8               println
      #59 = Utf8               (Z)V
    
    

    5.5 StringTable特性

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

    先看几道面试题:

    String s1 = "a";
    String s2 = "b";
    String s3 = "a" + "b"; //ab
    Strin s4 = s1 + s2;  //new String("ab")
    String s5 = "ab";
    String s6 = s4.intern();//常量池中以及有"ab"了,所以s4没能入池成功
    
    //问
    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");
    String x1 = "cd";
    x2.intern();//intern 方法在jdk1.6之前是不一样的
    
    //问,如果调换了【最后两行代码】的位置呢?如果jdk1.6呢?
    System.out.println(x1 == x2);//false
    

    常量池与串池的关系:

    常量池一开始是存在于字节码中,当运行时,都会加载到运行时常量池中,常量池中的信息都会被加载到运行时常量池,此时常量池中的符号还不是java的字符串对象

    StringTable 的字符串是延迟加载机制,即要使用才加载进来,

    常量池中的信息,都会被加载到运行时常量池中, 这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象
    ldc #2 会把 a 符号变为 "a" 字符串对象
    ldc #3 会把 b 符号变为 "b" 字符串对象
    ldc #4 会把 ab 符号变为 "ab" 字符串对象

    // 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";
        }
        
    
        //对应jvm指令为
        Code:
          stack=1, locals=4, args_size=1
             0: ldc           #2                  // String a
             2: astore_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 26: 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;
    }
    
       
    

    从指令中可以看出 String s4 = s1 + s2,是将s1以及s2的值使用StringBuilder进行拼接,最后调用StringBuilder的toString方法进行重新生成一个新的对象( 等于new StringBuilder().append("a").append("b").toString() new String("ab"))的底层是通过StringBuilder进行字符串的拼接生成字符串对象,存储在堆内存中 ,所以s4 不等于 s3

    String s4 = s1 + s2;
    //这一行代码的jvm指令为
            9: new           #5                  // class java/lang/StringBuilder
            12: dup
            13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
            16: aload_1
            17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            20: aload_2
            21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
            24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            27: astore        4
                
    
    //查看StringBuilder 的 toString方法,看出来是生成一个新的对象
        @Override
        public String toString() {
            // Create a copy, don't share the array
            return new String(value, 0, count);
        }
    
    
    

    String s5 = "a" + "b"; (javac在编译初期的优化,就已经确定为ab了,所以jvm指令直接读取ab,并生成对象)所以 s5 等于 s3

     String s5 = "a" + "b"; 
    // javac 在编译期间的优化,结果已经在编译期确定为ab
    //这一行代码的jvm指令为
     29: ldc           #4                  // String ab
     31: astore        5
    

    StringTable 字符串延迟加载

    在代码中设置多个断点,观察StringTable的字符数量变化,可以看出字符串是具有延迟加载机制的

    image

    在idea中debug模式中的Memory中可以查看串池中字符的个数

    image

    intern方法jdk 1.6 与 1.8区别

    • 1.8 将这个字符对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
    • 1.6 将这个字符对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,会把串池中的对象返回

    intern :

    最初为空的字符串池由StringString

    当调用intern方法时,如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。 否则,此String对象将添加到池中,并返回对此String对象的引用。

    由此可见,对于任何两个字符串sts.intern() == t.intern()true当且仅当s.equals(t)true

    将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回

    1.8

        // ["a", "b", "ab"]
        public static void main(String[] args) {
    
          
            String s = new String("a") + new String("b"); //new String("ab")
    
            // 堆  new String("a")   new String("b")  new String("ab")
            String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
           
            String x = "ab";
             System.out.println(s2 == x);
             System.out.println(s == x);
        }
    }
    
    //输出
    //true
    //true
    
    
    
        // ["ab", "a", "b"]
        public static void main(String[] args) {
    
            String x = "ab";
            String s = new String("a") + new String("b"); //new String("ab")
    
            // 堆  new String("a")   new String("b")  new String("ab")
            String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
            //由于此时,"ab"已经存在,直接返回"ab",s没能入池成功
    
    
            System.out.println( s2 == x);
            System.out.println( s == x ); //此时 s 是在堆中的对象
        }
    
    }
    //输出
    //true
    //false
    
    

    jdk1.6

        // ["ab", "a", "b"]
        public static void main(String[] args) {
    
            String x = "ab";
            String s = new String("a") + new String("b"); //new String("ab")
    
            // 堆  new String("a")   new String("b")  new String("ab")
            String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
            // s 拷贝一份,放入串池
    
    //         System.out.println(s2 == "ab");
    //         System.out.println(s == "ab");
    
    
            System.out.println( s2 == x);
            System.out.println( s == x );
        }
    
    }
    
    //输出
    //true
    //false
    
        // [ "a", "b","ab"]
        public static void main(String[] args) {
    
    
            String s = new String("a") + new String("b"); //new String("ab")
    
            // 堆  new String("a")   new String("b")  new String("ab")
            String s2 = s.intern(); // 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
            // s 拷贝一份,放入串池
    
    //         System.out.println(s2 == "ab");
    //         System.out.println(s == "ab");
    
            String x = "ab";
            System.out.println( s2 == x);
            System.out.println( s == x ); //s依然是堆里面的值,不一样
        }
    
    }
    
    //输出
    //true
    //false
    

    5.6 StringTable位置

    1. 7 的时候StringTable 是在堆空间

    1.6 时StringTable是在永久代中 ,永久代是Full GC (老年代空间不足才会触发)才会触发,导致StringTable的回收效率不高,所以在1.7 以后把StringTable 转移到堆中(只需要miner GC 就能触发垃圾回收)

    image image
    /**
     * 演示 StringTable 位置
     * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
     * 在jdk6下设置 -XX:MaxPermSize=10m
     */
    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);
            }
        }
    }
    
    

    jdk 1.8

    java.lang.OutOfMemoryError: Java heap space

    堆空间不足(StringTable 在堆中)

    jdk 1.6

    永久代空间不足(StringTable在永久代中)

    java.lang.OutOfMemoryError: PermGen space

    5.7 StringTable 垃圾回收

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

    5.8 StringTable 性能调优

    • 调整 -XX:StringTableSize=桶个数
    • 考虑将字符串对象是否入池

    ​ 如果字符常量比较多时,可以把桶的个数(StringTableSize)调大,让有更多的hash,减少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++) {
                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.intern());
                    }
                    System.out.println("cost:" +(System.nanoTime()-start)/1000000);
                }
            }
            System.in.read();    
    
    
        }
    }
    

    6.直接内存

    6.1 定义

    • 常见于NIO操作时,用于数据缓冲区

    • 分配回收成本较高,但读写性能高

    • 不受JVM内存回收管理

      传统io

      image

    ByteBuffer:

    image
    /**
     * 演示 ByteBuffer 作用
     */
    public class Demo1_9 {
        static final String FROM = "E:\\编程资料\\第三方教学视频\\youtube\\Getting Started with Spring Boot-sbPSjI4tt10.mp4";
        static final String TO = "E:\\a.mp4";
        static final int _1Mb = 1024 * 1024;
    
        public static void main(String[] args) {
            io(); // io 用时:1535.586957 1766.963399 1359.240226
            directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
        }
    
        private static void directBuffer() {
            long start = System.nanoTime();
            try (FileChannel from = new FileInputStream(FROM).getChannel();
                 FileChannel to = new FileOutputStream(TO).getChannel();
            ) {
                ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
                while (true) {
                    int len = from.read(bb);
                    if (len == -1) {
                        break;
                    }
                    bb.flip();
                    to.write(bb);
                    bb.clear();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            long end = System.nanoTime();
            System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
        }
    
        private static void io() {
            long start = System.nanoTime();
            try (FileInputStream from = new FileInputStream(FROM);
                 FileOutputStream to = new FileOutputStream(TO);
            ) {
                byte[] buf = new byte[_1Mb];
                while (true) {
                    int len = from.read(buf);
                    if (len == -1) {
                        break;
                    }
                    to.write(buf, 0, len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            long end = System.nanoTime();
            System.out.println("io 用时:" + (end - start) / 1000_000.0);
        }
    }
    
    

    6.2 分配和回收原理

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

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

    相关文章

      网友评论

        本文标题:图解jvm--(一)jvm内存结构

        本文链接:https://www.haomeiwen.com/subject/qxjpxhtx.html