JVM 內(nèi)存模型與GC機(jī)制

摘要:最近在壓測的時(shí)候遇到了OutOfMemoryError錯誤,發(fā)現(xiàn)是jvm內(nèi)存超限今瀑,雖然過程是數(shù)據(jù)庫瓶頸導(dǎo)致線程阻塞录煤,垃圾回收不及時(shí)所導(dǎo)致罩息,但當(dāng)時(shí)解決問題的時(shí)候還是采用了一個(gè)治標(biāo)不治本的法子:使用-Xms -Xmx調(diào)整jvm堆查占用內(nèi)存,后面發(fā)現(xiàn)沒解決根本為題茫经,無論將 -Xmx調(diào)整到多大巷波,只是增大緩存,最終還是會被塞滿卸伞,報(bào)OutOfMemoryError錯誤抹镊。

因此,在解決該問題后有特意了解了下jvm的內(nèi)存結(jié)構(gòu)和回收機(jī)制

一荤傲、jvm內(nèi)存模型

  • 程序計(jì)數(shù)器
  • java虛擬機(jī)棧
  • 本地方法棧
  • java堆
  • 方法區(qū)
  • 運(yùn)行時(shí)常量池
1垮耳、程序計(jì)數(shù)器

其實(shí)在了解程序計(jì)數(shù)器之前多線程執(zhí)行連貫性問題對我有些困擾,總不理解為啥多線程各個(gè)指令遂黍、數(shù)據(jù)不會竄線终佛。
程序計(jì)數(shù)器,是一塊較小的內(nèi)存空間雾家,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器铃彰,運(yùn)行的程序已經(jīng)是字節(jié)碼了,而字節(jié)碼解釋器工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令
每一個(gè)線程都有獨(dú)立的程序計(jì)數(shù)器芯咧,屬于每個(gè)線程的"私有內(nèi)存"牙捉,以此也可以來確保線程命令不會"竄線"竹揍。

2、java虛擬機(jī)棧

簡單理解鹃共,就是java的方法鬼佣,每個(gè)方法被執(zhí)行的時(shí)候都會同時(shí)創(chuàng)建一個(gè)棧幀(Stack Frame ①)用于存儲局部變量表、操作棧霜浴、動態(tài)鏈接晶衷、方法出口等信息。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程阴孟,就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程晌纫。
虛擬機(jī)棧內(nèi)存了方法還包括里面包含的局部變量,如數(shù)據(jù)基本類型(int ,char....),也包方法中所含對象的地址(注意永丝,此處是存對象地址锹漱,對象本身存在java堆中)。
當(dāng)方法出棧時(shí)該部分內(nèi)容會被釋放慕嚷。

3哥牍、本地方法棧

這個(gè)概念我也沒太明白,目前理解就是和java虛擬機(jī)棧一樣喝检,有點(diǎn)區(qū)別就是使用native修飾的方法會放到這個(gè)棧中嗅辣,望大神指教,指正挠说。

4澡谭、java堆

java堆是占用java虛擬機(jī)最大空間的一塊內(nèi)存,可以粗暴點(diǎn)理解就是存對象的地方损俭,Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域蛙奖,在虛擬機(jī)啟動時(shí)創(chuàng)建。此內(nèi)存區(qū)域的
唯一目的就是存放對象實(shí)例杆兵,幾乎所有的對象實(shí)例都在這里分配內(nèi)存雁仲。套用下Java 虛擬機(jī)規(guī)范:所有的對象實(shí)例以及數(shù)組都要在堆上分配。但也并非絕對(做判斷題要注意)琐脏。
這塊內(nèi)存分配就是我上文中所說使用-Xms和-Xmx來調(diào)節(jié)和實(shí)現(xiàn)拓展的伯顶。-Xms是最小占用空間,-Xmx是最大占用空間骆膝,可浮動祭衩。-Xmx默認(rèn)占用物理內(nèi)存的1/4,到如兩者相等則為固定值阅签。

5掐暮、方法區(qū)

包括常量、類變量政钟、靜態(tài)變量路克、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)樟结。屬于多線程恒共享數(shù)據(jù)【悖可以理解為jvm不停瓢宦,這里面的數(shù)據(jù)就會一直存在。真真的全局變量

二灰羽、直接內(nèi)存

這一塊怕說不明白驮履,我引用下:
直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域廉嚼,但是這部分內(nèi)存也被頻繁地使用玫镐,而且也可能導(dǎo)致OutOfMemoryError 異常出現(xiàn),所以我們放到這里一起講解怠噪。在JDK 1.4 中新加入了NIO(New Input/Output)類恐似,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O 方式,它可以使用Native 函數(shù)庫直接分配堆外內(nèi)存傍念,然后通過一個(gè)存儲在Java 堆里面的DirectByteBuffer 對象作為這塊內(nèi)存的引用進(jìn)行操作矫夷。這樣能在一些場景中顯著提高性能,因?yàn)楸苊饬嗽贘ava 堆和Native 堆中來回復(fù)制數(shù)據(jù)憋槐。
顯然口四,本機(jī)直接內(nèi)存的分配不會受到Java 堆大小的限制,但是秦陋,既然是內(nèi)存,則肯定還是會受到本機(jī)總內(nèi)存(包括RAM 及SWAP 區(qū)或者分頁文件)的大小及處理器尋址空間的限制治笨。服務(wù)器管理員配置虛擬機(jī)參數(shù)時(shí)驳概,一般會根據(jù)實(shí)際內(nèi)存設(shè)置-Xmx等參數(shù)信息,但經(jīng)常會忽略掉直接內(nèi)存旷赖,使得各個(gè)內(nèi)存區(qū)域的總和大于物理內(nèi)存限制(包括物理上的和操作系統(tǒng)級的限制)顺又,從而導(dǎo)致動態(tài)擴(kuò)展時(shí)出現(xiàn)OutOfMemoryError異常。
邏輯內(nèi)存模型我們已經(jīng)看到了等孵,那當(dāng)我們建立一個(gè)對象的時(shí)候是怎么進(jìn)行訪問的呢稚照?
在Java 語言中,對象訪問是如何進(jìn)行的俯萌?對象訪問在Java 語言中無處不在果录,是最普通的程序行為,但即使是最簡單的訪問咐熙,也會卻涉及Java 棧弱恒、Java 堆、方法區(qū)這三個(gè)最重要內(nèi)存區(qū)
域之間的關(guān)聯(lián)關(guān)系棋恼,如下面的這句代碼:
Object obj = new Object();
假設(shè)這句代碼出現(xiàn)在方法體中返弹,那“Object obj”這部分的語義將會反映到Java 棧的本地變量表中锈玉,作為一個(gè)reference 類型數(shù)據(jù)出現(xiàn)。而“new Object()”這部分的語義將會反映到Java 堆中义起,形成一塊存儲了Object 類型所有實(shí)例數(shù)據(jù)值(Instance Data拉背,對象中各個(gè)實(shí)例字段的數(shù)據(jù))的結(jié)構(gòu)化內(nèi)存,根據(jù)具體類型以及虛擬機(jī)實(shí)現(xiàn)的對象內(nèi)存布局(Object Memory Layout)的不同默终,這塊內(nèi)存的長度是不固定的椅棺。另外,在Java 堆中還必須包含能查找到此對象類型數(shù)據(jù)(如對象類型穷蛹、父類土陪、實(shí)現(xiàn)的接口、方法等)的地址信息肴熏,這些類型數(shù)據(jù)則存儲在方法區(qū)中鬼雀。由于reference 類型在Java 虛擬機(jī)規(guī)范里面只規(guī)定了一個(gè)指向?qū)ο蟮囊茫]有定義這個(gè)引用應(yīng)該通過哪種方式去定位蛙吏,以及訪問到Java 堆中的對象的具體位置源哩,因此不同虛擬機(jī)實(shí)現(xiàn)的對象訪問方式會有所不同,主流的訪問方式有兩種:使用句柄和直接指針鸦做。
如果使用句柄訪問方式励烦,Java 堆中將會劃分出一塊內(nèi)存來作為句柄池,reference中存儲的就是對象的句柄地址泼诱,而句柄中包含了對象實(shí)例數(shù)據(jù)和類型數(shù)據(jù)各自的=具體地址信息坛掠,如下圖所示。


image

如果使用直接指針訪問方式治筒,Java 堆對象的布局中就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息屉栓,reference 中直接存儲的就是對象地址,如下圖所示

image

這兩種對象的訪問方式各有優(yōu)勢耸袜,使用句柄訪問方式的最大好處就是reference 中存儲的是穩(wěn)定的句柄地址友多,在對象被移動(垃圾收集時(shí)移動對象是非常普遍的行為)時(shí)只會改變句柄中的實(shí)例數(shù)據(jù)指針,而reference 本身不需要被修改堤框。使用直接指針訪問方式的最大好處就是速度更快域滥,它節(jié)省了一次指針定位的時(shí)間開銷,由于對象的訪問在Java 中非常頻繁蜈抓,因此這類開銷積少成多后也是一項(xiàng)非称舸拢可觀的執(zhí)行成本。就本書討論的主要虛擬機(jī)Sun HotSpot 而言沟使,它是使用第二種方式進(jìn)行對象訪問的酬土,但從整個(gè)軟件開發(fā)的范圍來看,各種語言和框架使用句柄來訪問的情況也十分常見格带。

三撤缴、GC機(jī)制

GC 垃圾回收刹枉,主要針對區(qū)域?yàn)閖ava堆∏唬回收未被有效引用的無用對象微宝。
此處主要梳理下回收流程,回收詳解及算法分析可可參考下文章:
https://www.cnblogs.com/xiaoxi/p/6486852.html

java堆包含NewSpace和OldSpace兩部分虎眨,且NewSpace區(qū)域分為Eden區(qū)蟋软、From區(qū)、To區(qū)嗽桩。三者大小固定岳守。
當(dāng)有新對象進(jìn)入時(shí),會率先存入Eden區(qū)碌冶,from區(qū)和to區(qū)是兩個(gè)相對的概念湿痢。
步驟 :
1、當(dāng)有新對象產(chǎn)生扑庞,會存入到Eden區(qū)譬重,但到Eden區(qū)存不下該對象時(shí),此時(shí)jvm執(zhí)行一次Minor GC 罐氨,會將所有無用對象回收臀规,將有用的對象放在to區(qū),新增的對象放在Eden區(qū)栅隐,此時(shí)From 區(qū)為空

2塔嬉、當(dāng)新對象繼續(xù)產(chǎn)生,Eden區(qū)再裝滿租悄,在執(zhí)行一次Minor GC,回收無用對象谨究,將Eden中有用的對象和‘1’中To去中有用的對象 存入‘1’的From區(qū)。此時(shí)‘1’中的From區(qū)和To區(qū)角色切換恰矩。新的From區(qū)為空

3、重復(fù)1憎蛤、2外傅,直到執(zhí)行一次Minor GC時(shí)newSpace中的To區(qū)裝不下依然有用的對象,此時(shí)所有老對象被放入OldSpace中俩檬。

4萎胰、進(jìn)入OldSpace的幾種情況

  • 如3所描述,當(dāng)執(zhí)行Minor GC時(shí)棚辽,newSpace中的To區(qū)存不下還存活的對象時(shí)技竟,會把所有存活對象放入OldSpace
  • 當(dāng)新建的對象太大時(shí),直接存到OldSpace
  • 當(dāng)對象存活年齡較大時(shí)(可通過已經(jīng)挺過多少次Minor GC來計(jì)算)屈藐,存入OldSpace
  • 動態(tài)判斷年齡榔组,如某個(gè)年齡的對象已經(jīng)超過newSpace中To區(qū)的一半時(shí)熙尉,將大于等于改年齡的對象放入OldSpace

5、再次重復(fù)以上過程搓扯,當(dāng)出現(xiàn)OldSpace也存不下還存活的對象時(shí)检痰,則對整個(gè)java堆執(zhí)行一次Full GC,檢查當(dāng)前java堆所有對象使用情況锨推,并回收無用對象铅歼。若此時(shí)還不能給新對象分配內(nèi)存,或者按規(guī)矩要存入OldSpace的對象無法存入時(shí)换可,則報(bào)出OutOfMemoryError異常

所以針對最原始的問題椎椰,當(dāng)碰到OutOfMemoryError異常時(shí),一味加大java堆內(nèi)存也是不可取的沾鳄,這樣可以減少執(zhí)行Minor GC的次數(shù)慨飘,如果該被釋放的對象一直不被釋放,最終還是會導(dǎo)致java堆被撐滿洞渔,碰到此類情況應(yīng)著重排查內(nèi)存泄漏套媚。但有時(shí)候java堆內(nèi)存太小會使程序內(nèi)存“周轉(zhuǎn)”不過來,也會導(dǎo)致內(nèi)存溢出磁椒,此時(shí)可以適當(dāng)增大jvm內(nèi)存上限值堤瘤。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浆熔,隨后出現(xiàn)的幾起案子本辐,更是在濱河造成了極大的恐慌,老刑警劉巖医增,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慎皱,死亡現(xiàn)場離奇詭異,居然都是意外死亡叶骨,警方通過查閱死者的電腦和手機(jī)茫多,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忽刽,“玉大人天揖,你說我怎么就攤上這事」虻郏” “怎么了今膊?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長伞剑。 經(jīng)常有香客問我斑唬,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任恕刘,我火速辦了婚禮缤谎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雪营。我一直安慰自己弓千,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布献起。 她就那樣靜靜地躺著洋访,像睡著了一般。 火紅的嫁衣襯著肌膚如雪谴餐。 梳的紋絲不亂的頭發(fā)上姻政,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機(jī)與錄音岂嗓,去河邊找鬼汁展。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厌殉,可吹牛的內(nèi)容都是我干的食绿。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼公罕,長吁一口氣:“原來是場噩夢啊……” “哼器紧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起楼眷,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤铲汪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后罐柳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掌腰,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年张吉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了齿梁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肮蛹,死狀恐怖勺择,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蔗崎,我是刑警寧澤酵幕,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布扰藕,位于F島的核電站缓苛,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜未桥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一笔刹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冬耿,春花似錦舌菜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缤骨,卻和暖如春爱咬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绊起。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工精拟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人虱歪。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓蜂绎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親笋鄙。 傳聞我的和親對象是個(gè)殘疾皇子师枣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

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