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

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

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

1、堆

在這里插入圖片描述

1.1 定義

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

1.2 堆的作用

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

1.3 特點

  • 所有線程共享,堆內(nèi)存中的對象都需要考慮線程安全問題
  • 有垃圾回收機制,Java 堆是垃圾收集器管理的主要區(qū)域,因此很多時候也被稱做“GC 堆”(Garbage)
  • Java堆可以分成新生代和老年代 新生代可分為To Space、From Space、Eden
  • -Xmx -Xms:JVM初始分配的堆內(nèi)存由-Xms指定,默認是物理內(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虛擬機參數(shù),默認是4g
 */
public class Demo05 {
    public static void main(String[] args) {
        int i = 0;
        try {
            List<String> list = new ArrayList<>();// new 一個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啟動的時候被創(chuàng)建,并且它的實際的物理內(nèi)存空間和Java堆區(qū)一樣都可以是不連續(xù)的, 關(guān)閉Jvm就會釋放這個區(qū)域的內(nèi)存。
  • 方法區(qū)邏輯上是堆的一個組成部分,但是在不同版本的虛擬機里實現(xiàn)是不一樣的,最典型的就是永久代(PermGen space)和元空間(Metaspace)
    • (注意:方法區(qū)時一種規(guī)范,而永久代和元空間是它的一種實現(xiàn)方式)
  • 方法區(qū)的大小決定了系統(tǒng)可以保存多少個類,如果系統(tǒng)定義了太多的類,導致方法區(qū)溢出,虛擬機同樣會拋出內(nèi)存溢出錯誤:(java.lang.OutOfMemoryError:PermGen space、java.lang.OutOfMemoryError:Metaspace)。
  • 方法區(qū)用于存儲已被虛擬機加載的類型信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼緩存等。

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

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

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

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

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

方法區(qū)的演進:面試常問

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

在這里插入圖片描述

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

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

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

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

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

  • jdk7中將StringTable放到了堆空間中。因為永久代的回收效率很低,在full gc的時候才能觸發(fā)。而full gc是老年代的空間不足、永久代不足才會觸發(fā)。
  • 這就導致StringTable回收效率不高,而我們開發(fā)中會有大量的字符串被創(chuàng)建,回收效率低,導致永久代內(nèi)存不足,放到堆里,能及時回收內(nèi)存。

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

jdk7及以前:

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

jdk1.8及以后:

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

2.2 內(nèi)存溢出

1.8以前會導致永久代內(nèi)存溢出

1.8以后會導致元空間內(nèi)存溢出

案例

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

在這里插入圖片描述

/**
 * 演示元空間內(nèi)存溢出 java.lang.OutOfMemoryError: Metaspace
 * -XX:MaxMetaspaceSize=8m
 */
public class Demo1_8 extends ClassLoader { // 可以用來加載類的二進制字節(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 作用是生成類的二進制字節(jié)碼
                ClassWriter cw = new ClassWriter(0);
                // 版本號, 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 對象
            }
        } 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ù)這張常量表找到要執(zhí)行的類名,方法名,參數(shù)類型、字面量等信息

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

通過反編譯來查看類的信息

  • 獲得對應類的.class文件
  • 在JDK對應的bin目錄下運行cmd,也可以在IDEA控制臺輸入

img

  • 輸入 javac 對應類的絕對路徑

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

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

在控制臺輸入javap -v 類的絕對路徑

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

然后能在控制臺看到反編譯以后類的信息了

  • 類的基本信息

img

  • 常量池

img

img

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

img

2.4 運行時常量池

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

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

特征

  • 常量池中的字符串僅是符號,只有在被用到時才會轉(zhuǎn)化為對象
  • 利用串池的機制,來避免重復創(chuàng)建字符串對象
  • 字符串變量拼接的原理是StringBuilder
  • 字符串常量拼接的原理是編譯器優(yōu)化
  • 可以使用intern方法,主動將串池中還沒有的字符串對象放入串池中

注意無論是串池還是堆里面的字符串,都是對象

串池作用:用來放字符串對象且里面的元素不重復

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

常量池中的信息,都會被加載到運行時常量池中,但這是a b ab 僅是常量池中的符號,還沒有成為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

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

當執(zhí)行到ldc #3時,會把符號 b 變?yōu)?code>“b” 字符串對象,并放入串池中。

當執(zhí)行到ldc #4 時,會把符號 ab 變?yōu)?code>“ab”字符串對象,并放入串池中。

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

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


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

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
		// 拼接字符串對象來創(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

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

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

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

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

public class StringTableStudy {
	public static void main(String[] args) {
		String a = "a";
		String b = "b";
		String ab = "ab";
        // 拼接字符串對象來創(chuàng)建新的字符串
		String ab2 = a+b;// StringBuilder().append(“a”).append(“b”).toString()
		// 使用拼接字符串的方法創(chuàng)建字符串
		String ab3 = "a" + "b";// String ab (javac 在編譯期進行了優(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初始化時直接從串池中獲取字符串
        29: ldc           #4                  // String ab
        31: astore        5
        33: return
  • 使用拼接字符串常量的方法來創(chuàng)建新的字符串時,因為內(nèi)容是常量,javac在編譯期會進行優(yōu)化,結(jié)果已在編譯期確定為ab,而創(chuàng)建ab的時候已經(jīng)在串池中放入了“ab”,所以ab3直接從串池中獲取值,所以進行的操作和 ab = “ab”一致。
  • 使用拼接字符串變量的方法來創(chuàng)建新的字符串時,因為內(nèi)容是變量,只能在運行期確定它的值,所以需要使用StringBuffer來創(chuàng)建

JDK1.8 中的intern方法

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

  • 如果串池中沒有該字符串對象,則放入成功.
  • 如果有該字符串對象,則放入失敗.

無論放入是否成功,都會返回串池中的字符串對象.

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

例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方法,這時串池中如果沒有"ab",則會將該字符串對象放入到串池中,放入成功~此時堆內(nèi)存與串池中的"ab"是同一個對象
		String st2 = str.intern();
		// 給str3賦值,因為此時串池中已有"ab",則直接將串池中的內(nèi)容返回
		String str3 = "ab";
		// 因為堆內(nèi)存與串池中的"ab"是同一個對象,所以以下兩條語句打印的都為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)建字符串對象"ab",因為串池中還沒有"ab",所以將其放入串池中
		String str3 = "ab";
        // "a" "b" 被放入串池中,str則存在于堆內(nèi)存之中
		String str = new String("a") + new String("b");
        // 此時因為在創(chuàng)建str3時,"ab"已存在與串池中,所以放入失敗,但是會返回**串池**中的"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)用字符串對象的intern方法,會將該字符串對象嘗試放入到串池中

如果串池中沒有該字符串對象,會將該字符串對象復制一份,再放入到串池中如果有該字符串對象,則放入失敗

無論放入是否成功,都會返回串池中的字符串對象

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

2.6 StringTable的位置

在這里插入圖片描述

如圖:

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

2.7 StringTable 垃圾回收

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

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

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

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

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

3、直接內(nèi)存

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

img

使用了DirectBuffer

img

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

釋放原理

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

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

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

allocateDirect的實現(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); //申請內(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)); //通過虛引用,來實現(xiàn)直接內(nèi)存的釋放,this為虛引用的實際對象
    att = null;
}

這里調(diào)用了一個Cleaner的create方法,且后臺線程還會對虛引用的對象監(jiān)測,如果虛引用的實際對象(這里是DirectByteBuffer)被回收以后,就會調(diào)用Cleaner的clean方法,來清除直接內(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;
                   }
               });
           }

對應對象的run方法:

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

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

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

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

相關(guān)文章

  • 精辟全面且細致的java運算符教程詳解

    精辟全面且細致的java運算符教程詳解

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    深入理解Java中的Properties類

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

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

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

最新評論