JVM內(nèi)存的那些事兒

生活賦予我們一種巨大的和無限高貴的禮品履肃,這就是青春:充滿著力量啄骇,充滿著期待志愿突委,充滿著求知和斗爭的志向组力,充滿著希望信心和青春省容。 —— 奧斯特洛夫斯基

JVM內(nèi)存簡介

Java虛擬機在執(zhí)行Java程序的過程中會把它管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途燎字,以及創(chuàng)建和銷毀的時間腥椒。Java虛擬機所管理的內(nèi)存區(qū)域包括以下幾個運行時數(shù)據(jù)區(qū)域,如下圖所示:

JVM內(nèi)存區(qū)域

程序計數(shù)器

程序計數(shù)器是一塊較小的內(nèi)存空間候衍,可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器笼蛛。分支、循環(huán)蛉鹿、跳轉(zhuǎn)滨砍、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成妖异。
??在多線程環(huán)境中惋戏,每個線程都有一個獨立的程序計數(shù)器,各線程之間的計數(shù)器互不影響他膳,獨立存儲响逢,因此程序計數(shù)器是線程私有的。
??如果線程正在執(zhí)行的是一個Java方法矩乐,計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址龄句,如果執(zhí)行的是Native方法,這個計數(shù)器則為空(Undefined)散罕。

Java虛擬機棧

Java虛擬機棧也是線程私有的分歇,它和Java線程在同一時間創(chuàng)建,它保持方法的局部變量欧漱、部分結(jié)果职抡,并參與方法的調(diào)用和返回。
??Java虛擬機棧規(guī)范允許Java棧的大小是動態(tài)是動態(tài)或者固定的误甚。在Java虛擬機棧規(guī)范中缚甩,定義了兩種異常與椘拙唬空間有關(guān):
StackOverflowError和OutOfMemoryError。如果線程請求的棧深度大于虛擬機所允許的深度擅威,將拋出StackOverflowError異常壕探,如果虛擬機動態(tài)擴展時無法申請到足夠內(nèi)存時,將拋出OutOfMemoryError異常郊丛。
??在Hotspot虛擬機中李请,可以使用-Xss參數(shù)來設(shè)置棧的大小。棧的大小直接決定了函數(shù)調(diào)用的可達深度厉熟。
??以下示例展示了棧的溢出导盅。

public class StackTest {
    private int count = 0;
    public void recursion() {
        count++;
        recursion();
    }
    @Test
    public void testStack() {
        try {
            recursion();
        } catch (Throwable e) {
            System.out.println("deep of stack is " + count);
            e.printStackTrace();
        }
    }
}

默認情況下,程序輸出結(jié)果:

deep of stack is 18904
java.lang.StackOverflowError

使用參數(shù)-Xss2M再次執(zhí)行程序揍瑟,程序輸出結(jié)果:

deep of stack is 42442
java.lang.StackOverflowError

很明顯白翻,棧的內(nèi)存增大后,程序支持的函數(shù)調(diào)用深度也同時增大绢片。

public class StackTest {
    private int count = 0;
    public void recursion(long a, long b, long c, long d) {
        long e = 0, f = 0, g = 0;
        count++;
        recursion(a, b, c, d);
    }

    @Test
    public void testStack() {
        try {
            recursion(1L, 2L, 3L, 4L);
        } catch (Throwable e) {
            System.out.println("deep of stack is " + count);
            e.printStackTrace();
        }
    }
}

同樣使用參數(shù)-Xss2M執(zhí)行程序滤馍,程序輸出結(jié)果:

deep of stack is 21055
java.lang.StackOverflowError

隨著參數(shù)和局部變量的增多,棧幀的空間也隨之增大杉畜。(函數(shù)調(diào)用次數(shù)由無參時的42442降至21055)纪蜒。

虛擬機棧在運行時使用棧幀的數(shù)據(jù)結(jié)構(gòu)保存上下文數(shù)據(jù)。在棧幀中此叠,存放了方法的局部變量表、操作數(shù)棧随珠、動態(tài)連接方法和返回地址等信息灭袁。每一個方法的調(diào)用都伴隨著棧幀的入棧操作,相應(yīng)地窗看,方法的返回則表示棧幀的出棧操作茸歧。方法調(diào)用時,方法參數(shù)和局部變量相對較多显沈,那么局部變量表會比較大软瞎,棧幀會膨脹以滿足需求,因此單個方法調(diào)用所需的椑叮空間大小也會比較多涤浇。

棧幀結(jié)構(gòu)圖如下:

棧幀結(jié)構(gòu)

注意:對一個函數(shù)而言,它的參數(shù)越多魔慷,內(nèi)部局部變量越多只锭,它的棧幀就越大,其可達深度就越低院尔。

  • 局部變量表
    ??用于存放方法參數(shù)和方法內(nèi)部定義的局部變量蜻展,其大小在代碼編譯期間已經(jīng)確定喉誊,在方法運行期間不會改變。局部變量表以變量槽(Slot)為最小存儲單位纵顾,每個Slot能夠存放一個boolean伍茄、byte、char施逾、shot幻林、int、float音念、reference和returnAddress類型的32位數(shù)據(jù)沪饺,對于64位的數(shù)據(jù)類型long和double,虛擬機會以高位對齊的方式為其分配兩個連續(xù)的Slot空間闷愤。
    ??在方法執(zhí)行時整葡,如果是實例方法,即非static方法讥脐,局部變量表中第0位Slot默認存放對象實例的引用(虛擬機通過局部變量表將當前對象傳遞給當前方法)遭居,方法中可以通過關(guān)鍵字 this 進行訪問,方法參數(shù)按照參數(shù)列表順序旬渠,從第1位Slot開始分配俱萍,方法內(nèi)部變量則按照定義順序進行分配其余的Slot。

  • 操作數(shù)棧
    ??操作數(shù)棧是一個基本的棧告丢,那么它自然也遵守棧的后入先出的原則枪蘑。其次,它里面主要存放的是一些算數(shù)運算用到的參數(shù)也可能是中間結(jié)果岖免,也可能是在調(diào)用其他方法時需要用到的參數(shù)岳颇。通過這點可以看出,方法剛剛開始執(zhí)行的時候颅湘,這個里面是空的话侧。最后 要說明的是操作數(shù)棧中可以存放任意的Java數(shù)據(jù)類型,包括long和double,且32位的數(shù)據(jù)類型占一個棿巢危空間,64位的數(shù)據(jù)類型占2個椪芭簦空間。

  • 動態(tài)連接
    ??在說明什么是動態(tài)連接之前先看看方法的大概調(diào)用過程鹿寨。首先新博,在虛擬機運行的時候,運行時常量池會保存大量的符號引用释移,這些符號引用可以看成是每個方法的間接引用叭披。如果代表棧幀A的方法想調(diào)用代表棧幀B的方法,那么這個虛擬機的方法調(diào)用指令就會以B方法的符號引用作為參數(shù),但是因為符號引用并不是直接指向代表B方法的內(nèi)存位置涩蜘,所以在調(diào)用之前還必須要將符號引用轉(zhuǎn)換為直接引用嚼贡,然后通過直接引用才可以訪問到真正的方法。這時候就有一點需要注意同诫,如果符號引用是在類加載階段或者第一次使用的時候轉(zhuǎn)化為直接應(yīng)用粤策,那么這種轉(zhuǎn)換成為靜態(tài)解析,如果是在運行期間轉(zhuǎn)換為直接引用误窖,那么這種轉(zhuǎn)換就成為動態(tài)連接叮盘。

動態(tài)連接.
  • 返回地址
    ??方法的返回分為兩種情況,一種是正常退出霹俺,退出后會根據(jù)方法的定義來決定是否要傳返回值給上層的調(diào)用者柔吼,一種是異常導(dǎo)致的方法結(jié)束,這種情況是不會傳返回值給上層的調(diào)用方法丙唧。
    ??不過無論是那種方式的方法結(jié)束愈魏,在退出當前方法時都會跳轉(zhuǎn)到當前方法被調(diào)用的位置,如果方法是正常退出的想际,則調(diào)用者的PC計數(shù)器的值就可以作為返回地址培漏,如果是因為異常退出的,則是需要通過異常處理表來確定胡本。

本地方法棧

本地方法棧和虛擬機棧類似牌柄,兩者之間的區(qū)別是本地方法棧為Native方法服務(wù)。

Java堆

Java堆是Java運行時內(nèi)存中最為重要的部分侧甫,幾乎所有的對象實例以及數(shù)組都都是在堆中分配空間的珊佣。Java堆是所有線程共享的內(nèi)存區(qū)域。
Java堆分為新生代和老年代兩部分闺骚,新生代用于存放剛剛產(chǎn)生的對象和年輕的對象彩扔,(大對象除外,直接進入老年代僻爽,因為大對象占用空間多,為了有足夠空間容納大對象贾惦,JVM不得不移動大量新生代中的年輕對象至老年代胸梆,這對GC來說是不利的,另外须板,若是由于內(nèi)存空間緊張碰镜,JVM很可能不得不將部分年輕對象提前向老年代壓縮),如果對象經(jīng)歷過N(該次數(shù)可通過參數(shù)配置习瑰,默認是15)次GC而未被回收绪颖,則會被移入老年代。
??新生代又可進一步分為eden甜奄、from space(s0)柠横、to space(s1)(默認eden:s0:s1=8:1:1窃款,該比例可配置)。eden,即對象的出生地牍氛,大部分對象剛建立時晨继,都會存放在這里。s0和s1為survivor空間搬俊,直譯為幸存者紊扬,也就是說存放在其中的對象,至少經(jīng)歷了一次垃圾回收唉擂,并得以幸存餐屎,如果在幸存區(qū)的對象到了指定年齡仍未被回收,則有機會進入老年代玩祟。

方法區(qū)

方法區(qū)(又稱永久代)和Java堆一樣腹缩,是所有線程共享的內(nèi)存區(qū)域。方法區(qū)主要保存的是類的元數(shù)據(jù)卵凑。
??方法區(qū)中最為重要的是類的類型信息庆聘、常量池、域信息勺卢、方法信息伙判。類型信息包括類的完整名稱、父類的完整名稱黑忱、類型修飾符(public/protected/private)和類型的直接接口類表宴抚。常量池包括這個類的方法、域等信息所引用的常量甫煞。域信息包括域名稱菇曲、域類型和域修飾符。方法信息包括方法名稱抚吠、返回類型常潮、方法參數(shù)、方法修飾符楷力、方法字節(jié)碼喊式、操作數(shù)棧和方法棧幀的局部變量區(qū)大小以及異常表∠舫總之岔留,方法區(qū)內(nèi)保存的信息,大部分是來自于class文件检柬。
??運行時常量池用于存放編譯期間生成的各種字面常量(文本字符串献联、聲明為final的常量值)和符號引用(類和接口的完全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符)里逆。在JDK1.6中进胯,常量池是方法區(qū)的一部分,在JDK1.7中运悲,常量池存放在堆內(nèi)存里龄减,在JDK1.8中,常量池存放在MetaSpace里班眯。
??目前1.8的HotSpot中希停,已經(jīng)將方法區(qū)移除,取而代之的是MetaSpace署隘。
??當方法區(qū)無法滿足內(nèi)存分配需求時宠能,將拋出OutOfMemoryError異常。
??在HotSpot虛擬機中磁餐,在永久區(qū)中的對象违崇,同樣也是可以被回收的。對永久區(qū)GC的回收诊霹,主要從以下兩個方面分析:一是GC對永久區(qū)的常量池的回收羞延,二是永久區(qū)對類元數(shù)據(jù)的回收。

  • 常量池的回收
    ??只要常量池中的常量沒有被任何地方引用脾还,就可以被回收伴箩。
  • 類元數(shù)據(jù)的回收
    ??所有該類的實例被回收,且裝載該類的ClassLoader被回收鄙漏。

JVM內(nèi)存分配參數(shù)

  • -Xms:設(shè)置堆的初始大小嗤谚。
  • -Xmx:設(shè)置堆的最大值。
  • -Xss:設(shè)置線程棧的大小怔蚌。
  • -XX:MinHeapFreeRatio:設(shè)置堆空間最小空閑比例巩步。當堆空間的空閑內(nèi)存小于這個值時,便會擴展堆空間桦踊。
  • -XX:MaxHeapFreeRatio:設(shè)置堆空間最大空閑比例椅野。當堆空間的空閑內(nèi)存大于這個值時,便會壓縮堆空間籍胯,得到一個較小的堆鳄橘。
  • -XX:NewSize:設(shè)置新生代大小。
  • -XX:NewRatio:設(shè)置老年代與新生代的比例芒炼,它等于老年代大小除以新生代大小。
  • -XX:SurvivorRatio:設(shè)置新生代中Eden與survivor區(qū)的比例术徊。
  • -XX:MaxPerPermSize:設(shè)置最大的持久區(qū)大小本刽。
  • -XX:PerPermSize:設(shè)置持久區(qū)的初始大小。
  • -XX:TargetSurvivorRatio:設(shè)置survivor區(qū)的可使用率。當survivor區(qū)的空間使用率達到這個值時子寓,會將對象送入老年代暗挑。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市斜友,隨后出現(xiàn)的幾起案子炸裆,更是在濱河造成了極大的恐慌,老刑警劉巖鲜屏,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烹看,死亡現(xiàn)場離奇詭異,居然都是意外死亡洛史,警方通過查閱死者的電腦和手機惯殊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來也殖,“玉大人土思,你說我怎么就攤上這事∫涫龋” “怎么了己儒?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捆毫。 經(jīng)常有香客問我闪湾,道長,這世上最難降的妖魔是什么冻璃? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任响谓,我火速辦了婚禮,結(jié)果婚禮上省艳,老公的妹妹穿的比我還像新娘娘纷。我一直安慰自己,他們只是感情好跋炕,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布赖晶。 她就那樣靜靜地躺著,像睡著了一般辐烂。 火紅的嫁衣襯著肌膚如雪遏插。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天纠修,我揣著相機與錄音胳嘲,去河邊找鬼。 笑死扣草,一個胖子當著我的面吹牛了牛,可吹牛的內(nèi)容都是我干的颜屠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼鹰祸,長吁一口氣:“原來是場噩夢啊……” “哼甫窟!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蛙婴,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤粗井,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后街图,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浇衬,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年台夺,在試婚紗的時候發(fā)現(xiàn)自己被綠了径玖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡颤介,死狀恐怖梳星,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滚朵,我是刑警寧澤冤灾,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站辕近,受9級特大地震影響韵吨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜移宅,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一归粉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漏峰,春花似錦糠悼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至靖苇,卻和暖如春席噩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贤壁。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工悼枢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人脾拆。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓萧芙,卻偏偏與公主長得像给梅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子双揪,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • JVM內(nèi)存模型Java虛擬機(Java Virtual Machine=JVM)的內(nèi)存空間分為五個部分,分別是: ...
    光劍書架上的書閱讀 2,483評論 2 26
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理包帚,因此不免有一些不準確的地方渔期,同時不同JDK版本的...
    高廣超閱讀 15,545評論 3 83
  • 從三月份找實習到現(xiàn)在,面了一些公司渴邦,掛了不少疯趟,但最終還是拿到小米、百度谋梭、阿里信峻、京東、新浪瓮床、CVTE盹舞、樂視家的研發(fā)崗...
    時芥藍閱讀 42,192評論 11 349
  • 阿里招司機,面試官問: 你知道發(fā)動機的原理嗎隘庄?你知道離合器是如何實現(xiàn)的踢步?發(fā)動機的曲柄連桿機構(gòu)是做了什么事情?電控燃...
    月光在心中閱讀 738評論 0 0
  • 文/琪琪love 有一種距離, 明明很近卻無法觸及街州。 有一種感情兼丰, 明明喜歡卻不敢表明。 有一種逃避唆缴, 因為得不到...
    琪琪love閱讀 716評論 0 2