一. JVM的運(yùn)行數(shù)據(jù)區(qū)

首先我簡(jiǎn)單來(lái)畫(huà)一張 JVM的結(jié)構(gòu)原理圖,如下。

我們重點(diǎn)關(guān)注 JVM在運(yùn)行時(shí)的數(shù)據(jù)區(qū),你可以看到在程序運(yùn)行時(shí),大致有5個(gè)部分。

1.方法區(qū)

不止是存“方法”,而是存儲(chǔ)整個(gè) class文件的信息,JVM運(yùn)行時(shí),類加載器子系統(tǒng)將會(huì)提取 class文件里面的類信息,并將其存放在方法區(qū)中。例如類的名稱、類的類型(枚舉、類、接口)、字段、方法等等。

2.堆( Heap)

熟習(xí) c/c++編程的同學(xué)們應(yīng)該相當(dāng)熟習(xí) Heap了,而對(duì)于JAVA而言,每個(gè)應(yīng)用都唯一對(duì)應(yīng)一個(gè)JVM實(shí)例,而每一個(gè)JVM實(shí)例唯一對(duì)應(yīng)一個(gè)堆。堆主要包括關(guān)鍵字 new的對(duì)象實(shí)例、 this指針,或者者數(shù)組都放在堆中,并由應(yīng)用所有的線程共享。堆由JVM的自動(dòng)內(nèi)存管理機(jī)制所管理,名為垃圾回收—— GC(garbage collection)。

3.棧( Stack)

操作系統(tǒng)內(nèi)核為某個(gè)進(jìn)程或者者線程建立的存儲(chǔ)區(qū)域,它保存著一個(gè)線程中的方法的調(diào)用狀態(tài),它具備先進(jìn)后出的特性。在棧中的數(shù)據(jù)大小與生命周期嚴(yán)格來(lái)說(shuō)都是確定的,例如在一個(gè)函數(shù)中公告的int變量便是存儲(chǔ)在 stack中,它的大小是固定的,在函數(shù)退出后它的生命周期也從此結(jié)束。在棧中,每一個(gè)方法對(duì)應(yīng)一個(gè)棧幀,JVM會(huì)對(duì)Java棧執(zhí)行兩種操作:壓棧和出棧。這兩種操作在執(zhí)行時(shí)都是以棧幀為單位的。還有少量即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

4.PC寄存器

pc寄存器用于存放一條指令的地址,每一個(gè)線程都有一個(gè)PC寄存器。

5.本地方法棧

用來(lái)調(diào)用其余語(yǔ)言的本地方法,例如 C/C++寫(xiě)的本地代碼, 這些方法在本地方法棧中執(zhí)行,而不會(huì)在Java棧中執(zhí)行。

二. 數(shù)據(jù)類型

Java虛擬機(jī)中,數(shù)據(jù)類型可以分為兩類:基本類型和引用類型。

基本類型的變量保存原始值,即:他代表的值就是數(shù)值本身;而引用類型的變量保存引用值?!耙弥怠贝砹四硞€(gè)對(duì)象的引用,而不是對(duì)象本身,對(duì)象本身存放在這個(gè)引用值所表示的地址的位置?;绢愋桶ǎ篵yte,short,int,long,char,float,double,Boolean,returnAddress。引用類型包括:類類型,接口類型和數(shù)組。

三. 堆與棧

堆和棧是程序運(yùn)行的關(guān)鍵,很有必要把他們的關(guān)系說(shuō)清楚。

棧是運(yùn)行時(shí)的單位,而堆是存儲(chǔ)的單位。

棧解決程序的運(yùn)行問(wèn)題,即程序如何執(zhí)行,或者說(shuō)如何處理數(shù)據(jù);堆解決的是數(shù)據(jù)存儲(chǔ)的問(wèn)題,即數(shù)據(jù)怎么放、放在哪兒。

在Java中一個(gè)線程就會(huì)相應(yīng)有一個(gè)線程棧與之對(duì)應(yīng),這點(diǎn)很容易理解,因?yàn)椴煌木€程執(zhí)行邏輯有所不同,因此需要一個(gè)獨(dú)立的線程棧。而堆則是所有線程共享的。

棧因?yàn)槭沁\(yùn)行單位,因此里面存儲(chǔ)的信息都是跟當(dāng)前線程(或程序)相關(guān)信息的。包括局部變量、程序運(yùn)行狀態(tài)、方法返回值等等;而堆只負(fù)責(zé)存儲(chǔ)對(duì)象信息。

為什么要把堆和棧區(qū)分出來(lái)呢?棧中不是也可以存儲(chǔ)數(shù)據(jù)嗎?

第一,從軟件設(shè)計(jì)的角度看,棧代表了處理邏輯,而堆代表了數(shù)據(jù)。這樣分開(kāi),使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設(shè)計(jì)的方方面面都有體現(xiàn)。

第二,堆與棧的分離,使得堆中的內(nèi)容可以被多個(gè)棧共享(也可以理解為多個(gè)線程訪問(wèn)同一個(gè)對(duì)象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存),另一方面,堆中的共享常量和緩存可以被所有棧訪問(wèn),節(jié)省了空間。

第三,棧因?yàn)檫\(yùn)行時(shí)的需要,比如保存系統(tǒng)運(yùn)行的上下文,需要進(jìn)行地址段的劃分。由于棧只能向上增長(zhǎng),因此就會(huì)限制住棧存儲(chǔ)內(nèi)容的能力。而堆不同,堆中的對(duì)象是可以根據(jù)需要?jiǎng)討B(tài)增長(zhǎng)的,因此棧和堆的拆分,使得動(dòng)態(tài)增長(zhǎng)成為可能,相應(yīng)棧中只需記錄堆中的一個(gè)地址即可。

第四,面向?qū)ο缶褪嵌押蜅5耐昝澜Y(jié)合。其實(shí),面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒(méi)有任何區(qū)別。但是,面向?qū)ο蟮囊?,使得?duì)待問(wèn)題的思考方式發(fā)生了改變,而更接近于自然方式的思考。當(dāng)我們把對(duì)象拆開(kāi),你會(huì)發(fā)現(xiàn),對(duì)象的屬性其實(shí)就是數(shù)據(jù),存放在堆中;而對(duì)象的行為(方法),就是運(yùn)行邏輯,放在棧中。我們?cè)诰帉?xiě)對(duì)象的時(shí)候,其實(shí)即編寫(xiě)了數(shù)據(jù)結(jié)構(gòu),也編寫(xiě)的處理數(shù)據(jù)的邏輯。不得不承認(rèn),面向?qū)ο蟮脑O(shè)計(jì),確實(shí)很美。

在 Java 中,main 函數(shù)就是棧的起始點(diǎn),也是程序的起始點(diǎn)。

程序要運(yùn)行總是有一個(gè)起點(diǎn)的。同 C 語(yǔ)言一樣,java 中的 main 就是那個(gè)起點(diǎn)。無(wú)論什么 java 程序,找到 main 就找到了程序執(zhí)行的入口。

四. Java對(duì)象的大小

基本數(shù)據(jù)的類型的大小是固定的,這里就不多說(shuō)了。對(duì)于非基本類型的Java對(duì)象,其大小就值得商榷。

在Java中,一個(gè)空Object對(duì)象的大小是8byte,這個(gè)大小只是保存堆中一個(gè)沒(méi)有任何屬性的對(duì)象的大小??聪旅嬲Z(yǔ)句:

這樣在程序中完成了一個(gè)Java對(duì)象的生命,但是它所占的空間為:4byte+8byte。4byte是上面部分所說(shuō)的Java棧中保存引用的所需要的空間。而那8byte則是Java堆中對(duì)象的信息。

因?yàn)樗械腏ava非基本類型的對(duì)象都需要默認(rèn)繼承Object對(duì)象,因此不論什么樣的Java對(duì)象,其大小都必須是大于8byte。

有了Object對(duì)象的大小,我們就可以計(jì)算其他對(duì)象的大小了。

其大小為:空對(duì)象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因?yàn)镴ava在對(duì)對(duì)象內(nèi)存分配時(shí)都是以8的整數(shù)倍來(lái)分,因此大于17byte的最接近8的整數(shù)倍的是24,因此此對(duì)象的大小為24byte。

這里需要注意一下基本類型的包裝類型的大小。因?yàn)檫@種包裝類型已經(jīng)成為對(duì)象了,因此需要把他們作為對(duì)象來(lái)看待。包裝類型的大小至少是12byte(聲明一個(gè)空Object至少需要的空間),而且12byte沒(méi)有包含任何有效信息,同時(shí),因?yàn)镴ava對(duì)象大小是8的整數(shù)倍,因此一個(gè)基本類型包裝類的大小至少是16byte。

這個(gè)內(nèi)存占用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的內(nèi)存占用更是夸張(隨便想下就知道了)。因此,可能的話應(yīng)盡量少使用包裝類。在JDK5.0以后,因?yàn)榧尤肓俗詣?dòng)類型裝換,因此,Java虛擬機(jī)會(huì)在存儲(chǔ)方面進(jìn)行相應(yīng)的優(yōu)化。

五. 引用類型

對(duì)象引用類型分為強(qiáng)引用、軟引用、弱引用和虛引用。

強(qiáng)引用

:就是我們一般聲明對(duì)象是時(shí)虛擬機(jī)生成的引用,強(qiáng)引用環(huán)境下,垃圾回收時(shí)需要嚴(yán)格判斷當(dāng)前對(duì)象是否被強(qiáng)引用,如果被強(qiáng)引用,則不會(huì)被垃圾回收

軟引用

:軟引用一般被做為緩存來(lái)使用。與強(qiáng)引用的區(qū)別是,軟引用在垃圾回收時(shí),虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的剩余內(nèi)存來(lái)決定是否對(duì)軟引用進(jìn)行回收。

如果剩余內(nèi)存比較緊張,則虛擬機(jī)會(huì)回收軟引用所引用的空間;如果剩余內(nèi)存相對(duì)富裕,則不會(huì)進(jìn)行回收。

換句話說(shuō),虛擬機(jī)在發(fā)生OutOfMemory時(shí),肯定是沒(méi)有軟引用存在的。

弱引用

:弱引用與軟引用類似,都是作為緩存來(lái)使用。但與軟引用不同,弱引用在進(jìn)行垃圾回收時(shí),是一定會(huì)被回收掉的,因此其生命周期只存在于一個(gè)垃圾回收周期內(nèi)。

強(qiáng)引用不用說(shuō),我們系統(tǒng)一般在使用時(shí)都是用的強(qiáng)引用。而“軟引用”和“弱引用”比較少見(jiàn)。他們一般被作為緩存使用,而且一般是在內(nèi)存大小比較受限的情況下做為緩存。

因?yàn)槿绻麅?nèi)存足夠大的話,可以直接使用強(qiáng)引用作為緩存即可,同時(shí)可控性更高。因而,他們常見(jiàn)的是被使用在桌面應(yīng)用系統(tǒng)的緩存。

六.Java中參數(shù)傳遞時(shí)傳值還是傳引用?

要說(shuō)明這個(gè)問(wèn)題,先要明確兩點(diǎn):

1.

不要試圖與C進(jìn)行類比,Java中沒(méi)有指針的概念

2.

程序運(yùn)行永遠(yuǎn)都是在棧中進(jìn)行的,因而參數(shù)傳遞時(shí),只存在傳遞基本類型和對(duì)象引用的問(wèn)題

。不會(huì)直接傳對(duì)象本身。

明確以上兩點(diǎn)后。Java在方法調(diào)用傳遞參數(shù)時(shí),因?yàn)闆](méi)有指針,所以

它都是進(jìn)行傳值調(diào)用

(這點(diǎn)可以參考C的傳值調(diào)用)。因此,很多書(shū)里面都說(shuō)Java是進(jìn)行傳值調(diào)用,這點(diǎn)沒(méi)有問(wèn)題,而且也簡(jiǎn)化的C中復(fù)雜性。

但是傳引用的錯(cuò)覺(jué)是如何造成的呢?在運(yùn)行棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調(diào)用,也同時(shí)可以理解為“傳引用值”的傳值調(diào)用,即引用的處理跟基本類型是完全一樣的。但是當(dāng)進(jìn)入被調(diào)用方法時(shí),被傳遞的這個(gè)引用的值,被程序解釋(或者查找)到堆中的對(duì)象,這個(gè)時(shí)候才對(duì)應(yīng)到真正的對(duì)象。如果此時(shí)進(jìn)行修改,修改的是引用對(duì)應(yīng)的對(duì)象,而不是引用本身,即:修改的是堆中的數(shù)據(jù)。所以這個(gè)修改是可以保持的了。

對(duì)象,從某種意義上說(shuō),是由基本類型組成的??梢园岩粋€(gè)對(duì)象看作為一棵樹(shù),對(duì)象的屬性如果還是對(duì)象,則還是一顆樹(shù)(即非葉子節(jié)點(diǎn)),基本類型則為樹(shù)的葉子節(jié)點(diǎn)。

程序參數(shù)傳遞時(shí),被傳遞的值本身都是不能進(jìn)行修改的,但是,如果這個(gè)值是一個(gè)非葉子節(jié)點(diǎn)(即一個(gè)對(duì)象引用),則可以修改這個(gè)節(jié)點(diǎn)下面的所有內(nèi)容

堆和棧中,棧是程序運(yùn)行最根本的東西。程序運(yùn)行可以沒(méi)有堆,但是不能沒(méi)有棧。而堆是為棧進(jìn)行數(shù)據(jù)存儲(chǔ)服務(wù),說(shuō)白了堆就是一塊共享的內(nèi)存。不過(guò),正是因?yàn)槎押蜅5姆蛛x的思想,才使得Java的垃圾回收成為可能。

Java中,棧的大小通過(guò)-Xss來(lái)設(shè)置,

當(dāng)棧中存儲(chǔ)數(shù)據(jù)比較多時(shí),需要適當(dāng)調(diào)大這個(gè)值,否則會(huì)出現(xiàn)java.lang.StackOverflowError異常

。常見(jiàn)的出現(xiàn)這個(gè)異常的是無(wú)法返回的遞歸,因?yàn)榇藭r(shí)棧中保存的信息都是方法返回的記錄點(diǎn)。

七. Java虛擬機(jī)中對(duì)象的訪問(wèn)及存放

舉個(gè)實(shí)例Student stu=new Student();

這份代碼中Student stu是一個(gè)引用變量所以存放在java虛擬機(jī)棧上,new Student()是一個(gè)實(shí)例對(duì)象存放在java堆上。另外,在Java 堆中還必須包含能查找到此對(duì)象類型數(shù)據(jù)(如對(duì)象類型、父類、實(shí)現(xiàn)的接口、方法等)的地址信息,這些類型數(shù)據(jù)則存儲(chǔ)在方法區(qū)中。

由于reference 類型在Java 虛擬機(jī)規(guī)范里面只規(guī)定了一個(gè)指向?qū)ο蟮囊?,并沒(méi)有定義這個(gè)引用應(yīng)該通過(guò)哪種方式去定位,以及訪問(wèn)到Java 堆中的對(duì)象的具體位置,因此不同虛擬機(jī)實(shí)現(xiàn)的對(duì)象訪問(wèn)方式會(huì)有所不同,主流的訪問(wèn)方式有兩種:使用句柄和直接指針。如果使用句柄訪問(wèn)方式Java 堆中將會(huì)劃分出一塊內(nèi)存來(lái)作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的具體地址信息,如下圖所示。

指針?lè)绞?/p>

Java 堆對(duì)象的布局中就必須考慮如何放置訪問(wèn)類型

這兩種對(duì)象的訪問(wèn)方式各有優(yōu)勢(shì),使用句柄訪問(wèn)方式的最大好處就是reference 中存儲(chǔ)的是穩(wěn)定的句柄地址,在對(duì)象被移動(dòng)(垃圾收集時(shí)移動(dòng)對(duì)象是非常普遍的行為)時(shí)只會(huì)改變句柄中的實(shí)例數(shù)據(jù)指針,而引用對(duì)象本身不需要被修改。使用直接指針訪問(wèn)方式的最大好處就是速度更快,它節(jié)省了一次指針定位的時(shí)間開(kāi)銷(xiāo),由于對(duì)象的訪問(wèn)在Java 中非常頻繁,因此這類開(kāi)銷(xiāo)積少成多后也是一項(xiàng)非常可觀的執(zhí)行成本。

1.《jvm調(diào)優(yōu) JVM調(diào)優(yōu):基本概念》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無(wú)關(guān),侵刪請(qǐng)聯(lián)系頁(yè)腳下方聯(lián)系方式。

2.《jvm調(diào)優(yōu) JVM調(diào)優(yōu):基本概念》僅供讀者參考,本網(wǎng)站未對(duì)該內(nèi)容進(jìn)行證實(shí),對(duì)其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。

3.文章轉(zhuǎn)載時(shí)請(qǐng)保留本站內(nèi)容來(lái)源地址,http://f99ss.com/keji/347457.html