虛擬機的內存結構

一、摘要

熟悉 Java 語言特性的同學都知道,相比 C、C++ 等編程語言,Java 無需通過手動方式回收內存,內存中所有的對象都可以交給 Java 虛擬機來幫助自動回收;而像 C、C++ 等編程語言,需要開發者通過代碼手動釋放內存資源,否則會導致內存溢出。

盡管如此,如果編程不當,Java 應用程序也可能會出現內存溢出的現象,例如下面這個異常!

Exception?in?thread?"main"?java.lang.OutOfMemoryError:?Java?heap?spaceat?java.util.Arrays.copyOf(Arrays.java:2760)at?java.util.Arrays.copyOf(Arrays.java:2734)at?java.util.ArrayList.ensureCapacity(ArrayList.java:167)at?java.util.ArrayList.add(ArrayList.java:351)

它表示當前服務已出現內存溢出,簡單的說就是當服務出現了內存不足時,就會拋OutOfMemoryError異常。

這種異常是怎么出現的呢?該如何解決呢?

熟悉 JVM 內存結構的同學,可能會很快看得出以上錯誤信息表示虛擬機堆內存空間不足,因此了解 JVM 內存結構對快速定位問題并解決問題有著非常重要的意義。今天我們一起來了解一下 JVM 內存結構。

本文以 JDK1.7 版本為例,不同的版本 JVM 內存布局可能稍有不同,但是所涉及的知識點基本大同小異。

二、內存結構介紹

Java 虛擬機在執行程序的過程中,會把所管理的內存劃分成若干不同的數據區域。這些區域各有各有的用途,有的區域會隨著虛擬機進程的啟動而一直存在;有的區域會伴隨著用戶線程的啟用和結束而創建和銷毀。

其次,JVM 內存區域也稱為運行時數據區域,這些數據區域包括:程序計數器、虛擬機棧、本地方法棧、堆、方法區等,可以用如下圖來簡要概括。

其中,運行時數據區的程序計數器、虛擬機棧、本地方法棧屬于每個線程私有的區域;堆和方法區屬于所有線程間共享的區域

運行時數據區的線程間內存區域布局,可以用如下圖來簡要描述:

2.1、程序計數器

程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。

在虛擬機的概念模型里,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,比如分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。

我們知道 Java 是支持多線程的,其中虛擬機的多線程就是通過輪流切換線程并分配處理器執行時間的方式來實現的。在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)只會執行一條線程中的指令,為了線程切換后能恢復到正確的執行位置,虛擬機為每個線程都設計了一個獨立的程序計數器,各條線程之間的程序計數器互不影響,獨立存儲,屬于線程私有的內存區域,生命周期與線程相同

在 JVM 規范中,如果線程執行的是非native方法,則程序計數器中保存的是當前需要執行的指令的地址;如果線程執行的是native方法,則程序計數器中的值是Undefined,也就是空。

由于程序計數器中存儲的數據所占空間的大小不會隨程序的執行而發生改變,因此,此內存區域是唯一一個在 JVM 規范中沒有規定任何OutOfMemoryError情況的區域

2.2、虛擬機棧

虛擬機棧(Java Virtual Machine Stacks)與程序計數器一樣,也是線程私有的內存區域,它的生命周期與線程相同

虛擬機棧描述的是 Java 方法執行時的內存模型,每個方法執行的時候都會創建一個棧幀(Stack Frame), 用于存儲局部變量表、操作數棧、動態鏈接、方法出口和一些額外的附加信息。每一個方法從被調用直到執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的全過程。

虛擬機棧內部結構,可以用如下圖來簡要描述。

2.2.1、局部變量表

局部變量表是一組變量值的存儲空間,用于存儲方法參數和局部變量,例如:

  • 基本數據類型:比如 boolean、byte、char、short、int、float、long、double 等 8 種基本數據類型

  • 對象引用類型:指向對象起始地址的引用指針

  • 返回地址類型:指向一條字節碼指令的返回地址

通常,局部變量表的內存空間在編譯器就會確定其大小,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是可以完全確定的,因此在程序執行期間局部變量表的大小是不會改變的。

其次,局部變量表的最小單位為 32 位的字長,對于 64 位的 long 和 double 變量而言,虛擬機會為其分配兩個連續的局部變量空間。

2.2.2、操作數棧

操作數棧也常稱為操作棧,是一個后入先出的棧。虛擬機會利用操作棧的壓棧和出棧操作來執行指令運算。

比如下面的兩個數據相加的計算示例。

begin
iload_0????//?push?the?int?in?local?variable?0?onto?the?stack
iload_1????//?push?the?int?in?local?variable?1?onto?the?stack
iadd???????//?pop?two?ints,?add?them,?push?result
istore_2???//?pop?int,?store?into?local?variable?2
end

在這個字節碼序列里,前兩個指令iload_0iload_1將存儲在局部變量表中索引為01的整數壓入操作數棧中;接著iadd指令從操作數棧中彈出那兩個整數相加,再將結果壓入操作數棧;最后istore_2指令從操作數棧中彈出結果,并把它存儲到局部變量表索引為2的位置,完成數據的計算。

2.2.3、動態鏈接

每個棧幀都包含一個對當前方法類型的運行時常量池的引用,以支持方法調用過程中的動態鏈接。可以簡單的理解成,當前棧幀與運行時常量池的方法引用建立鏈接。

比如方法 a 入棧后,棧幀中的動態鏈接會持有對當前方法所屬類的常量池的引用,當方法 a 中調用了方法 b(符號引用),就可以通過運行時常量池查找到方法 b 具體的直接引用(方法地址),然后調用執行。

2.2.4、方法出口

當一個方法執行完畢之后,要返回之前調用它的地方,因此在棧幀中必須保存一個方法返回地址,也稱為方法出口。

在虛擬機棧中,只有兩種方式可以退出當前方法:

  • 正常返回:當執行遇到返回指令,會將返回值傳遞給上層的方法調用者,這種退出方式稱為正常返回,一般來說,調用者的程序計數器可以作為方法返回地址

  • 異常返回:當執行遇到異常,并且當前方法體內沒有得到處理,就會導致方法退出,此時是沒有返回值的,這種退出方式稱為異常返回,返回地址要通過異常處理器表來確定

當一個方法返回時,可能依次進行以下 3 個操作:

  • 1.恢復上層方法的局部變量表和操作數棧

  • 2.把返回值壓入調用者棧幀的操作數棧

  • 3.將程序計數器的值指向下一條方法指令位置

2.2.5、小結

在 JVM 規范中,對這個內存區域規定了兩種異常狀況:

  • 如果當前線程請求的棧深度大于虛擬機棧所允許的深度,將拋出StackOverFlowError異常(當前虛擬機棧不允許動態擴展的情況下)

  • 如果虛擬機棧可以動態擴展,當擴展到無法申請內存到足夠的內存,就會拋出OutOfMemoryError異常

2.3、本地方法棧

本地方法棧(Native Method Stacks)與虛擬機棧發揮的作用非常相似,主要區別在于:虛擬機棧為虛擬機執行 Java 方法(也就是字節碼)服務;本地方法棧則是為虛擬機使用到的Native方法服務(通常采用 C 編寫)

有些虛擬機發行版本,比如Sun HotSpot虛擬機,直接將本地方法棧和 Java 虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧也會拋出StackOverflowErrorOutOfMemoryError異常。

2.4、堆

Java 堆是被所有線程共享的最大的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例和數組都在這里分配內存,也是出現OutOfMemoryError異常最常見的區域。

在虛擬機中,堆被劃分成兩個不同的區域:年輕代 (Young Generation) 和老年代 (Old Generation),默認情況下按照1 : 2的比例來分配空間

其中年輕代又被劃分為三個不同的區域:Eden 區、From Survivor 區、To Survivor 區,默認情況下按照8 : 1 : 1的比例來分配空間

整個堆內存的空間劃分,可以用如下圖來簡要描述。

這樣劃分的目的是為了使 JVM 能夠更好的管理堆內存中的對象,包括內存的分配以及回收。

新創建的對象分配會首先放在年輕代的 Eden 區,此區的對象回收頻次會比較高,Survivor 區作為 Eden 區和 Old 區之間的緩沖區,在 Survivor 區的對象經歷若干次收集仍然存活的,就會被轉移到老年代 Old 區。

關于對象內存回收的相關知識,我們在后續的文章會再次進行介紹。

2.5、方法區

方法區在 JVM 中也是一個非常重要的區域,和 Java 堆一樣,也是多個線程共享區域,它用于存儲類的信息(包括類的名稱、方法信息、字段信息)、靜態變量、常量以及即時編譯后的代碼等數據。

為了與 Java 堆區分,它還有一個別名 Non-Heap(非堆的意思)。相對而言,GC 對于這個區域的收集是很少出現的,但是也不意味著不會出現異常,當方法區無法滿足內存分配需求時,也會拋出OutOfMemoryError異常。

在 Java 7 及之前版本,大家也習慣稱方法區它為“永久代”(Permanent Generation),更確切來說,應該是“HotSpot 使用永久代實現了方法區”!

2.6、運行時常量池

運行時常量池是方法區的一部分Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池 (Constant pool table),用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入運行時常量池中存放

運行時常量池的功能類似于傳統編程語言的符號表,方便下游程序通過查表可找到對應的數據信息。

同時,運行時常量池相對于Class文件常量池的另外一個特性是具備動態性,Java 語言并不要求常量一定只有編譯器才產生,也就是說并非預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,其中String.intern()方法就是這個特性的應用。

2.7、直接內存

在之前的 Java NIO 文章中,我們提及到直接內存。直接內存(Direct Memory)并不是虛擬機運行時數據區的一部分,也不是 JVM 規范中定義的內存區域。

在 JDK1.4 中引入了 NIO 機制,它允許 Java 程序直接從操作系統中分配直接內存,這部分內存也被稱為堆外內存,在某些場景下可以提高程序執行性能,因為避免了在 Java 堆和 Native 堆中來回復制數據的耗時。

Java NIO 創建堆外內存的簡單示例。

//?創建直接內存
ByteBuffer?byteBuffer?=?ByteBuffer.allocateDirect(1024);

這部分內存如果出現資源不足,也可能導致OutOfMemoryError異常出現。

三、內存設置相關的命令

所有內存溢出的問題,除了代碼可能存在問題以外,更直觀的問題是內存空間不足,如何通過參數來控制各區域的內存大小呢?

3.1、堆內存大小相關參數設置

1)-Xms

設置堆的最小空間大小,此值必須是 1024 的倍數且大于 1 MB。附加字母 k 或 k 表示千字節,m 或 m 表示兆字節,g 或 g 表示千兆字節,其它命令參數同理。比如-Xms1024m,表示堆的最小內存為1024M,默認值為物理內存的1/64

2)-Xmx

設置堆的最大空間大小,此值必須是 1024 的倍數且大于 2 MB。比如-Xmx2048m,表示堆最大內存為2G,默認值為物理內存的1/4

對于服務器部署,-Xms-Xmx通常建議設置為相同的值,以避免堆的內存空間頻繁擴縮。

3)-XX:+HeapDumpOnOutOfMemoryError

表示可以讓虛擬機在出現內存溢出異常時 Dump 出當前的堆內存轉儲快照

3.2、年輕代內存大小相關參數設置

1)-XX:NewSize

設置年輕代的最小空間大小,比如-XX:NewSize=256m,表示年輕代的最小內存為256M

GC 在這個區域比在其他區域執行的頻率更高,如果年輕一代的設置太小,那么將進行大量的小頻率 GCs。如果設置太大,那么會執行完整的GCs,這可能需要很長時間才能完成。Oracle 建議將年輕一代的大小保持在堆總大小的一半到四分之一之間。同時,該值需要小于-Xms的值。

2)-XX:MaxNewSize

設置年輕代的最大空間大小,比如-XX:MaxNewSize=512m,表示年輕代的最大內存為512M

3)-Xmn

設置年輕代堆的初始大小和最大大小,比如-Xmn128m,表示年輕代的初始大小和最大大小為128M

這個參數是對-XX:newSize-XX:MaxnewSize兩個參數同時進行配置,雖然會很方便,但需要注意的是這個參數是在 JDK1.4 版本以后才加入的,低于此版本無法使用。

沒有直接設置老年代的參數,但是可以設置堆空間大小和年輕代空間大小兩個參數來間接控制,公式如下:

老年代空間大小?=?堆空間大小?-?年輕代空間大小
3.3、比例方式相關參數設置

1)-XX:NewRatio

設置年輕代和老年代大小之間的比例,默認值是-XX:NewRatio=2,表示Young : Old = 1 : 2

2)-XX:SurvivorRatio

設置 Eden 空間大小和 Survivor 空間大小之間的比例,默認值是-XX:SurvivorRatio=8,表示Eden : from : to = 8 : 1 : 1

3)-XX:MinHeapFreeRatio

設置 GC 事件后允許的最小可用堆空間百分比(0到100),如果可用堆空間低于此值,則堆將被擴展。默認情況下,此參數為-XX:MinHeapFreeRatio=40,表示40%

4)-XX:MaxHeapFreeRatio

設置 GC 事件后允許的最大可用堆空間百分比(0到100)。如果可用堆空間高于此值,則堆將被縮小。默認情況下,此參數為-XX:MaxHeapFreeRatio=70,表示70%

3.4、非堆區相關參數設置

1)-XX:PermSize

設置永久代的最小空間大小,比如-XX:PermSize=256m,表示永久代的最小內存為256M,默認值為物理內存的1/64

2)-XX:MaxPermSize

置永久代的最大空間大小,比如-XX:MaxPermSize=512m,表示永久代的最大內存為512M,默認值為物理內存的1/4

值得注意的是,-XX:PermSize-XX:MaxPermSize這兩個參數,在 JDK1.7 及以前的版本中有效,在 JDK1.8 中已經被棄用,被-XX:MetaspaceSize-XX:MaxMetaspaceSize兩個參數取代。

3.5、棧內存相關參數設置

1)-Xss

設置每個線程的棧大小,比如-Xss1024k,表示每個線程的堆棧空間大小為1024KB,通常不需要我們調整設置,默認值取決于平臺:

  • Linux/ARM (32-bit):320 KB

  • Linux/i386 (32-bit):320 KB

  • Linux/x64 (64-bit):1024 KB

  • OS X (64-bit):1024 KB

  • Oracle Solaris/i386 (32-bit):320 KB

  • Oracle Solaris/x64 (64-bit):1024 KB

2)-Xoss

設置每個線程中的本地方法棧大小,比如-Xoss128k,表示每個線程中的本地方法棧大小為128KB,不過 HotSpot 并不區分虛擬機棧和本地方法棧,因此對于 HotSpot 來說這個參數是無效的。

3.6、堆外內存相關參數設置

1)-XX:MaxDirectMemorySize

此參數的含義是通過Direct ByteBuffer方式分配的最大堆外內存大小。比如-XX:MaxDirectMemorySize=60m,表示堆外最大內存不能超過60M,如果沒有設置,默認是 0,JVM 會自動申請內存的大小,最大大小受限于-Xmx值。

四、內存溢出的幾種場景

在上文中,我們介紹了 JVM 內存結構以及可能會發生的異常狀況,下面我們一起來復現一下幾種常見的內存溢出現象。

4.1、堆溢出

堆溢出測試類如下。

/***?虛擬機參數:?-Xms10m?-Xmx10m?-XX:+HeapDumpOnOutOfMemoryError*/
public?class?HeapOOMTest?{public?static?void?main(String[]?args)?{List<HeapOOMTest>?list?=?new?ArrayList<>();while?(true){list.add(new?HeapOOMTest());}}
}

在 IDEA 中設置 JVM 相關的參數。

運行后輸出結果如下:

java.lang.OutOfMemoryError:?Java?heap?space
Dumping?heap?to?java_pid21886.hprof?...
Exception?in?thread?"main"?java.lang.OutOfMemoryError:?Java?heap?spaceat?java.util.Arrays.copyOf(Arrays.java:3210)at?java.util.Arrays.copyOf(Arrays.java:3181)at?java.util.ArrayList.grow(ArrayList.java:265)at?java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)at?java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)at?java.util.ArrayList.add(ArrayList.java:462)at?HeapOOMTest.main(HeapOOMTest.java:21)
Heap?dump?file?created?[12920047?bytes?in?0.090?secs]

從報錯的日志上可以清晰的看到,出現內存溢出的區域在Java heap space,問題代碼在HeapOOMTest.java:21。生成的快照文件在當前工程目錄下。

4.2、虛擬機棧和本地方法棧溢出

棧溢出測試類如下,JVM 相關的參數設置步驟同上。

/***?虛擬機參數:?-Xss256k*/
public?class?StackOOMTest?{private?int?stackLength?=?1;public?static?void?main(String[]?args)?{StackOOMTest?stackOOMTest?=?new?StackOOMTest();try?{stackOOMTest.stackLeak();}?catch?(Throwable?e){System.out.println("stack?length:"?+?stackOOMTest.stackLength);throw?e;}}private?void?stackLeak()?{stackLength++;stackLeak();}
}

運行后輸出結果如下:

Exception?in?thread?"main"?java.lang.StackOverflowError
stack?length:2326at?StackOOMTest.stackLeak(StackOOMTest.java:23)at?StackOOMTest.stackLeak(StackOOMTest.java:23)at?StackOOMTest.stackLeak(StackOOMTest.java:23)at?StackOOMTest.stackLeak(StackOOMTest.java:23)at?StackOOMTest.stackLeak(StackOOMTest.java:23)......

在單個線程下,當棧幀的深度過大,也會超出虛擬機棧的最大容量,當無法分配內存的時候,虛擬機就會拋出StackOverflowError異常。

我們在來看另一個例子。

/***?虛擬機參數:?-Xss256k*/
public?class?StackOOMTest2?{public?static?void?main(String[]?args)?{StackOOMTest2?stackOOMTest?=?new?StackOOMTest2();stackOOMTest.stackLeakByThread();}private?void?stackLeakByThread()?{while?(true)?{new?Thread(new?Runnable()?{@Overridepublic?void?run()?{running();}}).start();}}private?void?running()?{while?(true)?{}}
}

運行后輸出結果如下:

Exception?in?thread?"main"?java.lang.OutOfMemoryError:?unable?to?create?new?native?thread

在無限制的創建多個線程下,虛擬機棧也可能會出現OutOfMemoryError異常,此時操作系統會出現假死, CPU 被完全跑滿了,測試過程中發現操作系統下所有的應用無法正常操作,請謹慎測試。(以上報錯內容,引入網上博主的測試結果)

4.3、方法區和運行時常量池溢出

運行時常量池屬于方法區的一部分,這兩個區域中我們抽取運行時常量池區域來測試內存溢出的現象。

針對這個區域,我們可以采用String.intern()方法進行測試。String.intern()是一個Native方法,意思是如果常量池中有一個String對象的字符串,就返回池中的這個字符串的String對象;否則,將此String對象包含的字符串添加到常量池中去,并且返回此String對象的引用。

測試代碼如下,JVM 相關的參數設置步驟同上。

/***?虛擬機參數:-XX:PermSize=10M?-XX:MaxPermSize=10M*/
public?class?RuntimeConstantPoolOOMTest?{public?static?void?main(String[]?args)?{List<String>?list?=?new?ArrayList<>();int?i?=?0;while?(true)?{list.add(String.valueOf(i++).intern());}}
}

運行后輸出結果如下:

Exception?in?thread?"main"?java.lang.OutOfMemoryError:?PermGen?spaceat?java.lang.String.intern(Native?Method)

實際上,這個異常只會出現在 JDK1.6 及之前的版本中,在 JDK1.7 中是不會有這個異常的,它會一直while循環下去。

在上文中我們介紹過,在 JDK1.7 及之前的版本中,方法區也被稱為永久代,因此看到的是PermGen space區域的OutOfMemoryError異常信息。

但在 JDK1.8 及之后的版本中,沒有-XX:PermSize-XX:MaxPermSize這兩個參數,取而代之的是-XX:MetaspaceSize-XX:MaxMetaspaceSize這兩個參數,同時方法區被稱為元空間,并劃入到本地內存中。

4.4、直接內存溢出

直接內存溢出測試類如下,JVM 相關的參數設置步驟同上。

/***?虛擬機參數:?-XX:MaxDirectMemorySize=2048k*/
public?class?DirectMemoryTest?{public?static?void?main(String[]?args){int?i?=?0;List<ByteBuffer>?buffers?=?new?ArrayList<>();while?(true)?{ByteBuffer?bb?=?ByteBuffer.allocateDirect(1024?*?1024?*?1);buffers.add(bb);System.out.println(i++);}}
}

運行后輸出結果如下:

0
1
Exception?in?thread?"main"?java.lang.OutOfMemoryError:?Direct?buffer?memoryat?java.nio.Bits.reserveMemory(Bits.java:694)at?java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)at?java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)at?DirectMemoryTest.main(DirectMemoryTest.java:22)

從日志上可以清晰的看到,OutOfMemoryError的異常區域為Direct buffer memory,也就是直接內存區域。

五、JDK 各版本內存布局變化

在上文中我們也提及到過,不同的版本 JVM 內存布局可能有所不同。最后,我們再一起來看下 JDK 1.6、1.7、1.8 的內存模型演變過程。

每一次的調整改動,都是為了更好的適應當下 CPU 性能,最大限度的提升 JVM 運行效率,各個版本的差異如下:

  • 在 JDK1.6 中,常量池存放于方法區,也被稱為永久代

  • 在 JDK1.7 中,將常量池進行細分,字符串常量池存放于堆中,運行時常量池和類常量池,存放于方法區中

  • 在 JDK1.8 中,無永久代,將運行時常量池和類常量池都保存在元數據區中,也就是大家常說的元空間,但字符串常量池仍然存放在堆上

關于各個內存區域的變化,有些面試官會提出以下一些問題,我們一起來看下。

問題一:在 JDK 1.7 中,為什么要將字符串常量池移動到堆中

這個問題的主要原因在于 GC 的回收效率上,在永久代中的數據, GC 回收效率非常低,只有在整堆收集 (Full GC) 的時候才會被執行 GC;而 Java 程序中通常會有大量的被創建的字符串需要等待回收,將字符串常量池放到堆中,能夠更高效及時的回收字符串,釋放內存。

問題二:JDK 1.8 為什么要廢棄永久代,用元空間取而代之

HotSpot 團隊選擇移除永久代,簡單的說有兩個因素:

  • 外因:在之前的文章中我們說到過,Oralce 擁有 JRockit 與 HotSpot 兩款優秀的虛擬機,在 JRockit 中并沒有永久代,為了將 JRockit 優秀的設計融入 HotSpot 中,在 JDK 1.8 中 HotSpot 移除了永久代

  • 內因:JDK 1.7 中永久代大小受-XX:PermSize-XX:MaxPermSize這兩個參數的限制,這兩個參數在物理空間上又受到 JVM 設定的內存大小限制,這就會導致在使用中永久代可能出現內存溢出的問題,因此在 JDK 1.8 及之后的版本中徹底移除了永久代,用元空間來進行替代,其中元空間并不在虛擬機內存中而是使用本地內存,相比 JDK 1.7 而言,出現內存溢出的風險要小很多,但也不是完全不限制,其大小受操作系統可用內存大小的限制,也支持通過-XX:MetaspaceSize-XX:MaxMetaspaceSize這兩個參數來配置

如果想要在 JDK1.8 中測試元空間的內存溢出現象,可以通過 Cglib 動態代理框架來創建類,它會將類存放在元空間,測試示例如下。

/***?虛擬機參數:?-XX:MetaspaceSize=10m?-XX:MaxMetaspaceSize=10m*/
public?class?MetaspaceOOMTest?{public?static?void?main(String[]?args)?{int?i?=?0;try?{while?(true)?{Enhancer?enhancer?=?new?Enhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(new?MethodInterceptor()?{@Overridepublic?Object?intercept(Object?obj,?Method?method,?Object[]?args,?MethodProxy?proxy)?throws?Throwable?{return?proxy.invokeSuper(obj,?args);}});//?創建一個動態代理類enhancer.create();i++;}}?catch?(Throwable?e)?{System.out.println("第"?+?i?+?"次時發生異常");e.printStackTrace();}}private?static?class?OOMObject?{public?OOMObject()?{}}
}

運行后輸出結果如下:

第546次時發生異常
net.sf.cglib.core.CodeGenerationException:?java.lang.reflect.InvocationTargetException-->nullat?net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:348)at?net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)at?net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:117)at?net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)at?net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)at?net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)at?MetaspaceOOMTest.main(MetaspaceOOMTest.java:26)
Caused?by:?java.lang.reflect.InvocationTargetExceptionat?sun.reflect.GeneratedMethodAccessor1.invoke(Unknown?Source)at?sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at?java.lang.reflect.Method.invoke(Method.java:498)at?net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)at?net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)...?6?more
Caused?by:?java.lang.OutOfMemoryError:?Metaspaceat?java.lang.ClassLoader.defineClass1(Native?Method)at?java.lang.ClassLoader.defineClass(ClassLoader.java:763)...?11?more

從日志上可以清晰的看到,執行到第 546 次時出現OutOfMemoryError,內存區域在Metaspace

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/696857.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/696857.shtml
英文地址,請注明出處:http://en.pswp.cn/news/696857.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

MedicalGPT 訓練醫療大模型,實現了包括增量預訓練、有監督微調、RLHF(獎勵建模、強化學習訓練)和DPO(直接偏好優化)

MedicalGPT 訓練醫療大模型&#xff0c;實現了包括增量預訓練、有監督微調、RLHF(獎勵建模、強化學習訓練)和DPO(直接偏好優化)。 MedicalGPT: Training Your Own Medical GPT Model with ChatGPT Training Pipeline. 訓練醫療大模型&#xff0c;實現了包括增量預訓練、有監督微…

Linux第63步_為新創建的虛擬機添加必要的目錄和安裝支持linux系統移植的軟件

1、創建必要的目錄 1)、創建“/home/zgq/linux/”目錄 打開終端&#xff0c;進入“/home/zgq/”目錄 輸入“mkdir linux回車”&#xff0c;創建“/home/zgq/linux/”目錄 輸入“ls回車”&#xff0c;列舉“/home/zgq/”目錄的所有文件和文件夾 創建好“/home/zgq/linux/”…

EIS(防抖):meshflow算法 C++實現

視頻防抖的應用 對視頻防抖的需求在許多領域都有。 這在消費者和專業攝像中是極其重要的。因此&#xff0c;存在許多不同的機械、光學和算法解決方案。即使在靜態圖像拍攝中&#xff0c;防抖技術也可以幫助拍攝長時間曝光的手持照片。 在內窺鏡和結腸鏡等醫療診斷應用中&…

Go 中的 init 如何用?它的常見應用場景有哪些呢?

嗨&#xff0c;大家好&#xff01;我是波羅學。本文是系列文章 Go 技巧第十六篇&#xff0c;系列文章查看&#xff1a;Go 語言技巧。 Go 中有一個特別的 init() 函數&#xff0c;它主要用于包的初始化。init() 函數在包被引入后會被自動執行。如果在 main 包中&#xff0c;它也…

QT基本組件

四、基本組件 Designer 設計師&#xff08;重點&#xff09; Qt包含了一個Designer程序&#xff0c;用于通過可視化界面設計開發界面&#xff0c;保存文件格式為.ui&#xff08;界面文件&#xff09;。界面文件內部使用xml語法的標簽式語言。 在Qt Creator中創建文件時&#xf…

滾雪球學Java(67):深入理解 TreeMap:Java 中的有序鍵值映射表

咦咦咦&#xff0c;各位小可愛&#xff0c;我是你們的好伙伴——bug菌&#xff0c;今天又來給大家普及Java SE相關知識點了&#xff0c;別躲起來啊&#xff0c;聽我講干貨還不快點贊&#xff0c;贊多了我就有動力講得更嗨啦&#xff01;所以呀&#xff0c;養成先點贊后閱讀的好…

機器人內部傳感器閱讀筆記及心得-位置傳感器-旋轉變壓器、激光干涉式編碼器

旋轉變壓器 旋轉變壓器是一種輸出電壓隨轉角變化的檢測裝置&#xff0c;是用來檢測角位移的&#xff0c;其基本結構與交流繞線式異步電動機相似&#xff0c;由定子和轉子組成。 旋轉變壓器的原理如圖1所示&#xff0c;定子相當于變壓器的一次側&#xff0c;有兩組在空間位置上…

MyBatis-Plus 優雅實現數據加密存儲

文章目錄 前言一、數據庫字段加解密實現1. 定義加密類型枚舉2. 定義AES密鑰和偏移量3. 配置定義使用的加密類型4. 加密解密接口5. 解密解密異常類6. 加密解密實現類6.1 AES加密解密實現類6.2 Base64加密解密實現類 7. 實現數據庫的字段保存加密與查詢解密處理類8. MybatisPlus配…

使用python進行量化交易

yfinance yfinance國內不能使用&#xff0c;可以使用tushare、akshare代替 import yfinance as yf# 輸入股票代碼 stock_symbol AAPL # 替換為你想要查詢的股票代碼# 獲取股票數據 data yf.download(stock_symbol)# 打印實時數據 print(data)pip install akshare import …

Selenium安裝與配置

文章目錄 一、selenium安裝1. Python環境準備&#xff1a;2. 安裝Selenium&#xff1a;3. 瀏覽器驅動安裝&#xff1a;4. 驗證安裝&#xff1a; 二、常見問題1. Selenium版本與瀏覽器驅動程序不兼容&#xff1a;2. 瀏覽器驅動程序路徑未正確設置&#xff1a; Selenium是一個用于…

2024年1月手機市場行業分析:蘋果手機份額驟降,國產高端手機成功逆襲!

小米Ultra發布。 一方面&#xff0c;我們有望看到國產手機再一次超越自己的決心&#xff0c;繼續創新追逐高端&#xff1b;另一方面&#xff0c;我們也不得不正視目前手機市場所面臨的危機狀態。 2024年1月的線上手機市場遠不如去年。根據鯨參謀數據顯示&#xff0c;今年1月京…

Qt(C++)面試題 | 精選25項常問

面試是每個求職者都必須經歷的一關,而QT面試更是需要面試者有深厚的編程基礎和豐富的實戰經驗。下面我們為大家整理了25道QT面試題,希望能夠幫助大家在求職路上獲得成功。 ?Qt 中常用的五大模塊是哪些? Qt 中常用的五大模塊包括: QtCore:提供了 Qt 的核心功能,例如基本的…

Java面試題之分布式/微服務篇

經濟依舊不景氣啊&#xff0c;如此大環境下Java還是這么卷&#xff0c;又是一年一次的金三銀四。 兄弟們&#xff0c;你準備好了嗎&#xff1f;沖沖沖&#xff01;歐里給&#xff01; 分布式/微服務相關面試題解 題一&#xff1a;CAP理論&#xff0c;BASE理論題二&#xff1a;…

深度神經網絡

包括&#xff1a;深度前饋神經網絡、深度卷積神經網絡、深度循環神經網絡 深度神經網絡全面概述&#xff1a;從基本概念到實際模型和硬件基礎-騰訊云開發者社區-騰訊云

MQL語言實現JSON協議庫

文章目錄 一、MQL語言實現JSON協議的意義二、定義JSON數據枚舉類型簡單數據類型復雜數據類型枚舉數據類型定義類變量清理與賦值方法構造與析構方法重載運算符添加與設置方法序列化與反序列方法 一、MQL語言實現JSON協議的意義 數據交互&#xff1a;JSON是一種輕量級的數據交換格…

【2024軟件測試面試必會技能】Postman(1): postman的介紹和安裝

Postman的介紹 Postman 是一款谷歌開發的接口測試工具,使API的調試與測試更加便捷。 它提供功能強大的 Web API & HTTP 請求調試。它能夠發送任何類型的HTTP 請求 (GET, HEAD, POST, PUT..)&#xff0c;附帶任何數量的參數 headers。 postman是一款支持http協議的接口調試…

【PTA|函數題|期末復習】指針

目錄 6-1 計算兩數的和與差&#xff08;5分&#xff09; 函數接口定義&#xff1a; 裁判測試程序樣例&#xff1a; 輸入樣例&#xff1a; 輸出樣例&#xff1a; 代碼 6-2 拆分實數的整數與小數部分 (5分) 函數接口定義&#xff1a; 裁判測試程序樣例&#xff1a; 輸入…

springboot整合mybatisPlus超級詳細

springboot整合mybatis-plus超級詳細 一、環境二、springboot整合myBatisPlus2.1新建2.2 添加Mybatis-plus和mysql依賴2.3 修改配置文件2.4 新建包和文件2.5 新建表2.6 創建實體類2.7 創建Mapper接口2.8 創建Service接口2.9 創建Service實現類2.10 增刪改查 MyBatis-Plus&#…

C# Onnx 使用onnxruntime部署實時視頻幀插值

目錄 介紹 效果 模型信息 項目 代碼 下載 C# Onnx 使用onnxruntime部署實時視頻幀插值 介紹 github地址&#xff1a;https://github.com/google-research/frame-interpolation FILM: Frame Interpolation for Large Motion, In ECCV 2022. The official Tensorflow 2…

四.QT5工具安裝和環境變量的配置

1.以管理員身份運行安裝包 2.登錄qt賬號&#xff0c;點擊【next】 3.選中同意 4.選擇安裝目錄&#xff0c;注意不能有中文和空格 5.勾選 64位 mingw。點擊【next】&#xff0c;等待安裝完成 6.配置環境變量