面試必問(wèn):說(shuō)一下 Java 虛擬機(jī)的內(nèi)存布局?
我們通常所說(shuō)的 Java 虛擬機(jī)(JVM)的內(nèi)存布局,一般是指 Java 虛擬機(jī)的運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime Data Area),也就是當(dāng)字節(jié)碼被類(lèi)加載器加載之后的執(zhí)行區(qū)域劃分。當(dāng)然它通常是 JVM 模塊的第一個(gè)面試問(wèn)題,所以,接下來(lái)我們一起來(lái)看它里面包含了哪些內(nèi)容。
官方定義
《Java虛擬機(jī)規(guī)范》中將 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)域劃分為以下 5 部分:
- 程序計(jì)數(shù)器(Program Counter Register)
- Java虛擬機(jī)棧(Java Virtual Machine Stacks)
- 本地方法棧(Native Method Stack)
- Java 堆(Java Heap)
- 方法區(qū)(Methed Area)
如下圖所示:
接下來(lái),我們分別來(lái)看每個(gè)模塊的作用及詳細(xì)介紹。
一、程序計(jì)數(shù)器
《Java虛擬機(jī)規(guī)范》中對(duì)程序計(jì)數(shù)器的定義如下:
The Java Virtual Machine can support many threads of execution at once (JLS §17). Each Java Virtual Machine thread has its own pc (program counter) register. At any point, each Java Virtual Machine thread is executing the code of a single method, namely the current method (§2.6) for that thread. If that method is not native, the pc register contains the address of the Java Virtual Machine instruction currently being executed. If the method currently being executed by the thread is native, the value of the Java Virtual Machine's pc register is undefined. The Java Virtual Machine's pc register is wide enough to hold a returnAddress or a native pointer on the specific platform.
以上內(nèi)容翻譯成中文,簡(jiǎn)單來(lái)說(shuō)它的含義是:JVM 中可以有多個(gè)執(zhí)行線(xiàn)程,每個(gè)線(xiàn)程都有自己的程序計(jì)數(shù)器,在程序計(jì)數(shù)器中包含的是正在執(zhí)行線(xiàn)程的指令地址。
也就是說(shuō),程序計(jì)數(shù)器(Program Counter Register)是線(xiàn)程獨(dú)有一塊很小的內(nèi)存區(qū)域,保存當(dāng)前線(xiàn)程所執(zhí)行字節(jié)碼的位置,包括正在執(zhí)行的指令、跳轉(zhuǎn)、分支、循環(huán)、異常處理等。
1、作用
我們知道,CPU 核數(shù)是比較少的,而任務(wù)(線(xiàn)程)是比較多的,所以真實(shí)的情況是,CPU 會(huì)不停的切換線(xiàn)程以執(zhí)行所有的程序,當(dāng)然因?yàn)椋–PU)切換的速度比較快,所以我們是感知不到的,我們感覺(jué)好像所有的程序都是一直在執(zhí)行,其實(shí)從微觀(guān)的層面來(lái)看,所有的程序都是切換執(zhí)行的。
那么問(wèn)題來(lái)了,CPU 一直在切換線(xiàn)程執(zhí)行任務(wù),那 CPU 再次切換到某個(gè)線(xiàn)程時(shí),它是怎么知道當(dāng)前的線(xiàn)程上次知道到哪了?
這就是程序計(jì)數(shù)器的作用,程序計(jì)數(shù)器里面保存了當(dāng)前線(xiàn)程執(zhí)行的行號(hào),這樣當(dāng) CPU 切換到當(dāng)前線(xiàn)程時(shí),才能接著上次執(zhí)行的位置,繼續(xù)執(zhí)行。
PS:程序計(jì)數(shù)器中真實(shí)記錄的是下一行任務(wù)的執(zhí)行指令。程序計(jì)數(shù)器也是 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)中執(zhí)行最快的一塊區(qū)域。
2、線(xiàn)程共享
程序計(jì)數(shù)器記錄的是每個(gè)線(xiàn)程的執(zhí)行行號(hào),所以每個(gè)線(xiàn)程都擁有自己的程序計(jì)數(shù)器,所以此區(qū)域不是線(xiàn)程共享的,而是線(xiàn)程私有的。
3、GC
GC 是 Garbage Collection 的縮寫(xiě),譯為垃圾收集。
此區(qū)域不存在 GC。
4、OOM
OOM 是 Out of Memory 的縮寫(xiě),譯為內(nèi)存溢出。
此區(qū)域不存在 OOM 的問(wèn)題。
二、Java 虛擬機(jī)棧
Java 虛擬機(jī)棧(Java Virtual Machine Stack)也叫做 JVM 棧,《Java虛擬機(jī)規(guī)范》對(duì)此區(qū)域的說(shuō)明如下:
Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread. A Java Virtual Machine stack stores frames (§2.6). A Java Virtual Machine stack is analogous to the stack of a conventional language such as C: it holds local variables and partial results, and plays a part in method invocation and return. Because the Java Virtual Machine stack is never manipulated directly except to push and pop frames, frames may be heap allocated. The memory for a Java Virtual Machine stack does not need to be contiguous.
In the First Edition of The Java? Virtual Machine Specification, the Java Virtual Machine stack was known as the Java stack.
This specification permits Java Virtual Machine stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of Java Virtual Machine stacks, as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.
The following exceptional conditions are associated with Java Virtual Machine stacks:
- If the computation in a thread requires a larger Java Virtual Machine stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
- If Java Virtual Machine stacks can be dynamically expanded, and expansion is attempted but insufficient memory can be made available to effect the expansion, or if insufficient memory can be made available to create the initial Java Virtual Machine stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
以上內(nèi)容翻譯成中文的含義如下:
Java 虛擬機(jī)棧是線(xiàn)程私有的區(qū)域,它隨著線(xiàn)程的創(chuàng)建而創(chuàng)建。它里面保存的是局部變量表(基礎(chǔ)數(shù)據(jù)類(lèi)型和對(duì)象引用地址)和計(jì)算過(guò)程中的中間結(jié)果。Java 虛擬機(jī)的內(nèi)存不需要連續(xù),它只有兩個(gè)操作:入棧和出棧。
Java 虛擬機(jī)棧要么大小固定,要么根據(jù)計(jì)算動(dòng)態(tài)的擴(kuò)展和收縮。程序員可以對(duì) Java 虛擬機(jī)棧進(jìn)行初始值的大小設(shè)置和最大值的設(shè)置。
Java 虛擬機(jī)棧出現(xiàn)的異常有兩種:
- 當(dāng) Java 虛擬機(jī)棧大小固定時(shí),如果程序中的棧分配超過(guò)了最大虛擬機(jī)棧就會(huì)出現(xiàn) StackOverflowError 異常。
- 如果 Java 虛擬機(jī)棧是動(dòng)態(tài)擴(kuò)展的,那么當(dāng)內(nèi)存不足時(shí),就會(huì)引發(fā) OutOfMemoryError 的異常。
1、作用
Java 虛擬機(jī)棧主要是管 Java 程序運(yùn)行的,它保存的是方法的局部變量、方法執(zhí)行中的部分結(jié)果,并參與方法的調(diào)用和返回。
簡(jiǎn)單來(lái)說(shuō),棧是運(yùn)行時(shí)單位,而堆是存儲(chǔ)單位。也就是說(shuō):棧解決的是程序運(yùn)行的問(wèn)題,即程序如何執(zhí)行?或者說(shuō)如何處理數(shù)據(jù)。堆解決的是數(shù)據(jù)存儲(chǔ)的問(wèn)題,即數(shù)據(jù)怎么放?放在哪兒。
2、線(xiàn)程共享
Java 虛擬機(jī)棧是線(xiàn)程私有的,它的生命周期和線(xiàn)程的生命周期一致。
3、GC
Java 虛擬機(jī)棧因?yàn)橹挥腥霔:统鰲蓚€(gè)操作,所以它是不涉及垃圾回收的。
4、OOM
此區(qū)域雖然沒(méi)有 GC,但存在兩種異常:
- 當(dāng) Java 虛擬機(jī)棧大小固定時(shí),如果程序中的棧分配超過(guò)了最大虛擬機(jī)棧就會(huì)出現(xiàn) StackOverflowError 異常。
- 如果 Java 虛擬機(jī)棧是動(dòng)態(tài)擴(kuò)展的,那么當(dāng)內(nèi)存不足時(shí),就會(huì)引發(fā) OutOfMemoryError 的異常。
也就是,Java 虛擬機(jī)棧是可能存在 OOM 的。
5、常見(jiàn)參數(shù)設(shè)置
設(shè)置 Java 虛擬機(jī)棧大小:-Xss<size>。
如設(shè)置:-Xss128k,表示設(shè)置每個(gè)線(xiàn)程的棧大小為 128k,此設(shè)置等價(jià)于 -XX:ThreadStackSize=128k。
6、常見(jiàn)問(wèn)題演示
最簡(jiǎn)單的錯(cuò)誤示例就是死循環(huán),方法自己調(diào)自己,這樣 Java 虛擬機(jī)棧就會(huì)只入棧不出棧,當(dāng)?shù)竭_(dá) Java 虛擬機(jī)棧的最大數(shù)之后就會(huì)出現(xiàn) StackOverflowError 異常,如下圖所示:
三、本地方法棧
本地方法棧(Native Method Stacks),《Java 虛擬機(jī)規(guī)范》對(duì)此區(qū)域的說(shuō)明如下:
An implementation of the Java Virtual Machine may use conventional stacks, colloquially called "C stacks," to support native methods (methods written in a language other than the Java programming language). Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C. Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks. If supplied, native method stacks are typically allocated per thread when each thread is created.
This specification permits native method stacks either to be of a fixed size or to dynamically expand and contract as required by the computation. If the native method stacks are of a fixed size, the size of each native method stack may be chosen independently when that stack is created.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the native method stacks, as well as, in the case of varying-size native method stacks, control over the maximum and minimum method stack sizes.
The following exceptional conditions are associated with native method stacks:
- If the computation in a thread requires a larger native method stack than is permitted, the Java Virtual Machine throws a StackOverflowError.
- If native method stacks can be dynamically expanded and native method stack expansion is attempted but insufficient memory can be made available, or if insufficient memory can be made available to create the initial native method stack for a new thread, the Java Virtual Machine throws an OutOfMemoryError.
以上內(nèi)容,挑重點(diǎn)簡(jiǎn)單翻譯一下:本地方法棧俗稱(chēng)“C棧”,它是 Native(本地)方法(用 Java 編程語(yǔ)言以外的語(yǔ)言編寫(xiě)的方法),此區(qū)域和 Java 虛擬機(jī)棧類(lèi)似,這不過(guò)諸如 C 語(yǔ)言等使用的??臻g。它也是存在兩種異常:StackOverflowError 和 OutOfMemoryError。
PS:因?yàn)榇藚^(qū)域是非 Java 語(yǔ)言實(shí)現(xiàn)和使用的,所以本文就不做過(guò)多的贅述,總之,記得一句話(huà):此區(qū)域和 Java 虛擬機(jī)棧類(lèi)似,不過(guò)是給 C/C++ 語(yǔ)言使用的。
四、堆
堆(Heap)《Java 虛擬機(jī)規(guī)范》對(duì)此區(qū)域的說(shuō)明如下:
The Java Virtual Machine has a heap that is shared among all Java Virtual Machine threads. The heap is the run-time data area from which memory for all class instances and arrays is allocated.
The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the heap, as well as, if the heap can be dynamically expanded or contracted, control over the maximum and minimum heap size.
The following exceptional condition is associated with the heap:
- If a computation requires more heap than can be made available by the automatic storage management system, the Java Virtual Machine throws an OutOfMemoryError.
以上內(nèi)容,挑重點(diǎn)簡(jiǎn)單翻譯一下:
堆是線(xiàn)程共享的,程序中所有類(lèi)實(shí)例和數(shù)組的內(nèi)存都存儲(chǔ)在此區(qū)域,它在 Java 虛擬機(jī)啟動(dòng)時(shí)就會(huì)創(chuàng)建。對(duì)象不會(huì)被顯式釋放,只會(huì)在垃圾收集時(shí)釋放。堆的大小可以是固定的,也可以動(dòng)態(tài)擴(kuò)展或收縮。堆的內(nèi)存在物理層面不需要是連續(xù)的。
程序員可以對(duì)堆進(jìn)行初始大小控制,或者設(shè)置最大、最小堆的容量。
堆可能會(huì)出現(xiàn) OutOfMemoryError 異常。
1、作用
堆是 Java 虛擬機(jī)的主要存儲(chǔ)單位,Java 中所有的對(duì)象和數(shù)組都是保存在此區(qū)域的。
2、線(xiàn)程共享
堆是線(xiàn)程共享的,堆上的對(duì)象可能被多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)。
3、GC
堆是 JVM 最大的一塊區(qū)域,也是垃圾回收器進(jìn)行垃圾回收最頻繁的一塊區(qū)域。
4、OOM
當(dāng)堆空間不足時(shí),會(huì)發(fā)生 OutOfMemoryError 異常。
5、常見(jiàn)參數(shù)設(shè)置
- -Xms<size>:設(shè)置初始 Java 堆大小,比如:-Xms10m,表示設(shè)置堆的初始大小為 10MB。
- -Xmx<size>:設(shè)置最大 Java 堆大小,比如:-Xmx10m,表示設(shè)置堆的最大空間為 10MB。
6、常見(jiàn)問(wèn)題演示
接下來(lái),我們來(lái)演示一下堆空間 OOM 的問(wèn)題,我們先使用“-Xmx50m”的參數(shù)來(lái)設(shè)置一下 Idea,它表示將程序運(yùn)行的最大內(nèi)存設(shè)置為 50m,如果程序的運(yùn)行超過(guò)這個(gè)值就會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題,設(shè)置方法如下:
設(shè)置后的最終效果這樣的:
PS:因?yàn)槲沂褂玫?Idea 是社區(qū)版,所以可能和你的界面不一樣,你只需要點(diǎn)擊“Edit Configurations...”找到“VM options”選項(xiàng),設(shè)置上“-Xmx50m”參數(shù)就可以了。
配置完 Idea 之后,接下來(lái)我們來(lái)實(shí)現(xiàn)一下業(yè)務(wù)代碼。在代碼中我們會(huì)創(chuàng)建一個(gè)大對(duì)象,這個(gè)對(duì)象中會(huì)有一個(gè) 10m 大的數(shù)組,然后我們將這個(gè)大對(duì)象存儲(chǔ)在 ThreadLocal 中,再使用線(xiàn)程池執(zhí)行大于 5 次添加任務(wù),因?yàn)樵O(shè)置了最大運(yùn)行內(nèi)存是 50m,所以理想的情況是執(zhí)行 5 次添加操作之后,就會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題,實(shí)現(xiàn)代碼如下:
以上程序的執(zhí)行結(jié)果如下:
從上述圖片可看出,當(dāng)程序執(zhí)行到第 5 次添加對(duì)象時(shí)就出現(xiàn)內(nèi)存溢出的問(wèn)題了,這是因?yàn)樵O(shè)置了最大的運(yùn)行內(nèi)存是 50m,每次循環(huán)會(huì)占用 10m 的內(nèi)存,加上程序啟動(dòng)會(huì)占用一定的內(nèi)存,因此在執(zhí)行到第 5 次添加任務(wù)時(shí),就會(huì)出現(xiàn)內(nèi)存溢出的問(wèn)題。
五、方法區(qū)
方法區(qū)(Method Area)《Java 虛擬機(jī)規(guī)范》對(duì)此區(qū)域的說(shuō)明如下:
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
The following exceptional condition is associated with the method area:
- If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.
以上內(nèi)容,挑重點(diǎn)簡(jiǎn)單翻譯一下:
方法區(qū)是線(xiàn)程共享的,方法區(qū)類(lèi)似于傳統(tǒng)語(yǔ)言的編譯代碼的存儲(chǔ)區(qū),或者類(lèi)似于操作系統(tǒng)進(jìn)程中的“文本”段。它存儲(chǔ)每個(gè)類(lèi)的結(jié)構(gòu),如運(yùn)行時(shí)常量池、字段和方法數(shù)據(jù),以及方法和構(gòu)造函數(shù)的代碼。
方法區(qū)域是在 Java 虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的,盡管方法區(qū)域在邏輯上是堆的一部分,但簡(jiǎn)單的實(shí)現(xiàn)可能選擇不進(jìn)行垃圾收集或壓縮。方法區(qū)域可以是固定的大小,也可以動(dòng)態(tài)擴(kuò)展。方法區(qū)的(物理)內(nèi)存不需要連續(xù)。
Java 虛擬機(jī)實(shí)現(xiàn)可以為程序員或用戶(hù)提供對(duì)方法區(qū)域初始大小的控制,以及在可變大小的方法區(qū)域的情況下,對(duì)最大和最小方法區(qū)域大小的控制。
如果方法區(qū)中的內(nèi)存無(wú)法滿(mǎn)足分配請(qǐng)求,Java 虛擬機(jī)將拋出一個(gè) OutOfMemoryError。
1、作用
用于存儲(chǔ)每個(gè)類(lèi)的結(jié)構(gòu),包括運(yùn)行時(shí)常量池、靜態(tài)變量、字段和方法數(shù)據(jù)。
2、HotSpot 方法區(qū)實(shí)現(xiàn)
HotSpot 虛擬機(jī)是 Sun JDK 和 Open JDK 中自帶的虛擬機(jī),也是目前使用范圍最廣的 Java 虛擬機(jī)。作為官方 Java 虛擬機(jī)的化身,目前所講的所有知識(shí),幾乎都是針對(duì)此虛擬機(jī)的,所以我們要看 HotSpot 虛擬機(jī)對(duì)《Java 虛擬機(jī)規(guī)范》中方法區(qū)的實(shí)現(xiàn)。
?對(duì)于 HotSpot 虛擬機(jī)來(lái)說(shuō),不同的 JDK 方法區(qū)的實(shí)現(xiàn)是不同的,在 JDK 1.7 之前,HotSpot 技術(shù)團(tuán)隊(duì)使用的是永久代來(lái)實(shí)現(xiàn)方法區(qū)的,但這種實(shí)現(xiàn)有一個(gè)致命的問(wèn)題,這樣設(shè)計(jì)更容易造成內(nèi)存溢出。因?yàn)橛谰么?-XX:MaxPermSize(方法區(qū)分配的最大內(nèi)存)的上限,即使不設(shè)置也會(huì)有默認(rèn)的大小。例如,32 位操作系統(tǒng)中的 4GB 內(nèi)存限制等,并且這樣設(shè)計(jì)導(dǎo)致了部分的方法在不同類(lèi)型的 Java 虛擬機(jī)下的表現(xiàn)也不同,比如 String::intern() 方法。所以在 JDK 1.7 時(shí) HotSpot 虛擬機(jī)已經(jīng)把原本放在永久代的字符串常量池和靜態(tài)變量等移出了方法區(qū),并且在 JDK 1.8 中完全廢棄了永久代的概念。
JDK 1.8 之后,HotSpot 虛擬機(jī)開(kāi)始使用元空間(Meta Space)來(lái)實(shí)現(xiàn)方法區(qū)了。?
3、線(xiàn)程共享
方法區(qū)是線(xiàn)程共享的。
4、GC
《Java 虛擬機(jī)規(guī)范》中規(guī)定方法區(qū)可以沒(méi)有 GC(垃圾回收),但 HotSpot 中對(duì)此區(qū)域?qū)崿F(xiàn)了 GC 操作。
5、OOM
方法區(qū)是存在 OOM 情況的,比如在 JDK 1.8 中,如果元空間設(shè)置空間過(guò)小,而類(lèi)信息產(chǎn)生的過(guò)多就會(huì)產(chǎn)生 OOM,如下示例所示:
以上程序的執(zhí)行結(jié)果如下圖所示:
以上代碼是通過(guò) CGLIB 不斷的產(chǎn)生動(dòng)態(tài)代理類(lèi)將方法區(qū)填滿(mǎn),從而就導(dǎo)致 OOM 的問(wèn)題。
PS:在使用 CGLIB 之前,需要現(xiàn)在當(dāng)前項(xiàng)目中導(dǎo)入 CGLIB 才可以正常使用。
6、常用參數(shù)設(shè)置
永久代(HotSpot 虛擬機(jī),JDK 1.7 之前設(shè)置有效):
- -XX:PermSize=100m:設(shè)置永久代初始值為 100MB。
- -XX:MaxPermSize=100m:設(shè)置永久代最大值為 100MB。
元空間(HotSpot 虛擬機(jī),JDK 1.8 之后設(shè)置有效):
- -XX:MetaspaceSize=100m:設(shè)置元空間初始大小為 100MB。
- -XX:MaxMetaspaceSize=100m:設(shè)置元空間最大容量為 100MB,默認(rèn)不設(shè)置,則沒(méi)有限制。
- -XX:CompressedClassSpaceSize=100m:設(shè)置 Class Metaspace 的大小為 100MB,默認(rèn)值為 1G。
直接內(nèi)存(HotSpot 虛擬機(jī),JDK 1.8 之后設(shè)置有效):
-XX:MaxDirectMemorySize=100m:指定直接內(nèi)存的最大容量為 100MB。
總結(jié)
《Java虛擬機(jī)規(guī)范》中將 JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)域劃分為以下 5 部分:
- 程序計(jì)數(shù)器(Program Counter Register);
- Java 虛擬機(jī)棧(Java Virtual Machine Stacks);
- 本地方法棧(Native Method Stack);
- Java 堆(Java Heap);
- 方法區(qū)(Methed Area)。
其中線(xiàn)程私有的區(qū)域是:程序計(jì)數(shù)器、Java 虛擬機(jī)棧、本地方法棧;而線(xiàn)程共享的是:Java 堆和方法區(qū)。
而除了程序計(jì)數(shù)器,其他區(qū)域都是可以會(huì)出現(xiàn) OOM 的。