一、JVM內(nèi)存區(qū)域劃分
大多數(shù) JVM 將內(nèi)存區(qū)域劃分為 Method Area(Non-Heap),Heap,Program Counter Register, Java Method Stack,Native Method Stack和Direct Memomry(注意 Directory Memory 并不屬于 JVM 管理的內(nèi)存區(qū)域)。前三者一般譯為:方法區(qū)、堆、程序計(jì)數(shù)器。但不同的資料和書(shū)籍上對(duì)于后三者的中文譯名不盡相同,這里將它們分別譯作:Java 方法棧、原生方法棧和直接內(nèi)存區(qū)。對(duì)于不同的 JVM,內(nèi)存區(qū)域劃分可能會(huì)有所差異,比如 Hot Spot 就將 Java 方法棧和原生方法棧合二為一,我們可以統(tǒng)稱(chēng)為方法棧(Method Stack)。
首先我們熟悉一下一個(gè)一般性的 Java 程序的工作過(guò)程。一個(gè) Java 源程序文件,會(huì)被編譯為字節(jié)碼文件(以 class 為擴(kuò)展名),然后告知 JVM 程序的運(yùn)行入口,再被 JVM 通過(guò)字節(jié)碼解釋器加載運(yùn)行。那么程序開(kāi)始運(yùn)行后,都是如何涉及到各內(nèi)存區(qū)域的呢?
概括地說(shuō)來(lái),JVM 每遇到一個(gè)線程,就為其分配一個(gè)程序計(jì)數(shù)器、Java 方法棧和原生方法棧。當(dāng)線程終止時(shí),兩者所占用的內(nèi)存空間也會(huì)被釋放掉。棧中存儲(chǔ)的是棧幀,可以說(shuō)每個(gè)棧幀對(duì)應(yīng)一個(gè)“運(yùn)行現(xiàn)場(chǎng)”。在每個(gè)“運(yùn)行現(xiàn)場(chǎng)”中,如果出現(xiàn)了一個(gè)局部對(duì)象,則它的實(shí)例數(shù)據(jù)被保存在堆中,而類(lèi)數(shù)據(jù)被保存在方法區(qū)。
二、指令、方法與屬性
在講各部分之前,我們首先要搞清楚的是什么是數(shù)據(jù)以及什么是指令。然后要搞清楚對(duì)象的方法和對(duì)象的屬性分別保存在哪里。
1)方法本身是指令的操作碼部分,保存在Stack中;
2)方法內(nèi)部變量作為指令的操作數(shù)部分,跟在指令的操作碼之后,保存在Stack中(實(shí)際上是簡(jiǎn)單類(lèi)型保存在Stack中,對(duì)象類(lèi)型在Stack中保存地址,在Heap 中保存值);上述的指令操作碼和指令操作數(shù)構(gòu)成了完整的Java 指令。
3)對(duì)象實(shí)例包括其屬性值作為數(shù)據(jù),保存在數(shù)據(jù)區(qū)Heap 中。
非靜態(tài)的對(duì)象屬性作為對(duì)象實(shí)例的一部分保存在Heap 中,而對(duì)象實(shí)例必須通過(guò)Stack中保存的地址指針才能訪問(wèn)到。因此能否訪問(wèn)到對(duì)象實(shí)例以及它的非靜態(tài)屬性值完全取決于能否獲得對(duì)象實(shí)例在Stack中的地址指針。
非靜態(tài)方法和靜態(tài)方法的區(qū)別:
非靜態(tài)方法有一個(gè)和靜態(tài)方法很重大的不同:非靜態(tài)方法有一個(gè)隱含的傳入?yún)?shù),該參數(shù)是JVM給它的,和我們?cè)趺磳?xiě)代碼無(wú)關(guān),這個(gè)隱含的參數(shù)就是對(duì)象實(shí)例在Stack中的地址指針。因此非靜態(tài)方法(在Stack中的指令代碼)總是可以找到自己的專(zhuān)用數(shù)據(jù)(在Heap 中的對(duì)象屬性值)。當(dāng)然非靜態(tài)方法也必須獲得該隱含參數(shù),因此非靜態(tài)方法在調(diào)用前,必須先new一個(gè)對(duì)象實(shí)例,獲得Stack中的地址指針,否則JVM將無(wú)法將隱含參數(shù)傳給非靜態(tài)方法。
靜態(tài)方法無(wú)此隱含參數(shù),因此也不需要new對(duì)象,只要class文件被ClassLoader load進(jìn)入JVM的Stack,該靜態(tài)方法即可被調(diào)用。當(dāng)然此時(shí)靜態(tài)方法是存取不到Heap 中的對(duì)象屬性的。
總結(jié)一下該過(guò)程:當(dāng)一個(gè)class文件被ClassLoader load進(jìn)入JVM后,方法指令保存在Stack中,此時(shí)Heap 區(qū)沒(méi)有數(shù)據(jù)。然后程序技術(shù)器開(kāi)始執(zhí)行指令,如果是靜態(tài)方法,直接依次執(zhí)行指令代碼,當(dāng)然此時(shí)指令代碼是不能訪問(wèn)Heap 數(shù)據(jù)區(qū)的;如果是非靜態(tài)方法,由于隱含參數(shù)沒(méi)有值,會(huì)報(bào)錯(cuò)。因此在非靜態(tài)方法執(zhí)行前,要先new對(duì)象,在Heap 中分配數(shù)據(jù),并把Stack中的地址指針交給非靜態(tài)方法,這樣程序技術(shù)器依次執(zhí)行指令,而指令代碼此時(shí)能夠訪問(wèn)到Heap 數(shù)據(jù)區(qū)了。
靜態(tài)屬性和動(dòng)態(tài)屬性:
前面提到對(duì)象實(shí)例以及動(dòng)態(tài)屬性都是保存在Heap 中的,而Heap 必須通過(guò)Stack中的地址指針才能夠被指令(類(lèi)的方法)訪問(wèn)到。因此可以推斷出:靜態(tài)屬性是保存在Stack中的,而不同于動(dòng)態(tài)屬性保存在Heap 中。正因?yàn)槎际窃赟tack中,而Stack中指令和數(shù)據(jù)都是定長(zhǎng)的,因此很容易算出偏移量,也因此不管什么指令(類(lèi)的方法),都可以訪問(wèn)到類(lèi)的靜態(tài)屬性。也正因?yàn)殪o態(tài)屬性被保存在Stack中,所以具有了全局屬性。
在JVM中,靜態(tài)屬性保存在Stack指令內(nèi)存區(qū),動(dòng)態(tài)屬性保存在Heap數(shù)據(jù)內(nèi)存區(qū)。
三、Stack 棧
Stack(棧)是JVM的內(nèi)存指令區(qū)。Stack管理很簡(jiǎn)單,push一定長(zhǎng)度字節(jié)的數(shù)據(jù)或者指令,Stack指針壓棧相應(yīng)的字節(jié)位移;pop一定字節(jié)長(zhǎng)度數(shù)據(jù)或者指令,Stack指針彈棧。Stack的速度很快,管理很簡(jiǎn)單,并且每次操作的數(shù)據(jù)或者指令字節(jié)長(zhǎng)度是已知的。所以Java 基本數(shù)據(jù)類(lèi)型,Java 指令代碼,常量都保存在Stack中。
棧也叫棧內(nèi)存,是 Java 程序的運(yùn)行區(qū),是在線程創(chuàng)建時(shí)創(chuàng)建,它的生命期是跟隨線程的生命
期,線程結(jié)束棧內(nèi)存也就釋放,對(duì)于棧來(lái)說(shuō)不存在垃圾回收問(wèn)題,只要線程一結(jié)束,該棧就 Over。
那么棧中存的是那些數(shù)據(jù)呢?又什么是格式呢?
棧中的數(shù)據(jù)都是以棧幀(Stack Frame)的格式存在,棧幀是一個(gè)內(nèi)存區(qū)塊,是一個(gè)數(shù)據(jù)集,是
一個(gè)有關(guān)方法(Method)和運(yùn)行期數(shù)據(jù)的數(shù)據(jù)集,當(dāng)一個(gè)方法 A 被調(diào)用時(shí)就產(chǎn)生了一個(gè)棧幀 F1,并
被壓入到棧中,A 方法又調(diào)用了 B 方法,于是產(chǎn)生棧幀 F2 也被壓入棧,執(zhí)行完畢后,先彈出 F2
棧幀,再?gòu)棾?F1 棧幀,遵循“先進(jìn)后出”原則。
那棧幀中到底存在著什么數(shù)據(jù)呢?棧幀中主要保存 3 類(lèi)數(shù)據(jù):本地變量(Local Variables),
包括輸入?yún)?shù)和輸出參數(shù)以及方法內(nèi)的變量;棧操作(Operand Stack),記錄出棧、入棧的操作;
棧幀數(shù)據(jù)(Frame Data),包括類(lèi)文件、方法等等。光說(shuō)比較枯燥,我們畫(huà)個(gè)圖來(lái)理解一下 Java
棧,如下圖所示:
四、Heap 堆
Heap(堆)是JVM的內(nèi)存數(shù)據(jù)區(qū)。Heap 的管理很復(fù)雜,每次分配不定長(zhǎng)的內(nèi)存空間,專(zhuān)門(mén)用來(lái)保存對(duì)象的實(shí)例。在Heap 中分配一定的內(nèi)存來(lái)保存對(duì)象實(shí)例,實(shí)際上也只是保存對(duì)象實(shí)例的屬性值,屬性的類(lèi)型和對(duì)象本身的類(lèi)型標(biāo)記等,并不保存對(duì)象的方法(方法是指令,保存在Stack中),在Heap 中分配一定的內(nèi)存保存對(duì)象實(shí)例和對(duì)象的序列化比較類(lèi)似。而對(duì)象實(shí)例在Heap 中分配好以后,需要在Stack中保存一個(gè)4字節(jié)的Heap 內(nèi)存地址,用來(lái)定位該對(duì)象實(shí)例在Heap 中的位置,便于找到該對(duì)象實(shí)例。
Java中堆是由所有的線程共享的一塊內(nèi)存區(qū)域。
4.1 Generation
JVM堆一般又可以分為以下三部分:
◆Perm
Perm代主要保存class,method,filed對(duì)象,這部門(mén)的空間一般不會(huì)溢出,除非一次性加載了很多的類(lèi),不過(guò)在涉及到熱部署的應(yīng)用服務(wù)器的時(shí)候,有時(shí)候會(huì)遇到j(luò)ava.lang.OutOfMemoryError : PermGen space 的錯(cuò)誤,造成這個(gè)錯(cuò)誤的很大原因就有可能是每次都重新部署,但是重新部署后,類(lèi)的class沒(méi)有被卸載掉,這樣就造成了大量的class對(duì)象保存在了perm中,這種情況下,一般重新啟動(dòng)應(yīng)用服務(wù)器可以解決問(wèn)題。
◆Tenured
Tenured區(qū)主要保存生命周期長(zhǎng)的對(duì)象,一般是一些老的對(duì)象,當(dāng)一些對(duì)象在Young復(fù)制轉(zhuǎn)移一定的次數(shù)以后,對(duì)象就會(huì)被轉(zhuǎn)移到Tenured區(qū),一般如果系統(tǒng)中用了application級(jí)別的緩存,緩存中的對(duì)象往往會(huì)被轉(zhuǎn)移到這一區(qū)間。
◆Young
Young區(qū)被劃分為三部分,Eden區(qū)和兩個(gè)大小嚴(yán)格相同的Survivor區(qū),其中Survivor區(qū)間中,某一時(shí)刻只有其中一個(gè)是被使用的,另外一個(gè)留做垃圾收集時(shí)復(fù)制對(duì)象用,在Young區(qū)間變滿(mǎn)的時(shí)候,minor GC就會(huì)將存活的對(duì)象移到空閑的Survivor區(qū)間中,根據(jù)JVM的策略,在經(jīng)過(guò)幾次垃圾收集后,任然存活于Survivor的對(duì)象將被移動(dòng)到Tenured區(qū)間。
4.2 Sizing the Generations
JVM提供了相應(yīng)的參數(shù)來(lái)對(duì)內(nèi)存大小進(jìn)行配置。正如上面描述,JVM中堆被分為了3個(gè)大的區(qū)間,同時(shí)JVM也提供了一些選項(xiàng)對(duì)Young,Tenured的大小進(jìn)行控制。
◆Total Heap
-Xms :指定了JVM初始啟動(dòng)以后初始化內(nèi)存
-Xmx:指定JVM堆得最大內(nèi)存,在JVM啟動(dòng)以后,會(huì)分配-Xmx參數(shù)指定大小的內(nèi)存給JVM,但是不一定全部使用,JVM會(huì)根據(jù)-Xms參數(shù)來(lái)調(diào)節(jié)真正用于JVM的內(nèi)存
-Xmx -Xms之差就是三個(gè)Virtual空間的大小
◆Young Generation
-XX:NewRatio=8意味著tenured 和 young的比值8:1,這樣eden+2*survivor=1/9
堆內(nèi)存
-XX:SurvivorRatio=32意味著eden和一個(gè)survivor的比值是32:1,這樣一個(gè)Survivor就占Young區(qū)的1/34.
-Xmn 參數(shù)設(shè)置了年輕代的大小
◆Perm Generation
-XX:PermSize=16M -XX:MaxPermSize=64M
Thread Stack
-XX:Xss=128K
五、The pc Register 程序計(jì)數(shù)器寄存器
JVM支持多個(gè)線程同時(shí)運(yùn)行。每個(gè)JVM都有自己的程序計(jì)數(shù)器。在任何一個(gè)點(diǎn),每個(gè)JVM線程執(zhí)行單個(gè)方法的代碼,這個(gè)方法是線程的當(dāng)前方法。如果方法不是native的,程序計(jì)數(shù)器寄存器包含了當(dāng)前執(zhí)行的JVM指令的地址,如果方法是 native的,程序計(jì)數(shù)器寄存器的值不會(huì)被定義。 JVM的程序計(jì)數(shù)器寄存器的寬度足夠保證可以持有一個(gè)返回地址或者native的指針。
六、Method Area 方法區(qū)
Object Class Data(類(lèi)定義數(shù)據(jù)) 是存儲(chǔ)在方法區(qū)的。除此之外,常量、靜態(tài)變量、JIT 編譯后的代碼也都在方法區(qū)。正因?yàn)榉椒▍^(qū)所存儲(chǔ)的數(shù)據(jù)與堆有一種類(lèi)比關(guān)系,所以它還被稱(chēng)為 Non-Heap。方法區(qū)也可以是內(nèi)存不連續(xù)的區(qū)域組成的,并且可設(shè)置為固定大小,也可以設(shè)置為可擴(kuò)展的,這點(diǎn)與堆一樣。
方法區(qū)內(nèi)部有一個(gè)非常重要的區(qū)域,叫做運(yùn)行時(shí)常量池(Runtime Constant Pool,簡(jiǎn)稱(chēng) RCP)。在字節(jié)碼文件中有常量池(Constant Pool Table),用于存儲(chǔ)編譯器產(chǎn)生的字面量和符號(hào)引用。每個(gè)字節(jié)碼文件中的常量池在類(lèi)被加載后,都會(huì)存儲(chǔ)到方法區(qū)中。值得注意的是,運(yùn)行時(shí)產(chǎn)生的新常量也可以被放入常量池中,比如 String 類(lèi)中的 intern() 方法產(chǎn)生的常量。
6.1 常量池 (constant pool)
常量池指的是在編譯期被確定,并被保存在已編譯的.class文件中的一些數(shù)據(jù)。除了包含代碼中所定義的各種基本類(lèi)型(如int、long等等)和對(duì)象型(如String及數(shù)組)的常量值(final)還包含一些以文本形式出現(xiàn)的符號(hào)引用,比如:
◆類(lèi)和接口的全限定名;
◆字段的名稱(chēng)和描述符;
◆方法和名稱(chēng)和描述符。
虛擬機(jī)必須為每個(gè)被裝載的類(lèi)型維護(hù)一個(gè)常量池。常量池就是該類(lèi)型所用到常量的一個(gè)有序集和,包括直接常量(string,integer和 floating point常量)和對(duì)其他類(lèi)型,字段和方法的符號(hào)引用。
對(duì)于String常量,它的值是在常量池中的。而JVM中的常量池在內(nèi)存當(dāng)中是以表的形式存在的, 對(duì)于String類(lèi)型,有一張固定長(zhǎng)度的CONSTANT_String_info表用來(lái)存儲(chǔ)文字字符串值,注意:該表只存儲(chǔ)文字字符串值,不存儲(chǔ)符號(hào)引 用。說(shuō)到這里,對(duì)常量池中的字符串值的存儲(chǔ)位置應(yīng)該有一個(gè)比較明了的理解了。
在程序執(zhí)行的時(shí)候,常量池 會(huì)儲(chǔ)存在Method Area,而不是堆中。
七、Java Method Stack Java 方法棧 與 Native Method Stack 原生方法棧
第七章內(nèi)容來(lái)源:http://blog.csdn.net/poechant/article/details/7289093
Java 方法棧也是線程私有的,每個(gè) Java 方法棧都是由一個(gè)個(gè)棧幀組成的,每個(gè)棧幀是一個(gè)方法運(yùn)行期的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),它存儲(chǔ)著局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。當(dāng)線程調(diào)用調(diào)用了一個(gè) Java 方法時(shí),一個(gè)棧幀就被壓入(push)到相應(yīng)的 Java 方法棧。當(dāng)線程從一個(gè) Java 方法返回時(shí),相應(yīng)的 Java 方法棧就彈出(pop)一個(gè)棧幀。
其中要詳細(xì)介紹的是局部變量表,它保存者各種基本數(shù)據(jù)類(lèi)型和對(duì)象引用(Object reference)。基本數(shù)據(jù)類(lèi)型包括 boolean、byte、char、short、int、long、float、double。對(duì)象引用,本質(zhì)就是一個(gè)地址(也可以說(shuō)是一個(gè)“指針”),該地址是堆中的一個(gè)地址,通過(guò)這個(gè)地址可以找到相應(yīng)的 Object(注意是“找到”,原因會(huì)在下面解釋?zhuān)6@個(gè)地址找到相應(yīng) Object 的方式有兩種。一種是該地址存儲(chǔ)著 Pointer to Object Instance Data 和 Pointer to Object Class Data,另一種是該地址存儲(chǔ)著 Object Instance Data,其中又包含有 Pointer to Object Class Data。如下兩圖所示。
第一種方式,Java 方法棧中有 Handler Pool 和 Instance Pool。無(wú)論哪種方式,Object Class Data 都是存儲(chǔ)在方法區(qū)的,Object Instance Data 都是存儲(chǔ)在堆中的。

圖1 句柄方式

圖2 直接方式
原生方法棧與 Java 方法棧相類(lèi)似,這里不再贅述。
八、JVM運(yùn)行原理 例子
以上都是純理論,我們舉個(gè)例子來(lái)說(shuō)明 JVM 的運(yùn)行原理,我們來(lái)寫(xiě)一個(gè)簡(jiǎn)單的類(lèi),代碼如下:
- public class JVMShowcase {
- //靜態(tài)類(lèi)常量,
- public final static String ClASS_CONST = "I'm a Const";
- //私有實(shí)例變量
- private int instanceVar=15;
- public static void main(String[] args) {
- //調(diào)用靜態(tài)方法
- runStaticMethod();
- //調(diào)用非靜態(tài)方法
- JVMShowcase showcase=new JVMShowcase();
- showcase.runNonStaticMethod(100);
- }
- //常規(guī)靜態(tài)方法
- public static String runStaticMethod(){
- return ClASS_CONST;
- }
- //非靜態(tài)方法
- public int runNonStaticMethod(int parameter){
- int methodVar=this.instanceVar * parameter;
- return methodVar;
- }
- }
這個(gè)類(lèi)沒(méi)有任何意義,不用猜測(cè)這個(gè)類(lèi)是做什么用,只是寫(xiě)一個(gè)比較典型的類(lèi),然后我們來(lái)看
看 JVM 是如何運(yùn)行的,也就是輸入 java JVMShow 后,我們來(lái)看 JVM 是如何處理的:
向操作系統(tǒng)申請(qǐng)空閑內(nèi)存。JVM 對(duì)操作系統(tǒng)說(shuō)“給我 64M 空閑內(nèi)存”,于是第 1 步,JVM 向操作系統(tǒng)申請(qǐng)空閑內(nèi)存
作系統(tǒng)就查找自己的內(nèi)存分配表,找了段 64M 的內(nèi)存寫(xiě)上“Java 占用”標(biāo)簽,然后把內(nèi)存段的起始地址和終止地址給 JVM,JVM 準(zhǔn)備加載類(lèi)文件。
分配內(nèi)存內(nèi)存。第 2 步,JVM 分配內(nèi)存。JVM 獲得到 64M 內(nèi)存,就開(kāi)始得瑟了,首先給 heap 分個(gè)內(nèi)存,并
且是按照 heap 的三種不同類(lèi)型分好的,然后給棧內(nèi)存也分配好。
文件。第 3 步,檢查和分析 class 文件。若發(fā)現(xiàn)有錯(cuò)誤即返回錯(cuò)誤。
加載類(lèi)。第 4 步,加載類(lèi)。由于沒(méi)有指定加載器,JVM 默認(rèn)使用 bootstrap 加載器,就把 rt.jar 下的所有
類(lèi)都加載到了堆類(lèi)存的永久存儲(chǔ)區(qū),JVMShow 也被加載到內(nèi)存中。我們來(lái)看看棧內(nèi)存,如下圖:
Heap 是空,Stack 是空,因?yàn)檫€沒(méi)有線程被執(zhí)行。Class Loader 通知 Execution Enginer 已經(jīng)加
載完畢。
執(zhí)行引擎執(zhí)行方法。第 5 步,執(zhí)行引擎執(zhí)行 main 方法。執(zhí)行引擎啟動(dòng)一個(gè)線程,開(kāi)始執(zhí)行 main 方法,在 main 執(zhí)
行完畢前,方法區(qū)如下圖所示:
在 Method Area 加入了 CLASS_CONST 常量,它是在第一次被訪問(wèn)時(shí)產(chǎn)生的。堆內(nèi)存中有兩個(gè)對(duì)象 object 和 showcase 對(duì)象,如下圖所示:
為什么會(huì)有 Object 對(duì)象呢?是因?yàn)樗?JVMShowcase 的父類(lèi),JVM 是先初始化父類(lèi),然后再
初始化子類(lèi),甭管有多少個(gè)父類(lèi)都初始化。在棧內(nèi)存中有三個(gè)棧幀,如下圖所示:
于此同時(shí),還創(chuàng)建了一個(gè)程序計(jì)數(shù)器指向下一條要執(zhí)行的語(yǔ)句。
釋放內(nèi)存。運(yùn)第 6 步,釋放內(nèi)存。運(yùn)行結(jié)束,JVM 向操作系統(tǒng)發(fā)送消息,說(shuō)“內(nèi)存用完了,我還給你”
行結(jié)束。
九、JVM 相關(guān)問(wèn)題
問(wèn):堆和棧有什么區(qū)別堆和棧有什么區(qū)別有什么
答:堆是存放對(duì)象的,但是對(duì)象內(nèi)的臨時(shí)變量是存在棧內(nèi)存中,如例子中的 methodVar 是在運(yùn)
行期存放到棧中的。
棧是跟隨線程的,有線程就有棧,堆是跟隨 JVM 的,有 JVM 就有堆內(nèi)存。
問(wèn):堆內(nèi)存中到底存在著什么東西?堆內(nèi)存中到底存在著什么東西?
答:對(duì)象,包括對(duì)象變量以及對(duì)象方法。
問(wèn):類(lèi)變量和實(shí)例變量有什么區(qū)別?類(lèi)變量和實(shí)例變量有什么區(qū)別?有什么區(qū)別
答:靜態(tài)變量是類(lèi)變量,非靜態(tài)變量是實(shí)例變量,直白的說(shuō),有 static 修飾的變量是靜態(tài)變量,
沒(méi)有 static 修飾的變量是實(shí)例變量。靜態(tài)變量存在方法區(qū)中,實(shí)例變量存在堆內(nèi)存中。
啟動(dòng)時(shí)就初始化好的,和你這說(shuō)的不同呀!
問(wèn):我聽(tīng)說(shuō)類(lèi)變量是在 JVM 啟動(dòng)時(shí)就初始化好的,和你這說(shuō)的不同呀!
答:那你是道聽(tīng)途說(shuō),信我的,沒(méi)錯(cuò)。
的方法(函數(shù))到底是傳值還是傳址值還是傳址?
問(wèn):Java 的方法(函數(shù))到底是傳值還是傳址?
答:都不是,是以傳值的方式傳遞地址,具體的說(shuō)原生數(shù)據(jù)類(lèi)型傳遞的值,引用類(lèi)型傳遞的地
址。對(duì)于原始數(shù)據(jù)類(lèi)型,JVM 的處理方法是從 Method Area 或 Heap 中拷貝到 Stack,然后運(yùn)行 frame
中的方法,運(yùn)行完畢后再把變量指拷貝回去。
產(chǎn)生?
問(wèn):為什么會(huì)產(chǎn)生 OutOfMemory 產(chǎn)生?
答:一句話(huà):Heap 內(nèi)存中沒(méi)有足夠的可用內(nèi)存了。這句話(huà)要好好理解,不是說(shuō) Heap 沒(méi)有內(nèi)存
了,是說(shuō)新申請(qǐng)內(nèi)存的對(duì)象大于 Heap 空閑內(nèi)存,比如現(xiàn)在 Heap 還空閑 1M,但是新申請(qǐng)的內(nèi)存需
要 1.1M,于是就會(huì)報(bào) OutOfMemory 了,可能以后的對(duì)象申請(qǐng)的內(nèi)存都只要 0.9M,于是就只出現(xiàn)
一次 OutOfMemory,GC 也正常了,看起來(lái)像偶發(fā)事件,就是這么回事。 但如果此時(shí) GC 沒(méi)有回
收就會(huì)產(chǎn)生掛起情況,系統(tǒng)不響應(yīng)了。
問(wèn):我產(chǎn)生的對(duì)象不多呀,為什么還會(huì)產(chǎn)生 OutOfMemory?我產(chǎn)生的對(duì)象不多呀,?
答:你繼承層次忒多了,Heap 中 產(chǎn)生的對(duì)象是先產(chǎn)生 父類(lèi),然后才產(chǎn)生子類(lèi),明白不?
錯(cuò)誤分幾種?問(wèn):OutOfMemory 錯(cuò)誤分幾種?
答:分兩種,分別是“OutOfMemoryError:java heap size”和”OutOfMemoryError: PermGen
space”,兩種都是內(nèi)存溢出,heap size 是說(shuō)申請(qǐng)不到新的內(nèi)存了,這個(gè)很常見(jiàn),檢查應(yīng)用或調(diào)整
堆內(nèi)存大小。
“PermGen space”是因?yàn)橛谰么鎯?chǔ)區(qū)滿(mǎn)了,這個(gè)也很常見(jiàn),一般在熱發(fā)布的環(huán)境中出現(xiàn),是
因?yàn)槊看伟l(fā)布應(yīng)用系統(tǒng)都不重啟,久而久之永久存儲(chǔ)區(qū)中的死對(duì)象太多導(dǎo)致新對(duì)象無(wú)法申請(qǐng)內(nèi)存,
一般重新啟動(dòng)一下即可。
問(wèn):為什么會(huì)產(chǎn)生 StackOverflowError??
答:因?yàn)橐粋€(gè)線程把 Stack 內(nèi)存全部耗盡了,一般是遞歸函數(shù)造成的。
之間可以互訪嗎?
問(wèn):一個(gè)機(jī)器上可以看多個(gè) JVM 嗎?JVM 之間可以互訪嗎?
答:可以多個(gè) JVM,只要機(jī)器承受得了。JVM 之間是不可以互訪,你不能在 A-JVM 中訪問(wèn)
B-JVM 的 Heap 內(nèi)存,這是不可能的。在以前老版本的 JVM 中,會(huì)出現(xiàn) A-JVM Crack 后影響到
B-JVM,現(xiàn)在版本非常少見(jiàn)。
要采用垃圾回收機(jī)制,的顯式
問(wèn):為什么 Java 要采用垃圾回收機(jī)制,而不采用 C/C++的顯式內(nèi)存管理?的顯 內(nèi)存管理?
答:為了簡(jiǎn)單,內(nèi)存管理不是每個(gè)程序員都能折騰好的。
問(wèn):為什么你沒(méi)有詳細(xì)介紹垃圾回收機(jī)制?為什么你沒(méi)有詳細(xì)介紹垃圾回收機(jī)制
答:垃圾回收機(jī)制每個(gè) JVM 都不同,JVM Specification 只是定義了要自動(dòng)釋放內(nèi)存,也就是
說(shuō)它只定義了垃圾回收的抽象方法,具體怎么實(shí)現(xiàn)各個(gè)廠商都不同,算法各異,這東西實(shí)在沒(méi)必要
深入。
中到底哪些區(qū)域是共享的?哪些是私有的?
問(wèn):JVM 中到底哪些區(qū)域是共享的?哪些是私有的?
答:Heap 和 Method Area 是共享的,其他都是私有的,
問(wèn):什么是 JIT,你怎么沒(méi)說(shuō)?,你怎么沒(méi)說(shuō)?
答:JIT 是指 Just In Time,有的文檔把 JIT 作為 JVM 的一個(gè)部件來(lái)介紹,有的是作為執(zhí)行引
擎的一部分來(lái)介紹,這都能理解。Java 剛誕生的時(shí)候是一個(gè)解釋性語(yǔ)言,別噓,即使編譯成了字
節(jié)碼(byte code)也是針對(duì) JVM 的,它需要再次翻譯成原生代碼(native code)才能被機(jī)器執(zhí)行,于
是效率的擔(dān)憂(yōu)就提出來(lái)了。Sun 為了解決該問(wèn)題提出了一套新的機(jī)制,好,你想編譯成原生代碼,
沒(méi)問(wèn)題,我在 JVM 上提供一個(gè)工具,把字節(jié)碼編譯成原生碼,下次你來(lái)訪問(wèn)的時(shí)候直接訪問(wèn)原生
碼就成了,于是 JIT 就誕生了,就這么回事。
還有哪些部分是你沒(méi)有提到的?
問(wèn):JVM 還有哪些部分是你沒(méi)有提到的?
答:JVM 是一個(gè)異常復(fù)雜的東西,寫(xiě)一本磚頭書(shū)都不為過(guò),還有幾個(gè)要說(shuō)明的:
常量池(constant pool)按照順序存放程序中的常量,:并且進(jìn)行索引編號(hào)的區(qū)域。比如 int i =100,
這個(gè) 100 就放在常量池中。
安全管理器(Security Manager):提供 Java 運(yùn)行期的安全控制,防止惡意攻擊,比如指定讀取
文件,寫(xiě)入文件權(quán)限,網(wǎng)絡(luò)訪問(wèn),創(chuàng)建進(jìn)程等等,Class Loader 在 Security Manager 認(rèn)證通過(guò)后才
能加載 class 文件的。
方法索引表(Methods table),記錄的是每個(gè) method 的地址信息,Stack 和 Heap 中的地址指針
其實(shí)是指向 Methods table 地址。
問(wèn):為什么不建議在程序中顯式的生命 System.gc()??
答:因?yàn)轱@式聲明是做堆內(nèi)存全掃描,也就是 Full GC,是需要停止所有的活動(dòng)的(Stop The
World Collection),你的應(yīng)用能承受這個(gè)嗎?
問(wèn):JVM 有哪些調(diào)整參數(shù)?
答:非常多,自己去找,堆內(nèi)存、棧內(nèi)存的大小都可以定義,甚至是堆內(nèi)存的三個(gè)部分、新生
代的各個(gè)比例都能調(diào)整。