JVM--初識(shí)JAVA虛擬機(jī)

原文地址:?https://www.cnblogs.com/wtzbk/p/7985156.html

一、為什么要學(xué)習(xí)Java虛擬機(jī)?

?這里我們使用舉例來(lái)說(shuō)明為什么要學(xué)習(xí)Java虛擬機(jī),其實(shí)這個(gè)問(wèn)題就和為什么要學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)和算法是一個(gè)道理,工欲善其事,必先利其器。曾經(jīng)的我經(jīng)常害怕處理內(nèi)存溢出的問(wèn)題,因?yàn)椴恢浪麨槭裁磿?huì)出現(xiàn)這個(gè)問(wèn)題,當(dāng)我在看了這本書以后明白了垃圾回收算法,以及JVM是如何幫助我們處理GC的,這個(gè)時(shí)候當(dāng)出現(xiàn)這個(gè)問(wèn)題的時(shí)候我就明白需要查找GC Root,或者查看GC日志,去查找這個(gè)問(wèn)題的根源,這樣就能處理這些問(wèn)題。還有以前的在理解重載和重構(gòu)的時(shí)候只是在表面去理解,當(dāng)我看完這本書明白,原來(lái)在方法調(diào)用時(shí)候這些東西就生成處理,另外還有一個(gè)new到底經(jīng)歷那些事情等等一序列問(wèn)題,如果你還在就糾結(jié)一些問(wèn)題為什么是這么處理的時(shí)候那你就去看Java虛擬機(jī)吧,或許會(huì)有不一樣的感悟,以上就是為什么要學(xué)習(xí)Java虛擬機(jī)的原因,可能有部分解釋的不是很全面,我想在感悟方面在仔細(xì)說(shuō)一下這個(gè)問(wèn)題。

二、感悟?

? ? ? 其實(shí)也算不上什么感悟,只是對(duì)一些問(wèn)題認(rèn)識(shí)更加深刻而已,這里面我們來(lái)談一下GC,要探討這個(gè)問(wèn)題我們需要從4個(gè)方面入手:

? ? ? 1.JVM是如何分配內(nèi)存的?

? ? ? 2.如何才能保證正確的回收?

? ? ? 3.JVM什么情況下觸發(fā)GC以及GC的方式?

  4.如何監(jiān)控和優(yōu)化GC?

 首先從JVM內(nèi)存分布開(kāi)始:下圖是JVM內(nèi)存分布圖


?。?線程計(jì)數(shù)器,是一塊較小的內(nèi)存空間,用來(lái)指定當(dāng)前線程執(zhí)行字節(jié)碼的行數(shù),每個(gè)線程計(jì)數(shù)器都是私有的,因?yàn)槊總€(gè)線程都需要記錄執(zhí)行的行數(shù);這里解釋一下為什么每個(gè)線程都需要一個(gè)線程計(jì)數(shù)器,JVM的多線程是通過(guò)線程輪流切換分配執(zhí)行時(shí)間來(lái)實(shí)現(xiàn)的,在任何時(shí)刻,每個(gè)處理器都只會(huì)執(zhí)行一個(gè)線程中的指令,當(dāng)線程進(jìn)行切換的時(shí),為了線程能恢復(fù)當(dāng)正確的位置,所以每個(gè)線程必須有個(gè)獨(dú)立的線程計(jì)數(shù)器,這樣才能保證線程之間不互相影響。

  這里注意下,如果線程執(zhí)行是一個(gè)Java方法的時(shí)候,計(jì)數(shù)器記錄的是虛擬機(jī)字節(jié)碼指令的地址;當(dāng)執(zhí)行的是Native的方法的時(shí)候,計(jì)數(shù)器指令為空;該內(nèi)存區(qū)域是Java虛擬機(jī)唯一沒(méi)有規(guī)定任何OutOfMemoryError的區(qū)域。

 2.Java虛擬棧,這個(gè)也是一個(gè)線程私有的,生命周期與線程是同步的,每個(gè)方法在執(zhí)行的同時(shí),都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出入口等信息,每個(gè)方法的調(diào)用到執(zhí)行完成的過(guò)程就是一個(gè)棧幀入棧到出棧的過(guò)程;

  這里解釋一下局部變量表,局部變量表存儲(chǔ)方法相關(guān)的局部變量,包括基本數(shù)據(jù),對(duì)象引用和返回地址等。在局部變量表中,只有l(wèi)ong和double類型會(huì)占用2個(gè)局部變量空間(Slot,對(duì)于32位機(jī)器,一個(gè)Slot就是32個(gè)bit),其它都是1個(gè)Slot。需要注意的是,局部變量表是在編譯時(shí)就已經(jīng)確定好的,方法運(yùn)行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內(nèi)都不會(huì)改變。這部分東西我還想等下一篇博客的時(shí)候我想仔細(xì)說(shuō)一下字節(jié)碼的執(zhí)行過(guò)程;

  虛擬機(jī)棧規(guī)定了2種異常情況,一種是線程請(qǐng)求棧的深度大于虛擬機(jī)棧所允許的深度,這時(shí)候?qū)?huì)拋出StackOverflowError異常,如果當(dāng)Java虛擬機(jī)允許動(dòng)態(tài)擴(kuò)展虛擬機(jī)棧的時(shí)候,當(dāng)擴(kuò)展的時(shí)候沒(méi)辦法分配到內(nèi)存的時(shí)候就會(huì)報(bào)OutOfMemoryError異常;

? ? 3.本地方法棧,與虛擬機(jī)棧執(zhí)行的基本相同,唯一的區(qū)別就是虛擬機(jī)棧是執(zhí)行Java方法的,本地方法棧是執(zhí)行native方法的;

? ? 4.Java堆,堆區(qū)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,Java堆是被所有線程共享的內(nèi)存區(qū)域,主要存儲(chǔ)對(duì)象的實(shí)例。

? ? ? ?當(dāng)堆中沒(méi)有內(nèi)存完成實(shí)例分配,并且堆無(wú)法擴(kuò)展的時(shí)候,將會(huì)拋出OutOfMemoryError異常;當(dāng)前虛擬機(jī)都是可以擴(kuò)展的;

? ?5.方法區(qū),這個(gè)也是線程共享的內(nèi)存區(qū)域,存儲(chǔ)被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯的代碼數(shù)據(jù)等;

? ? ? 方法區(qū)在物理上也是不需要連續(xù)的,可以選擇固定大小或者擴(kuò)展的大小,還可以選擇不實(shí)現(xiàn)垃圾收集,方法區(qū)的垃圾回收是比較少的,這就是方法區(qū)為什么被稱為永久區(qū)的原因,但是方法區(qū)也是可以執(zhí)行回收的,該區(qū)域主要是針對(duì)常量池和類型的卸載;在方法區(qū)也規(guī)定當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分布的時(shí)候,將會(huì)拋出OutOfMemoryError異常;

? ? ? 運(yùn)行時(shí)常量是方法區(qū)的一部分,常量池主要用于存放編譯生成的各種字面量和符合引用,由于常量池屬于方法區(qū)的一部分,所以當(dāng)常量池沒(méi)有內(nèi)存空間的時(shí)候就拋出OutOfMemoryError異常;

? ?6.直接內(nèi)存,不是虛擬機(jī)運(yùn)行時(shí)的一部分,可以直接訪問(wèn)堆外的內(nèi)存;所以當(dāng)內(nèi)存空間無(wú)法動(dòng)態(tài)擴(kuò)展的時(shí)候就會(huì)出現(xiàn)OutOfMemoryError異常;

? ?以上基本是JVM內(nèi)存分布的內(nèi)容,簡(jiǎn)單的理解水滿則溢出就是這個(gè)道理,系統(tǒng)的整個(gè)空間是一個(gè)大的容器,分不同的部分或者桶去分擔(dān)整個(gè)容量,當(dāng)那個(gè)桶不夠的時(shí)候自然會(huì)溢出。明白內(nèi)存區(qū)域的分布我們看下對(duì)象是如何分配在內(nèi)存空間里面的?

? Java對(duì)象這里指的是引用類型的對(duì)象,這里用Student stu=new Student()為例子訪問(wèn),Student stu作為引用對(duì)象,存在與Java虛擬機(jī)棧上,new Student()保存在Java堆中,堆中記錄Student類型的信息包括方法,接口,對(duì)象類型等地址,這些類型的執(zhí)行的數(shù)據(jù)存儲(chǔ)在方法區(qū)中;

? 這里需要說(shuō)明一下對(duì)象訪問(wèn)的方式,主要包括2種句柄訪問(wèn)和直接指針訪問(wèn):

?1. 句柄訪問(wèn)主要是Java堆中劃分一塊句柄池,虛擬機(jī)棧中存放句柄池中的地址,句柄池中包括對(duì)象的實(shí)例數(shù)據(jù)和對(duì)象類型的數(shù)據(jù)的地址,基本分布如下圖:

? ?2.直接指針訪問(wèn),就是虛擬機(jī)棧直接指向Java堆中的對(duì)象類型指針和對(duì)象的實(shí)例數(shù)據(jù),然后對(duì)象類型指針在指向方法區(qū)中對(duì)象類型的實(shí)例數(shù)據(jù),分布如下圖:

?HotSpot就是第二種訪問(wèn)方式,優(yōu)點(diǎn)在于訪問(wèn)速度快,省去一次指針開(kāi)銷時(shí)間,JVM內(nèi)存分布基本介紹到這里,接下來(lái)說(shuō)下如何保證正確回收?

? 回收是已經(jīng)沒(méi)有用的對(duì)象,那怎么判斷一個(gè)對(duì)象沒(méi)用引用?這里需要簡(jiǎn)單介紹2種方法:引用計(jì)數(shù)法和可達(dá)性分析算法;

? ? ?這里簡(jiǎn)單說(shuō)一下引用計(jì)數(shù)法:對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用計(jì)數(shù)器就增加1,引用失效就減少1,計(jì)數(shù)器為0就不可用;缺點(diǎn)就在于無(wú)法處理對(duì)象直接相互引用的問(wèn)題,因?yàn)橄嗷ヒ靡院鬅o(wú)法使計(jì)數(shù)器為0,所以無(wú)法回收;

? 可達(dá)性分析算法,也就是我們常說(shuō)的GC Root,,當(dāng)一個(gè)對(duì)象沒(méi)有與任何引用鏈相連的時(shí)候,就可以對(duì)該對(duì)象進(jìn)行回收,下面是Java中GC Root對(duì)象使用的幾個(gè)地方:


? ?以上對(duì)象簡(jiǎn)單就是分為可用和不可用這2種,現(xiàn)在Java對(duì)引用概念進(jìn)行擴(kuò)充:

? 明白這些我們基本明白JVM如何正確回收,接下來(lái)就是JVM什么情況下觸發(fā)GC以及GC觸發(fā)的方式?

? 第一個(gè)問(wèn)題比較容易回答當(dāng)然是當(dāng)內(nèi)存空間不足的時(shí)候就需要觸發(fā)GC,GC回收的時(shí)候采用的是分代收集的算法,主要分為年輕代和老年代,接下來(lái)我們簡(jiǎn)單介紹一下這2種方式:

? ?年輕代:當(dāng)一個(gè)對(duì)象被創(chuàng)建的時(shí)候,內(nèi)存分配首先分配在年輕代,大部分對(duì)象創(chuàng)建以后都不再使用,對(duì)象很快變得不可達(dá),就是對(duì)象無(wú)用,由于垃圾是被年輕代清理掉的,所以被叫做Minor GC或者Young GC。

? ?老年代:對(duì)象如果在年輕代存活了足夠長(zhǎng)的時(shí)間而沒(méi)有被清理掉(即在幾次Young GC后存活了下來(lái)),則會(huì)被復(fù)制到年老代,年老代的空間一般比年輕代大,能存放更多的對(duì)象,在年老代上發(fā)生的GC次數(shù)也比年輕代少。當(dāng)年老代內(nèi)存不足時(shí),將執(zhí)行Major GC,也叫 Full GC。

? ?明白這2塊主要存放什么東西以后接下來(lái)我們看下GC的整體結(jié)構(gòu),看一個(gè)對(duì)象如何被Kill掉的流程:

? ?1.當(dāng)一個(gè)對(duì)象被創(chuàng)建的時(shí)候(new)首先會(huì)在年輕代的Eden區(qū)被創(chuàng)建,直到當(dāng)GC的時(shí)候,根據(jù)可達(dá)性算法,看一個(gè)對(duì)象是否消亡,沒(méi)有消亡的對(duì)象會(huì)被放入年輕帶的Survivor區(qū),消亡的直接被Minor GC Kill掉;

? ?2.進(jìn)入到Survivor區(qū)的對(duì)象也不是安全的,當(dāng)下一次Minor GC來(lái)的時(shí)候還是會(huì)檢查Enden和Survivor存放對(duì)象區(qū)域中對(duì)象是否存活,存活放入另外一塊Survivor區(qū)域;

? ?3.當(dāng)2個(gè)Survivor區(qū)切換幾次以后,會(huì)直接進(jìn)入老年代,當(dāng)然進(jìn)入到老年代也不是安全的,當(dāng)老年代內(nèi)存空間不足的時(shí)候,會(huì)觸發(fā)Major GC,已經(jīng)消亡的依然還是被Kill掉;

? ?推薦一個(gè)這個(gè)寫的很逗可以看下:http://blog.csdn.net/sd4015700/article/details/50109939

? ?接下來(lái)我們還需要說(shuō)一下GC的算法:標(biāo)記--清除,復(fù)制,標(biāo)記--整理這3種算法;



? ?了解算法和GC內(nèi)存分布以后我們接下來(lái)介紹垃圾回收器,這部分內(nèi)容我不計(jì)劃用文字去介紹,在第三個(gè)欄目我會(huì)將我對(duì)《深入理解Java虛擬機(jī)》這本書的思維導(dǎo)圖,內(nèi)容還不是很完善我正在整理中,但是有GC這部分內(nèi)容包括各種參數(shù)配置,大家可以下載下來(lái)具體了解一下;

? ? 最后我們談一下監(jiān)控和優(yōu)化,當(dāng)年具備以上知識(shí)以后這些都將不是問(wèn)題,所以工欲善其事必先利其器,這就是我要說(shuō)的,剩下就是對(duì)工具操作,這些我認(rèn)為不需要介紹也是可以的,當(dāng)然我也推薦一個(gè)博客:http://blog.csdn.net/renfufei/article/details/56678064

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,286評(píng)論 0 2
  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,476評(píng)論 1 34
  • 1.1 概述 Java優(yōu)點(diǎn): 1、結(jié)構(gòu)嚴(yán)謹(jǐn),面向?qū)ο?2、擺脫硬件平臺(tái)束縛,實(shí)現(xiàn)了“一次編寫,到處運(yùn)行”的理想; ...
    viciyforever閱讀 1,334評(píng)論 1 9
  • 一、運(yùn)行時(shí)數(shù)據(jù)區(qū)域 Java虛擬機(jī)管理的內(nèi)存包括幾個(gè)運(yùn)行時(shí)數(shù)據(jù)內(nèi)存:方法區(qū)、虛擬機(jī)棧、本地方法棧、堆、程序計(jì)數(shù)器,...
    luhanlin閱讀 611評(píng)論 0 0
  • 1. Java 內(nèi)存區(qū)域與內(nèi)存溢出異常 1.1 運(yùn)行時(shí)數(shù)據(jù)區(qū)域 根據(jù)《Java 虛擬機(jī)規(guī)范(Java SE 7 版...
    java大濕兄閱讀 746評(píng)論 0 20

友情鏈接更多精彩內(nèi)容