zoukankan      html  css  js  c++  java
  • 图解jvm--(一)jvm内存结构

    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内存回收管理

      传统ioimage

    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 来释放直接内存

  • 相关阅读:
    Balanced Binary Tree
    Swap Nodes in Pairs
    Reverse Nodes in k-Group
    Reverse Linked List II
    Remove Nth Node From End of List
    Remove Duplicates from Sorted List II
    Remove Duplicates from Sorted List
    Partition List
    Merge Two Sorted Lists
    【Yii2.0】1.2 Apache检查配置文件语法
  • 原文地址:https://www.cnblogs.com/chenhanhao/p/12325767.html
Copyright © 2011-2022 走看看