欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JVM入門之內(nèi)存結(jié)構(gòu)(堆、方法區(qū))

 更新時(shí)間:2021年06月15日 16:57:11   作者:興趣使然的草帽路飛  
JVM 基本上是每家招聘公司都會(huì)問(wèn)到的問(wèn)題,它們會(huì)這么無(wú)聊問(wèn)這些不切實(shí)際的問(wèn)題嗎?很顯然不是。由 JVM 引發(fā)的故障問(wèn)題,無(wú)論在我們開發(fā)過(guò)程中還是生產(chǎn)環(huán)境下都是非常常見(jiàn)的

1、堆

在這里插入圖片描述

1.1 定義

  • 是Java內(nèi)存區(qū)域中一塊用來(lái)存放對(duì)象實(shí)例的區(qū)域幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存
  • 通過(guò)new關(guān)鍵字創(chuàng)建的對(duì)象都會(huì)被放在堆內(nèi)存,jvm 運(yùn)行時(shí)數(shù)據(jù)區(qū)中,占用內(nèi)存最大的就是堆(Heap)內(nèi)存!

1.2 堆的作用

  • 此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例
  • 方法體中的引用變量和基本類型的變量都在棧上,其他都在堆上
  • Java 堆(Java Heap)是 Java 虛擬機(jī)所管理的內(nèi)存中最大的一塊 Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域

1.3 特點(diǎn)

  • 所有線程共享,堆內(nèi)存中的對(duì)象都需要考慮線程安全問(wèn)題
  • 有垃圾回收機(jī)制,Java 堆是垃圾收集器管理的主要區(qū)域,因此很多時(shí)候也被稱做“GC 堆”(Garbage)
  • Java堆可以分成新生代和老年代 新生代可分為To Space、From Space、Eden
  • -Xmx -Xms:JVM初始分配的堆內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64

1.4 堆內(nèi)存溢出

java.lang.OutofMemoryError :java heap space. 堆內(nèi)存溢出。

內(nèi)存溢出案例:

/**
 * 演示堆內(nèi)存溢出 java.lang.OutOfMemoryError: Java heap space
 * -Xmx8m 最大堆空間的jvm虛擬機(jī)參數(shù),默認(rèn)是4g
 */
public class Demo05 {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();// new 一個(gè)list 存入堆中-------- list的有效范圍  ---------
            String a = "hello";
            while (true) {// 不斷地向list 中添加 a
                list.add(a); // hello, hellohello, hellohellohellohello ...
                a = a + a;  // hellohellohellohello
                i++;
            }//------------------------------------------------------------------ list的有效范圍  ---------
        } catch (Throwable e) {// list 使用結(jié)束,被jc 垃圾回收
            e.printStackTrace();
            System.out.println(i);
        }
    }
}

異常輸出結(jié)果:

// 給list分配堆內(nèi)存后,while(true)不斷向其中添加a 最終堆內(nèi)存溢出 
java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at com.haust.jvm_study.demo.Demo05.main(Demo05.java:19)

1.5 堆內(nèi)存診斷

用于堆內(nèi)存診斷的工具:

  • jps
  • jmap
  • jconsole
  • jvirsalvm

在這里插入圖片描述

2、方法區(qū)

在這里插入圖片描述

方法區(qū)概述:

  • 方法區(qū)在JVM啟動(dòng)的時(shí)候被創(chuàng)建,并且它的實(shí)際的物理內(nèi)存空間和Java堆區(qū)一樣都可以是不連續(xù)的, 關(guān)閉Jvm就會(huì)釋放這個(gè)區(qū)域的內(nèi)存。
  • 方法區(qū)邏輯上是堆的一個(gè)組成部分,但是在不同版本的虛擬機(jī)里實(shí)現(xiàn)是不一樣的,最典型的就是永久代(PermGen space)和元空間(Metaspace)
    • (注意:方法區(qū)時(shí)一種規(guī)范,而永久代和元空間是它的一種實(shí)現(xiàn)方式)
  • 方法區(qū)的大小決定了系統(tǒng)可以保存多少個(gè)類,如果系統(tǒng)定義了太多的類,導(dǎo)致方法區(qū)溢出,虛擬機(jī)同樣會(huì)拋出內(nèi)存溢出錯(cuò)誤:(java.lang.OutOfMemoryError:PermGen space、java.lang.OutOfMemoryError:Metaspace)。
  • 方法區(qū)用于存儲(chǔ)已被虛擬機(jī)加載的類型信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等。

  • 類型信息:( 類class、接口interface、枚舉enum、注解annotation)JVM必須在方法區(qū)中存儲(chǔ)以下類型信息:
    • 這個(gè)類型的完整有效名稱(全名=包名.類名)
    • 這個(gè)類型直接父類的完整有效名(對(duì)于interface或是java. lang.Object,都沒(méi)有父類)
    • 這個(gè)類型的修飾符(public, abstract, final的某個(gè)子集)
    • 這個(gè)類型直接接口的一個(gè)有序列表
  • 域信息(成員變量):
    • JVM必須在方法區(qū)中保存類型的所有域的相關(guān)信息以及域的聲明順序。
    • 域的相關(guān)信息包括:域名稱、 域類型、域修飾符(public, private, protected, static, final, volatile, transient的某個(gè)子集)。
  • 方法信息:JVM必須保存所有方法的以下信息,同域信息一樣包括聲明順序
    • 方法名稱
    • 方法的返回類型(或void)
    • 方法參數(shù)的數(shù)量和類型(按順序)
    • 方法的修飾符(public, private, protected, static, final,synchronized, native , abstract的一個(gè)子集)
    • 方法的字節(jié)碼(bytecodes)、操作數(shù)棧、局部變量表及大小( abstract和native 方法除外)異常表( abstract和native方法除外)
    • 每個(gè)異常處理的開始位置、結(jié)束位置、代碼處理在程序計(jì)數(shù)器中的偏移地址、被捕獲的異常類的常量池索引

2.1 結(jié)構(gòu)(1.6 對(duì)比 1.8)

由上圖可以看出,1.6版本方法區(qū)是由PermGen永久代實(shí)現(xiàn)(使用堆內(nèi)存的一部分作為方法區(qū)),且由JVM 管理,由Class ClassLoader 常量池(包括StringTable) 組成。

1.8 版本后,方法區(qū)交給本地內(nèi)存管理,而脫離了JVM,由元空間實(shí)現(xiàn)(元空間不再使用堆的內(nèi)存,而是使用本地內(nèi)存,即操作系統(tǒng)的內(nèi)存),由Class ClassLoader 常量池(StringTable 被移到了Heap 堆中管理) 組成。

  • 方法區(qū)是什么?
    • 是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類信息(比如class文件)、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
    • 什么是類信息:類版本號(hào)、方法、接口。
  • 方法區(qū)作用
    • 內(nèi)存中存放類信息、靜態(tài)變量、常量等數(shù)據(jù),屬于線程共享的一塊區(qū)域。
    • Hotspot使用永久代來(lái)實(shí)現(xiàn)方法區(qū) JRockit、IBM J9VM Java堆一樣管理這部分內(nèi)存。
  • 方法區(qū)特點(diǎn)
    • 并非數(shù)據(jù)進(jìn)入了方法區(qū)就如永久代的名字一樣“永久”存在了。這區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載。
    • 方法區(qū)也會(huì)拋出OutofMemoryError,當(dāng)它無(wú)法滿足內(nèi)存分配需求時(shí) 。

方法區(qū)的演進(jìn):面試常問(wèn)

  • Jdk 1.6 及之前:有永久代(靜態(tài)變量存放在永久代上)、字符串常量池(1.6在方法區(qū))
  • Jdk 1.7 :有永久代,但已經(jīng)逐步 " 去永久代 ",字符串常量池、靜態(tài)變量移除,保存在堆中
  • dk 1.8 及之后: 無(wú)永久代,常量池1.8在元空間。但靜態(tài)變量、字符串常量池仍在堆中

在這里插入圖片描述

為什么要用元空間取代永久代?

永久代設(shè)置空間大小很難確定:(

①. 永久代參數(shù)設(shè)置過(guò)小,在某些場(chǎng)景下,如果動(dòng)態(tài)加載的類過(guò)多,容易產(chǎn)生Perm區(qū)的OOM,比如某個(gè)實(shí)際Web工程中,因?yàn)楣δ茳c(diǎn)比較多,在運(yùn)行過(guò)程中,要不斷動(dòng)態(tài)加載很多類,經(jīng)常出現(xiàn)致命錯(cuò)誤
②. 永久代參數(shù)設(shè)置過(guò)大,導(dǎo)致空間浪費(fèi)
③. 默認(rèn)情況下,元空間的大小受本地內(nèi)存限制)

永久代進(jìn)行調(diào)優(yōu)很困難:(方法區(qū)的垃圾收集主要回收兩部分:常量池中廢棄的常量和不再使用的類型,而不再使用的類或類的加載器回收比較復(fù)雜,full gc 的時(shí)間長(zhǎng))

StringTable為什么要調(diào)整?

  • jdk7中將StringTable放到了堆空間中。因?yàn)橛谰么幕厥招屎艿?在full gc的時(shí)候才能觸發(fā)。而full gc是老年代的空間不足、永久代不足才會(huì)觸發(fā)。
  • 這就導(dǎo)致StringTable回收效率不高,而我們開發(fā)中會(huì)有大量的字符串被創(chuàng)建,回收效率低,導(dǎo)致永久代內(nèi)存不足,放到堆里,能及時(shí)回收內(nèi)存。

設(shè)置方法區(qū)大小

jdk7及以前:

  • -XX:PermSize=100m(默認(rèn)值是20.75M)
  • -XX:MaxPermSize=100m(32位機(jī)器默認(rèn)是64M,64位機(jī)器模式是82M)

jdk1.8及以后:

  • -XX:MetaspaceSize=100m(windows下,默認(rèn)約等于21M)
  • -XX:MaxMetaspaceSize=100m(默認(rèn)是-1,即沒(méi)有限制)

2.2 內(nèi)存溢出

1.8以前會(huì)導(dǎo)致永久代內(nèi)存溢出

1.8以后會(huì)導(dǎo)致元空間內(nèi)存溢出

案例

調(diào)整虛擬機(jī)參數(shù):-XX:MaxMetaspaceSize=8m

在這里插入圖片描述

/**
 * 演示元空間內(nèi)存溢出 java.lang.OutOfMemoryError: Metaspace
 * -XX:MaxMetaspaceSize=8m
 */
public class Demo1_8 extends ClassLoader { // 可以用來(lái)加載類的二進(jìn)制字節(jié)碼
    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 作用是生成類的二進(jìn)制字節(jié)碼
                ClassWriter cw = new ClassWriter(0);
                // 版本號(hào), public, 類名, 包名, 父類, 接口
                cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                // 返回 byte[]
                byte[] code = cw.toByteArray();
                // 執(zhí)行了類的加載
                test.defineClass("Class" + i, code, 0, code.length); // Class 對(duì)象
            }
        } finally {
            System.out.println(j);
        }
    }
}
3331
Exception in thread "main" java.lang.OutOfMemoryError: Compressed class space // 元空間內(nèi)存溢出
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.haust.jvm_study.metaspace.Demo1_8.main(Demo1_8.java:23)

2.3 常量池

常量池,可以看做是一張表,虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名,方法名,參數(shù)類型、字面量等信息

類的二進(jìn)制字節(jié)碼的組成:類的基本信息、常量池、類的方法定義(包含了虛擬機(jī)指令)。

通過(guò)反編譯來(lái)查看類的信息

  • 獲得對(duì)應(yīng)類的.class文件
  • 在JDK對(duì)應(yīng)的bin目錄下運(yùn)行cmd,也可以在IDEA控制臺(tái)輸入

img

  • 輸入 javac 對(duì)應(yīng)類的絕對(duì)路徑

F:\JAVA\JDK8.0\bin>javac F:\Thread_study\src\com\nyima\JVM\day01\Main.javaCopy

輸入完成后,對(duì)應(yīng)的目錄下就會(huì)出現(xiàn)類的.class文件

在控制臺(tái)輸入javap -v 類的絕對(duì)路徑

javap -v F:\Thread_study\src\com\nyima\JVM\day01\Main.classCopy

然后能在控制臺(tái)看到反編譯以后類的信息了

  • 類的基本信息

img

  • 常量池

img

img

  • 虛擬機(jī)中執(zhí)行編譯的方法(框內(nèi)的是真正編譯執(zhí)行的內(nèi)容,#號(hào)的內(nèi)容需要在常量池中查找)

img

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

  • 常量池
    • 就是一張表(如上圖中的constant pool),虛擬機(jī)指令根據(jù)這張常量表找到要執(zhí)行的類名、方法名、參數(shù)類型、字面量信息
  • 運(yùn)行時(shí)常量池
    • 常量池是*.class文件中的,當(dāng)該類被加載以后,它的常量池信息就會(huì)放入運(yùn)行時(shí)常量池,并把里面的符號(hào)地址變?yōu)檎鎸?shí)內(nèi)存地址
    • 行時(shí)常量池( Runtime Constant Pool)是方法區(qū)的一部分。
    • 常量池表(Constant Pool Table)是Class文件的一部分,用于存放編譯期生成的各種字面量與符號(hào)引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中。
    • 運(yùn)行時(shí)常量池中包含多種不同的常量,包括編譯期就已經(jīng)明確的數(shù)值字面量,也包括到運(yùn)行期解析后才能夠獲得的方法或者字段引用。此時(shí)不再是常量池中的符號(hào)地址了,這里換為真實(shí)地址。
    • **(方法區(qū)內(nèi)常量池之中主要存放的兩大類常量:字面量和符號(hào)引用。**字面量比較接近Java語(yǔ)言層次的常量概念,如文本字符串、被聲明為final的常量值等。而符號(hào)引用則屬于編譯原理方面的概念,包括下面三類常量:
      • 類和接口的全限定名
      • 字段的名稱和描述符
      • 方法的名稱和描述符

java 8 后,永久代已經(jīng)被移除,被稱為“元數(shù)據(jù)區(qū)”的區(qū)域所取代。類的元數(shù)據(jù)放入native memory, 字符串池和類的靜態(tài)變量放入java堆中(靜態(tài)變量之前是放在方法區(qū))。

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

串池StringTable

  • 常量池是.class文件,存放堆中數(shù)據(jù)的引用地址,而不是真實(shí)的對(duì)象,運(yùn)行時(shí)常量池是jvm運(yùn)行時(shí)將常量池中數(shù)據(jù)放入池中,此時(shí)引用地址真正的指向?qū)ο蠖皇?class文件;Stringtable是哈希表(不能擴(kuò)容),它也叫做串池,用來(lái)存儲(chǔ)字符串,這3個(gè)不是同一個(gè)東西,我們需要進(jìn)行區(qū)分。
  • StringTable中存儲(chǔ)的并不是String類型的對(duì)象,存儲(chǔ)的而是指向String對(duì)象的索引,真實(shí)對(duì)象還是存儲(chǔ)在堆中
  • jdk1.6中,StringTable是放在永久代(方法區(qū))中,jvm進(jìn)行FullGC才會(huì)對(duì)常量池進(jìn)行垃圾回收,影響效率,因此在jdk1.8中將StringTable放在堆中,jvm內(nèi)存緊張時(shí)就會(huì)對(duì)StringTable進(jìn)行垃圾回收。

特征

  • 常量池中的字符串僅是符號(hào),只有在被用到時(shí)才會(huì)轉(zhuǎn)化為對(duì)象
  • 利用串池的機(jī)制,來(lái)避免重復(fù)創(chuàng)建字符串對(duì)象
  • 字符串變量拼接的原理是StringBuilder
  • 字符串常量拼接的原理是編譯器優(yōu)化
  • 可以使用intern方法,主動(dòng)將串池中還沒(méi)有的字符串對(duì)象放入串池中

注意無(wú)論是串池還是堆里面的字符串,都是對(duì)象

串池作用:用來(lái)放字符串對(duì)象且里面的元素不重復(fù)

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a"; 
		String b = "b";
		String ab = "ab";
	}
}

常量池中的信息,都會(huì)被加載到運(yùn)行時(shí)常量池中,但這是a b ab 僅是常量池中的符號(hào),還沒(méi)有成為java字符串

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: returnCopy

當(dāng)執(zhí)行到 ldc #2 時(shí),會(huì)把符號(hào) a 變?yōu)?code>“a”字符串對(duì)象,并放入串池中(hashtable結(jié)構(gòu) 不可擴(kuò)容)

當(dāng)執(zhí)行到ldc #3時(shí),會(huì)把符號(hào) b 變?yōu)?code>“b” 字符串對(duì)象,并放入串池中。

當(dāng)執(zhí)行到ldc #4 時(shí),會(huì)把符號(hào) ab 變?yōu)?code>“ab”字符串對(duì)象,并放入串池中。

最終串池中存放:StringTable [“a”, “b”, “ab”

注意:字符串對(duì)象的創(chuàng)建都是懶惰的,只有當(dāng)運(yùn)行到那一行字符串且在串池中不存在的時(shí)候(如 ldc #2)時(shí),該字符串才會(huì)被創(chuàng)建并放入串池中。


案例1:使用拼接字符串變量對(duì)象創(chuàng)建字符串的過(guò)程:

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		// 拼接字符串對(duì)象來(lái)創(chuàng)建新的字符串
		String ab2 = a+b; // StringBuilder().append(“a”).append(“b”).toString()
	}
}

反編譯后的結(jié)果:

	 Code:
      stack=2, locals=5, 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/Str
ing;
        27: astore        4
        29: return

通過(guò)拼接的方式來(lái)創(chuàng)建字符串的過(guò)程是:StringBuilder().append(“a”).append(“b”).toString()

最后的toString()方法的返回值是一個(gè)新的字符串對(duì)象,但字符串的值和拼接的字符串一致,但是兩個(gè)不同的字符串,一個(gè)存在于串池之中,一個(gè)存在于堆內(nèi)存之中

String ab = "ab";// 串池之中
String ab2 = a+b;// 堆內(nèi)存之中
// 結(jié)果為false,因?yàn)閍b是存在于串池之中,ab2是由StringBuffer的toString方法所返回的一個(gè)對(duì)象,存在于堆內(nèi)存之中
System.out.println(ab == ab2);

案例2:使用拼接字符串常量對(duì)象的方法創(chuàng)建字符串

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
        // 拼接字符串對(duì)象來(lái)創(chuàng)建新的字符串
		String ab2 = a+b;// StringBuilder().append(“a”).append(“b”).toString()
		// 使用拼接字符串的方法創(chuàng)建字符串
		String ab3 = "a" + "b";// String ab (javac 在編譯期進(jìn)行了優(yōu)化)
	}
}

反編譯后的結(jié)果:

 	  Code:
      stack=2, 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/Str
ing;
        27: astore        4
        // ab3初始化時(shí)直接從串池中獲取字符串
        29: ldc           #4                  // String ab
        31: astore        5
        33: return
  • 使用拼接字符串常量的方法來(lái)創(chuàng)建新的字符串時(shí),因?yàn)?strong>內(nèi)容是常量,javac在編譯期會(huì)進(jìn)行優(yōu)化,結(jié)果已在編譯期確定為ab,而創(chuàng)建ab的時(shí)候已經(jīng)在串池中放入了“ab”,所以ab3直接從串池中獲取值,所以進(jìn)行的操作和 ab = “ab”一致。
  • 使用拼接字符串變量的方法來(lái)創(chuàng)建新的字符串時(shí),因?yàn)閮?nèi)容是變量,只能在運(yùn)行期確定它的值,所以需要使用StringBuffer來(lái)創(chuàng)建

JDK1.8 中的intern方法

調(diào)用字符串對(duì)象的intern方法,會(huì)將該字符串對(duì)象嘗試放入到串池中

  • 如果串池中沒(méi)有該字符串對(duì)象,則放入成功.
  • 如果有該字符串對(duì)象,則放入失敗.

無(wú)論放入是否成功,都會(huì)返回串池中的字符串對(duì)象.

注意:此時(shí)如果調(diào)用intern方法成功,堆內(nèi)存與串池中的字符串對(duì)象是同一個(gè)對(duì)象;如果失敗,則不是同一個(gè)對(duì)象!

例1

public class Main {
	public static void main(String[] args) {
		// "a" "b" 被放入串池中,str則存在于堆內(nèi)存之中
		String str = new String("a") + new String("b");
		// 調(diào)用str的intern方法,這時(shí)串池中如果沒(méi)有"ab",則會(huì)將該字符串對(duì)象放入到串池中,放入成功~此時(shí)堆內(nèi)存與串池中的"ab"是同一個(gè)對(duì)象
		String st2 = str.intern();
		// 給str3賦值,因?yàn)榇藭r(shí)串池中已有"ab",則直接將串池中的內(nèi)容返回
		String str3 = "ab";
		// 因?yàn)槎褍?nèi)存與串池中的"ab"是同一個(gè)對(duì)象,所以以下兩條語(yǔ)句打印的都為true
		System.out.println(str == st2);// true
		System.out.println(str == str3);// true
	}
}

例2

public class Main {
	public static void main(String[] args) {
        // 此處創(chuàng)建字符串對(duì)象"ab",因?yàn)榇刂羞€沒(méi)有"ab",所以將其放入串池中
		String str3 = "ab";
        // "a" "b" 被放入串池中,str則存在于堆內(nèi)存之中
		String str = new String("a") + new String("b");
        // 此時(shí)因?yàn)樵趧?chuàng)建str3時(shí),"ab"已存在與串池中,所以放入失敗,但是會(huì)返回**串池**中的"ab"
		String str2 = str.intern();
		System.out.println(str == str2);// false
		System.out.println(str == str3);// false
		System.out.println(str2 == str3);// true
	}
}

JDK1.6 中的intern方法

調(diào)用字符串對(duì)象的intern方法,會(huì)將該字符串對(duì)象嘗試放入到串池中

如果串池中沒(méi)有該字符串對(duì)象,會(huì)將該字符串對(duì)象復(fù)制一份,再放入到串池中如果有該字符串對(duì)象,則放入失敗

無(wú)論放入是否成功,都會(huì)返回串池中的字符串對(duì)象

注意:此時(shí)無(wú)論調(diào)用intern方法成功與否,串池中的字符串對(duì)象和堆內(nèi)存中的字符串對(duì)象都不是同一個(gè)對(duì)象

2.6 StringTable的位置

在這里插入圖片描述

如圖:

  • JDK1.6 時(shí),StringTable是屬于常量池的一部分。
  • JDK1.8 以后,StringTable是放在堆中的。

2.7 StringTable 垃圾回收

StringTable在內(nèi)存緊張時(shí),會(huì)發(fā)生垃圾回收。

2.8 方法區(qū)的垃圾回收

(1).有些人認(rèn)為方法區(qū)(如Hotspot,虛擬機(jī)中的元空間或者永久代)是沒(méi)有垃圾收集行為的,其實(shí)不然?!禞ava 虛擬機(jī)規(guī)范》對(duì)方法區(qū)的約束是非常寬松的,提到過(guò)可以不要求虛擬機(jī)在方法區(qū)中實(shí)現(xiàn)垃圾收集。事實(shí)上也確實(shí)有未實(shí)現(xiàn)或未能完整實(shí)現(xiàn)方法區(qū)類型卸載的收集器存在(如 JDK11 時(shí)期的 ZGC 收集器就不支持類卸載)

(2). 一般來(lái)說(shuō)這個(gè)區(qū)域的回收效果比較難令人滿意,尤其是類型的卸載,條件相當(dāng)苛刻。但是這部分區(qū)域的回收有時(shí)又確實(shí)是必要的。以前 Sun 公司的 Bug 列表中,曾出現(xiàn)過(guò)的若干個(gè)嚴(yán)重的 Bug 就是由于低版本的 Hotspot 虛擬機(jī)對(duì)此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏。

  • 法區(qū)的垃圾收集主要回收兩部分內(nèi)容常量池中廢奔的常量和不再使用的類型
  • 先來(lái)說(shuō)說(shuō)方法區(qū)內(nèi)常量池之中主要存放的兩大類常量:字面量和符號(hào)引用。 字面量比較接近Java語(yǔ)言層次的常量概念,如文本字符串、被聲明為final的常量值等。而符號(hào)引用則屬于編譯原理方面的概念,包括下面三類常量:
    • 類和接口的全限定名
    • 字段的名稱和描述符
    • 方法的名稱和描述符
  • HotSpot虛擬機(jī)對(duì)常量池的回收策略是很明確的,只要常量池中的常量沒(méi)有被任何地方引用,就可以被回收?;厥諒U棄常量與回收J(rèn)ava堆中的對(duì)象非常類似。
  • 判定一個(gè)常量是否“廢棄”還是相對(duì)簡(jiǎn)單,而要判定一個(gè)類型是否屬于“不再被使用的類”的條件就比較苛刻了。需要同時(shí)滿足下面三個(gè)條件:
    • 該類所有的實(shí)例都已經(jīng)被回收,也就是Java堆中不存在該類及其任何派生子類的實(shí)例。
    • 加載該類的類加載器已經(jīng)被回收,這個(gè)條件除非是經(jīng)過(guò)精心設(shè)計(jì)的可替換類加載器的場(chǎng)景,如OSGi、JSP的重加載等,否則通常是很難達(dá)成的
    • 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法
    • Java虛擬機(jī)被允許對(duì)滿足上述三個(gè)條件的無(wú)用類進(jìn)行回收,這里說(shuō)的僅僅是“被允許”,而并不是和對(duì)象一樣,沒(méi)有引用了就必然會(huì)回收。關(guān)于是否要對(duì)類型進(jìn)行回收,HotSpot虛擬機(jī)提供了一Xnoclassgc 參數(shù)進(jìn)行控制,還可以使用一verbose:class以及一XX: +TraceClass一Loading、一XX:+TraceClassUnLoading查看類加載和卸載信息。
    • 在大量使用反射、動(dòng)態(tài)代理、CGLib等字節(jié)碼框架,動(dòng)態(tài)生成JSP以及oSGi這類頻繁自定義類加載器的場(chǎng)景中,通常都需要Java虛擬機(jī)具備類型卸載的能力,以保證不會(huì)對(duì)方法區(qū)造成過(guò)大的內(nèi)存壓力。

3、直接內(nèi)存

  • 屬于操作系統(tǒng),常見(jiàn)于NIO操作時(shí),用于數(shù)據(jù)緩沖區(qū)
  • 分配回收成本較高,但讀寫性能高
  • 不受JVM內(nèi)存回收管理 文件讀寫流程

img

使用了DirectBuffer

img

直接內(nèi)存是操作系統(tǒng)和Java代碼都可以訪問(wèn)的一塊區(qū)域,無(wú)需將代碼從系統(tǒng)內(nèi)存復(fù)制到Java堆內(nèi)存,從而提高了效率。

釋放原理

直接內(nèi)存的回收不是通過(guò)JVM的垃圾回收來(lái)釋放的,而是通過(guò)unsafe.freeMemory來(lái)手動(dòng)釋放。

//通過(guò)ByteBuffer申請(qǐng)1M的直接內(nèi)存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);

申請(qǐng)直接內(nèi)存,但JVM并不能回收直接內(nèi)存中的內(nèi)容,它是如何實(shí)現(xiàn)回收的呢?

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

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}Copy

DirectByteBuffer類:

DirectByteBuffer(int cap) {   // package-private
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);
    long base = 0;
    try {
        base = unsafe.allocateMemory(size); //申請(qǐng)內(nèi)存
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //通過(guò)虛引用,來(lái)實(shí)現(xiàn)直接內(nèi)存的釋放,this為虛引用的實(shí)際對(duì)象
    att = null;
}

這里調(diào)用了一個(gè)Cleaner的create方法,且后臺(tái)線程還會(huì)對(duì)虛引用的對(duì)象監(jiān)測(cè),如果虛引用的實(shí)際對(duì)象(這里是DirectByteBuffer)被回收以后,就會(huì)調(diào)用Cleaner的clean方法,來(lái)清除直接內(nèi)存中占用的內(nèi)存。

public void clean() {
       if (remove(this)) {
           try {
               this.thunk.run(); //調(diào)用run方法
           } catch (final Throwable var2) {
               AccessController.doPrivileged(new PrivilegedAction<Void>() {
                   public Void run() {
                       if (System.err != null) {
                           (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                       }
                       System.exit(1);
                       return null;
                   }
               });
           }

對(duì)應(yīng)對(duì)象的run方法:

public void run() {
    if (address == 0) {
        // Paranoia
        return;
    }
    unsafe.freeMemory(address); //釋放直接內(nèi)存中占用的內(nèi)存
    address = 0;
    Bits.unreserveMemory(size, capacity);
}

直接內(nèi)存的回收機(jī)制總結(jié)

  • 使用了Unsafe類來(lái)完成直接內(nèi)存的分配回收,回收需要主動(dòng)調(diào)用freeMemory方法。
  • ByteBuffer的實(shí)現(xiàn)內(nèi)部使用了Cleaner(虛引用)來(lái)檢測(cè)ByteBuffer。一旦ByteBuffer被垃圾回收,那么會(huì)由ReferenceHandler來(lái)調(diào)用Cleaner的clean方法調(diào)用freeMemory來(lái)釋放內(nèi)存。ByteBuffer被垃圾回收,那么會(huì)由ReferenceHandler來(lái)調(diào)用Cleaner的clean方法調(diào)用freeMemory來(lái)釋放內(nèi)存。

希望大家可以多多關(guān)注腳本之家的其他文章!

相關(guān)文章

  • 精辟全面且細(xì)致的java運(yùn)算符教程詳解

    精辟全面且細(xì)致的java運(yùn)算符教程詳解

    這篇文章主要介紹了java運(yùn)算符教程,內(nèi)容非常的精辟全面且細(xì)致到每一個(gè)小注意點(diǎn),正在學(xué)java的同學(xué)快快建議收藏閱讀吧,希望能夠有所幫助,祝多多進(jìn)步早日升職加薪
    2021-10-10
  • 淺析java中Pair和Map的區(qū)別

    淺析java中Pair和Map的區(qū)別

    這篇文章主要介紹了java中Pair和Map的區(qū)別,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • 使用Java獲取html中Select,radio多選的值方法

    使用Java獲取html中Select,radio多選的值方法

    以下是對(duì)使用Java獲取html中Select,radio多選值的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下
    2013-08-08
  • 判斷List和Map是否相等并合并List中相同的Map

    判斷List和Map是否相等并合并List中相同的Map

    今天小編就為大家分享一篇關(guān)于判斷List和Map是否相等并合并List中相同的Map,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • JDBC實(shí)現(xiàn)數(shù)據(jù)庫(kù)增刪改查功能

    JDBC實(shí)現(xiàn)數(shù)據(jù)庫(kù)增刪改查功能

    這篇文章主要為大家詳細(xì)介紹了JDBC實(shí)現(xiàn)數(shù)據(jù)庫(kù)增刪改查功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • 解決eclipse上傳svn忽略target文件夾的坑

    解決eclipse上傳svn忽略target文件夾的坑

    這篇文章主要介紹了解決eclipse上傳svn忽略target文件夾的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-01-01
  • Java零基礎(chǔ)也看得懂的單例模式與final及抽象類和接口詳解

    Java零基礎(chǔ)也看得懂的單例模式與final及抽象類和接口詳解

    本文主要講了單例模式中的餓漢式和懶漢式的區(qū)別,final的使用,抽象類的介紹以及接口的具體內(nèi)容,感興趣的朋友來(lái)看看吧
    2022-05-05
  • springboot連接不同數(shù)據(jù)庫(kù)的寫法詳解

    springboot連接不同數(shù)據(jù)庫(kù)的寫法詳解

    這篇文章主要介紹了springboot連接不同數(shù)據(jù)庫(kù)的寫法?,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • 深入理解Java中的Properties類

    深入理解Java中的Properties類

    Properties類是Java中用于處理配置文件的工具類,它繼承自 Hashtable類,實(shí)現(xiàn)了Map接口,本文主要介紹了Java中的Properties類,感興趣的可以了解一下
    2024-01-01
  • Springboot?中的?Filter?實(shí)現(xiàn)超大響應(yīng)?JSON?數(shù)據(jù)壓縮的方法

    Springboot?中的?Filter?實(shí)現(xiàn)超大響應(yīng)?JSON?數(shù)據(jù)壓縮的方法

    這篇文章主要介紹了Springboot?中的?Filter?實(shí)現(xiàn)超大響應(yīng)?JSON?數(shù)據(jù)壓縮,定義GzipFilter對(duì)輸出進(jìn)行攔截,定義 Controller該 Controller 非常簡(jiǎn)單,主要讀取一個(gè)大文本文件,作為輸出的內(nèi)容,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10

最新評(píng)論