圖解jvm--(一)jvm內(nèi)存結(jié)構(gòu)

jvm內(nèi)存結(jié)構(gòu)

jvm內(nèi)存結(jié)構(gòu)

1.程序計(jì)數(shù)器

1.1 定義

Program Counter Register 程序計(jì)數(shù)器(寄存器)

  • 作用,記住下一條jvm指令的執(zhí)行地址
  • 特點(diǎn)
    • 是線程私有的
    • (唯一)不會(huì)存在內(nèi)存溢出

1.2 作用

二進(jìn)制字節(jié)碼 jvm指令

 public int add();
    Code:
       0: iconst_1    // 把1壓入操作數(shù)棧中
       1: istore_1    //將int類型值存入局部變量1,這個(gè)局部變量1指局部變量表中的第一個(gè)數(shù)
       2: iconst_2        
       3: istore_2
       4: iload_1     //從局部變量1中裝載int類型值,這里的局部變量指第一個(gè)數(shù)贰军,即a
       5: iload_2
       6: iadd         //執(zhí)行相加
       7: istore_3    //存儲(chǔ)c
       8: iload_3     //裝載c
       9: ireturn     //返回
}
image

實(shí)現(xiàn):

通過寄存器實(shí)現(xiàn)测蘑,把cup的寄存器當(dāng)做程序計(jì)數(shù)器

2.虛擬機(jī)棧

image

2.1定義

java Virtual Machine Stacks (java 虛擬機(jī)棧)

  • 每個(gè)線程運(yùn)行時(shí)所需要的內(nèi)存,稱為虛擬機(jī)棧
  • 每個(gè)棧有多個(gè)棧幀(Frame)組成尺栖,對(duì)應(yīng)著每次方法調(diào)用時(shí)所占的內(nèi)存
  • 每個(gè)線程只能有一個(gè)活動(dòng)棧幀,對(duì)應(yīng)著當(dāng)前正在執(zhí)行的那個(gè)方法

問題:

  1. 垃圾回收是否涉及棧內(nèi)存烦租?方法調(diào)用完會(huì)自動(dòng)彈出延赌,回收內(nèi)存除盏,垃圾回收不涉及棧內(nèi)存
  2. 棧內(nèi)存分配越大越好?棧內(nèi)存劃得越大挫以,線程數(shù)會(huì)越少者蠕,因?yàn)閮?nèi)存有限,棧內(nèi)存是線程獨(dú)享
  3. 方法內(nèi)的局部變量是否線程安全掐松? 線程棧的線程私有的踱侣,是線程安全的
    • 如果方法內(nèi)局部變量,沒有逃離方法的作用范圍大磺,它是線程安全的
    • 如果是局部變量引用了對(duì)象抡句,并逃離方法的作用方法,需要考慮線程安全
/**
 * 演示棧幀
 */
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;
    }
}

通過上面的代碼杠愧,設(shè)置斷點(diǎn)待榔,進(jìn)行debug,可以觀察method1以及method2的方法棧出入情況

image

2.2棧內(nèi)存溢出

image
  • 棧幀過多流济,導(dǎo)致棧內(nèi)存溢出
  • 棧幀過大究抓,導(dǎo)致棧內(nèi)存溢出

2.3線程運(yùn)行診斷

案例1: cpu占用過多

定位

Linux 的 nohup java 命令 可以后臺(tái)執(zhí)行java代碼

ps 命令可以 查看進(jìn)程與線程的對(duì)應(yīng)cpu占用情況

  • 用top定位哪個(gè)進(jìn)程對(duì)cpu的占用過高
  • ps H -eo pid,tid,%cpu |grep 進(jìn)程 id (用ps命令進(jìn)一步定位是哪個(gè)線程引起的cpu占用過高)
  • jstack 進(jìn)程id (列出java線程)
    • 可以根據(jù)線程id找到有問題的線程,進(jìn)一步定位到問題代碼的源碼行號(hào)(進(jìn)程號(hào)需要轉(zhuǎn)為16進(jìn)制進(jìn)行查找)

案例2:程序運(yùn)行很長(zhǎng)時(shí)間沒有結(jié)果

死鎖

使用 jstack 看死鎖

3.本地方法棧

本地方法棧發(fā)揮的作用與虛擬機(jī)棧的作用類似袭灯,用于native修飾的方法

4.堆

4.1 定義

Heap 堆

  • 通過 new 關(guān)鍵字刺下,創(chuàng)建對(duì)象都會(huì)使用堆內(nèi)存

特點(diǎn)

  • 它是線程共享的,堆中對(duì)象都需要考慮線程安全問題
  • 有垃圾回收機(jī)制

4.2 堆內(nèi)存溢出

/**
 * 演示堆內(nèi)存溢出 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);
        }
    }
}

下圖可以調(diào)整堆內(nèi)存大小

image

4.3 堆內(nèi)存診斷

  1. jps工具

    • 查看當(dāng)前系統(tǒng)中有哪些java進(jìn)程
  2. jmap工具

    • 查看堆內(nèi)存占用情況 jmap
  3. jconsole工具

    • 圖形界面稽荧,多功能的監(jiān)測(cè)工具橘茉,可以連續(xù)監(jiān)測(cè)

    4.jvisualvm(推薦使用)

package cn.itcast.jvm.t1.heap;

/**
 * 演示堆內(nèi)存
 */
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命令查看當(dāng)前java線程

使用 jmap -heap 線程號(hào) 可以查看當(dāng)前線程的堆的使用情況

在代碼中的1,2,3步驟中分別執(zhí)行3次,可以得到3個(gè)結(jié)果

image
image

案例:

  • 垃圾回收后姨丈,內(nèi)存占用仍然很高

    /**
     * 演示查看對(duì)象個(gè)數(shù) 堆轉(zhuǎn)儲(chǔ) 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 命令進(jìn)行診斷

    使用 堆Dunp 進(jìn)行對(duì)當(dāng)前線程內(nèi)存進(jìn)行轉(zhuǎn)儲(chǔ)快照畅卓,進(jìn)而分析為什么垃圾回收后內(nèi)存還很高

    image

找出占用內(nèi)存最大的一個(gè)數(shù)組,查看

image

找到原來時(shí)數(shù)組里面對(duì)象太多蟋恬,占用了很多內(nèi)存

image

5.方法區(qū)

5.1 定義

權(quán)威定義

5.2 組成

jdk 1.6 對(duì)方法區(qū)的實(shí)現(xiàn)稱為永久代

jdk 1.8 對(duì)方法區(qū)的實(shí)現(xiàn)稱為元空間

jdk1.6

image

jdk1.8以前翁潘,方法區(qū)是在jvm內(nèi)存上面,jdk1.8以后歼争,方法區(qū)是一個(gè)邏輯分區(qū)拜马,在計(jì)算機(jī)本地內(nèi)存上面

jdk1.8

image

5.3 方法區(qū)內(nèi)存溢出

?

public class Demo1_8 extends ClassLoader { //可以用來加載類的二進(jìn)制字節(jié)碼
    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 作用是生成類的二進(jìn)制字節(jié)碼
                ClassWriter cw = new ClassWriter(0);
                //版本號(hào),pubblic沐绒,類名俩莽,包名,父類
                cw.visit(Opcodes.V1_6, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                //生成類乔遮,并且返回byte[]
                byte[] code = cw.toByteArray();
                //只會(huì)觸發(fā)類的加載扮超,不會(huì)觸發(fā)鏈接。。等
                test.defineClass("Class" + i, code, 0, code.length);//class對(duì)象
            }
        } finally {
            System.out.println(j);
        }
    }
}
  • 1.8以前(1.6)會(huì)導(dǎo)致永久代內(nèi)存溢出

    演示永久代內(nèi)存溢出  java.lang.OutOfMemoryError: PermGen space
    -XX:MaxPermSize=8m
    
    
  • 1.8以后會(huì)導(dǎo)致元空間內(nèi)存溢出

    演示元空間內(nèi)存溢出  java.lang.OutOfMemoryError: Metaspace
    -XX:MaxMetaspaceSize=8m
    

?

場(chǎng)景:框架會(huì)產(chǎn)生很多運(yùn)行時(shí)的類出刷,容易導(dǎo)致內(nèi)存溢出

  • spring

  • mybatis

    都用到cglib

5.3 運(yùn)行時(shí)常量池

  • 常量池璧疗,就是一張表,虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名馁龟,方法名崩侠,參數(shù)類型,字面量等信息
  • 運(yùn)行時(shí)常量池屁柏,常量池是*.class 文件中的啦膜,當(dāng)該類被加載有送,它的常量池信息就會(huì)放入運(yùn)行時(shí)常量池淌喻,并把里面的符號(hào)地址變?yōu)檎鎸?shí)地址
// 二進(jìn)制字節(jié)碼(類基本信息,常量池雀摘,類方法定義裸删,包含了虛擬機(jī)指令)
public class Demo1_22 {

    public static void main(String[] args) {
        String s1 = "a"; //懶惰
        String s2 = "b";
        String s3 = "ab";
    }

執(zhí)行以下命令對(duì)代碼進(jìn)行反編譯

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
    //執(zhí)行指令代碼
    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 是運(yùn)行時(shí)常量池中的一個(gè)東西

// StringTable [ "a", "b" ,"ab" ]  hashtable 結(jié)構(gòu)风喇,不能擴(kuò)容
public class Demo1_22 {
    // 常量池中的信息贴铜,都會(huì)被加載到運(yùn)行時(shí)常量池中, 這時(shí) a b ab 都是常量池中的符號(hào)惯驼,還沒有變?yōu)?java 字符串對(duì)象
    // ldc #2 會(huì)把 a 符號(hào)變?yōu)?"a" 字符串對(duì)象
    // ldc #3 會(huì)把 b 符號(hào)變?yōu)?"b" 字符串對(duì)象
    // ldc #4 會(huì)把 ab 符號(hào)變?yōu)?"ab" 字符串對(duì)象

    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 在編譯期間的優(yōu)化清蚀,結(jié)果已經(jīng)在編譯期確定為ab

        System.out.println(s3 == s5);

    }
}

//結(jié)果
s4 不等于 s3 //s3是串池中的匕荸,s4是通過new對(duì)象生成的,其值存在堆中
s3 等于 s5 //
    
    

對(duì)于 單獨(dú)的賦值枷邪,是對(duì)數(shù)據(jù)的直接到StringTable中取找榛搔,如果沒有,則創(chuàng)建东揣,有則直接取

對(duì)于 s4 = s1+s2 則是使用StringBuilder(),方法進(jìn)行拼接践惑,結(jié)果是一個(gè)新的對(duì)象,該對(duì)象存在堆上面

使用 javap -v 命令后編譯結(jié)果如下

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

常量池內(nèi)容

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特性

  • 常量池中的字符串僅是符號(hào)嘶卧,第一次用到時(shí)才變?yōu)閷?duì)象
  • hashtable 結(jié)構(gòu)尔觉,不能擴(kuò)容
  • 利用串池的機(jī)制,來避免重復(fù)創(chuàng)建字符串對(duì)象
  • 字符串變量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是編譯期優(yōu)化
  • 可以使用itern方法芥吟,主動(dòng)將串池中還沒有的字符串對(duì)象放入串池
    • 1.8 將這個(gè)字符對(duì)象嘗試放入串池侦铜,如果有則并不會(huì)放入,如果沒有則放入串池钟鸵,會(huì)把串池中的對(duì)象返回
    • 1.6 將這個(gè)字符對(duì)象嘗試放入串池泵额,如果有則并不會(huì)放入,如果沒有會(huì)把此對(duì)象復(fù)制一份携添,放入串池嫁盲,會(huì)把串池中的對(duì)象返回

先看幾道面試題:

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之前是不一樣的

//問,如果調(diào)換了【最后兩行代碼】的位置呢羞秤?如果jdk1.6呢缸托?
System.out.println(x1 == x2);//false

常量池與串池的關(guān)系:

常量池一開始是存在于字節(jié)碼中,當(dāng)運(yùn)行時(shí)瘾蛋,都會(huì)加載到運(yùn)行時(shí)常量池中俐镐,常量池中的信息都會(huì)被加載到運(yùn)行時(shí)常量池,此時(shí)常量池中的符號(hào)還不是java的字符串對(duì)象

StringTable 的字符串是延遲加載機(jī)制哺哼,即要使用才加載進(jìn)來佩抹,

常量池中的信息,都會(huì)被加載到運(yùn)行時(shí)常量池中取董, 這時(shí) a b ab 都是常量池中的符號(hào)棍苹,還沒有變?yōu)?java 字符串對(duì)象
ldc #2 會(huì)把 a 符號(hào)變?yōu)?"a" 字符串對(duì)象
ldc #3 會(huì)把 b 符號(hào)變?yōu)?"b" 字符串對(duì)象
ldc #4 會(huì)把 ab 符號(hào)變?yōu)?"ab" 字符串對(duì)象

// StringTable [ "a", "b" ,"ab" ]  hashtable 結(jié)構(gòu),不能擴(kuò)容
public class Demo1_22 {
    // 常量池中的信息茵汰,都會(huì)被加載到運(yùn)行時(shí)常量池中枢里, 這時(shí) a b ab 都是常量池中的符號(hào),還沒有變?yōu)?java 字符串對(duì)象
    // ldc #2 會(huì)把 a 符號(hào)變?yōu)?"a" 字符串對(duì)象
    // ldc #3 會(huì)把 b 符號(hào)變?yōu)?"b" 字符串對(duì)象
    // ldc #4 會(huì)把 ab 符號(hào)變?yōu)?"ab" 字符串對(duì)象

    public static void main(String[] args) {
        String s1 = "a"; //懶惰
        String s2 = "b";
        String s3 = "ab";
    }
    

    //對(duì)應(yīng)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進(jìn)行拼接栏豺,最后調(diào)用StringBuilder的toString方法進(jìn)行重新生成一個(gè)新的對(duì)象( 等于new StringBuilder().append("a").append("b").toString() new String("ab"))的底層是通過StringBuilder進(jìn)行字符串的拼接生成字符串對(duì)象,存儲(chǔ)在堆內(nèi)存中 豆胸,所以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方法奥洼,看出來是生成一個(gè)新的對(duì)象
    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }


String s5 = "a" + "b"; (javac在編譯初期的優(yōu)化,就已經(jīng)確定為ab了晚胡,所以jvm指令直接讀取ab,并生成對(duì)象)所以 s5 等于 s3

 String s5 = "a" + "b"; 
// javac 在編譯期間的優(yōu)化灵奖,結(jié)果已經(jīng)在編譯期確定為ab
//這一行代碼的jvm指令為
 29: ldc           #4                  // String ab
 31: astore        5

StringTable 字符串延遲加載

在代碼中設(shè)置多個(gè)斷點(diǎn),觀察StringTable的字符數(shù)量變化搬泥,可以看出字符串是具有延遲加載機(jī)制的

image

在idea中debug模式中的Memory中可以查看串池中字符的個(gè)數(shù)

image

intern方法jdk 1.6 與 1.8區(qū)別

  • 1.8 將這個(gè)字符對(duì)象嘗試放入串池桑寨,如果有則并不會(huì)放入,如果沒有則放入串池忿檩,會(huì)把串池中的對(duì)象返回
  • 1.6 將這個(gè)字符對(duì)象嘗試放入串池尉尾,如果有則并不會(huì)放入,如果沒有會(huì)把此對(duì)象復(fù)制一份燥透,放入串池沙咏,會(huì)把串池中的對(duì)象返回

intern :

最初為空的字符串池由StringString

當(dāng)調(diào)用intern方法時(shí)班套,如果池已經(jīng)包含與equals(Object)方法確定的相當(dāng)于此String對(duì)象的字符串肢藐,則返回來自池的字符串。 否則吱韭,此String對(duì)象將添加到池中吆豹,并返回對(duì)此String對(duì)象的引用。

由此可見,對(duì)于任何兩個(gè)字符串st 痘煤, s.intern() == t.intern()true當(dāng)且僅當(dāng)s.equals(t)true 凑阶。

將這個(gè)字符串對(duì)象嘗試放入串池,如果有則并不會(huì)放入衷快,如果沒有則放入串池宙橱, 會(huì)把串池中的對(duì)象返回

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(); // 將這個(gè)字符串對(duì)象嘗試放入串池,如果有則并不會(huì)放入蘸拔,如果沒有則放入串池师郑, 會(huì)把串池中的對(duì)象返回
       
        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(); // 將這個(gè)字符串對(duì)象嘗試放入串池,如果有則并不會(huì)放入调窍,如果沒有則放入串池宝冕, 會(huì)把串池中的對(duì)象返回
        //由于此時(shí),"ab"已經(jīng)存在陨晶,直接返回"ab",s沒能入池成功


        System.out.println( s2 == x);
        System.out.println( s == x ); //此時(shí) s 是在堆中的對(duì)象
    }

}
//輸出
//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(); // 將這個(gè)字符串對(duì)象嘗試放入串池猬仁,如果有則并不會(huì)放入帝璧,如果沒有則放入串池先誉, 會(huì)把串池中的對(duì)象返回
        // 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(); // 將這個(gè)字符串對(duì)象嘗試放入串池的烁,如果有則并不會(huì)放入褐耳,如果沒有則放入串池, 會(huì)把串池中的對(duì)象返回
        // 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 的時(shí)候StringTable 是在堆空間

1.6 時(shí)StringTable是在永久代中 ,永久代是Full GC (老年代空間不足才會(huì)觸發(fā))才會(huì)觸發(fā)襟雷,導(dǎo)致StringTable的回收效率不高刃滓,所以在1.7 以后把StringTable 轉(zhuǎn)移到堆中(只需要miner GC 就能觸發(fā)垃圾回收)

image
image
/**
 * 演示 StringTable 位置
 * 在jdk8下設(shè)置 -Xmx10m -XX:-UseGCOverheadLimit
 * 在jdk6下設(shè)置 -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());//把產(chǎn)生的數(shù)字變成字符,然后加入串池中
                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 性能調(diào)優(yōu)

  • 調(diào)整 -XX:StringTableSize=桶個(gè)數(shù)
  • 考慮將字符串對(duì)象是否入池

? 如果字符常量比較多時(shí)耸弄,可以把桶的個(gè)數(shù)(StringTableSize)調(diào)大咧虎,讓有更多的hash,減少hash沖突

/**
 * 演示 intern 減少內(nèi)存占用
 * -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.直接內(nèi)存

6.1 定義

  • 常見于NIO操作時(shí),用于數(shù)據(jù)緩沖區(qū)

  • 分配回收成本較高计呈,但讀寫性能高

  • 不受JVM內(nèi)存回收管理

    傳統(tǒng)io

    image

ByteBuffer:

image
/**
 * 演示 ByteBuffer 作用
 */
public class Demo1_9 {
    static final String FROM = "E:\\編程資料\\第三方教學(xué)視頻\\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 用時(shí):1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer 用時(shí):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 用時(shí):" + (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 用時(shí):" + (end - start) / 1000_000.0);
    }
}

6.2 分配和回收原理

  • 使用了Unsafe 對(duì)象完成直接內(nèi)存的分配回收砰诵,并且回收需要主動(dòng)調(diào)用freeMemory方法

  • ByteBuffer 的實(shí)現(xiàn)類內(nèi)部,使用了 Cleaner(虛引用) 來監(jiān)測(cè) ByteBuffer 對(duì)象捌显,一旦 ByteBuffer 對(duì)象被垃圾回收茁彭,那么就會(huì)由 ReferenceHandler 線程通過 Cleaner 的 clean 方法調(diào)用 freeMenory 來釋放直接內(nèi)存

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扶歪,隨后出現(xiàn)的幾起案子理肺,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妹萨,死亡現(xiàn)場(chǎng)離奇詭異贪薪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)眠副,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門画切,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人囱怕,你說我怎么就攤上這事霍弹。” “怎么了娃弓?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵典格,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我台丛,道長(zhǎng)耍缴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任挽霉,我火速辦了婚禮防嗡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘侠坎。我一直安慰自己蚁趁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布实胸。 她就那樣靜靜地躺著他嫡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庐完。 梳的紋絲不亂的頭發(fā)上钢属,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音门躯,去河邊找鬼淆党。 笑死,一個(gè)胖子當(dāng)著我的面吹牛生音,可吹牛的內(nèi)容都是我干的宁否。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缀遍,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼慕匠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起域醇,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤台谊,失蹤者是張志新(化名)和其女友劉穎蓉媳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锅铅,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酪呻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盐须。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片玩荠。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贼邓,靈堂內(nèi)的尸體忽然破棺而出阶冈,到底是詐尸還是另有隱情,我是刑警寧澤塑径,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布女坑,位于F島的核電站,受9級(jí)特大地震影響统舀,放射性物質(zhì)發(fā)生泄漏匆骗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一誉简、第九天 我趴在偏房一處隱蔽的房頂上張望碉就。 院中可真熱鬧,春花似錦描融、人聲如沸铝噩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至毛甲,卻和暖如春年叮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背玻募。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工只损, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人七咧。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓跃惫,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親艾栋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子爆存,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 所有知識(shí)點(diǎn)已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數(shù)? 在 Jav...
    侯蛋蛋_閱讀 2,442評(píng)論 1 4
  • 一蝗砾、運(yùn)行時(shí)數(shù)據(jù)區(qū)域 Java虛擬機(jī)管理的內(nèi)存包括幾個(gè)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存:方法區(qū)先较、虛擬機(jī)棧携冤、本地方法棧、堆闲勺、程序計(jì)數(shù)器曾棕,...
    加油小杜閱讀 1,519評(píng)論 1 15
  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,172評(píng)論 0 2
  • 這篇文章解釋了Java 虛擬機(jī)(JVM)的內(nèi)部架構(gòu)。下圖顯示了遵守Java SE 7 規(guī)范的典型的 JVM 核心內(nèi)...
    飲墨饗書閱讀 660評(píng)論 0 1
  • 其實(shí)菜循,“命”和“運(yùn)”完全是兩碼事翘地,“命”是先天的、注定的癌幕,無法改變的子眶。而“運(yùn)”卻是可以調(diào)整和改變的“運(yùn)”會(huì)因?yàn)槟愕?..
    周清荷_閱讀 186評(píng)論 0 0