寫在前文:參考大佬講解辆沦,大部分都是參考大佬的昼捍,有加一點自己的理解和增加一些面試中遇到的JVM相關(guān)的問題,想了解的可以先看看大佬的再看看我寫的查缺補漏
理解堆和棧
操作系統(tǒng)的堆和棧
操作系統(tǒng)的堆:一般由程序員分配釋放肢扯,若程序員不釋放妒茬,程序結(jié)束可能由OS回收,分配方式類似于鏈表
操作系統(tǒng)的棧:由操作系統(tǒng)自動分配釋放蔚晨,存放函數(shù)的參數(shù)值郊闯,局部變量等。操作方式于數(shù)據(jù)結(jié)構(gòu)中的棧類似
JVM的堆和棧
問題一:為什么JVM的內(nèi)存存放在操作系統(tǒng)的堆中?
答:操作系統(tǒng)的棧是操作系統(tǒng)管理的团赁,它隨時會被回收,所以如果JVM放在棧中谨履,那java的一個null對象就很難確定會被誰回收了欢摄,所以GC的存在就一點意義都沒有了。
其中左半部分并不是在JVM中笋粟,程序員編寫的.java文件怀挠,經(jīng)過JAVA編譯器編譯成.class文件(如maven工程需要maven install,打成jar包害捕,jar包里都是.class文件)绿淋,這些工作都是在編譯器中進行的。
問題二:java被編譯成了class文件尝盼,JVM怎么從硬盤上找到這個文件并裝載到JVM里呢吞滞?
答:是通過java本地接口(JNI),找到class文件后并裝載進JVM盾沫,然后找到main方法裁赠,最后執(zhí)行。
問題三:JVM虛擬機位于操作系統(tǒng)的堆中赴精,并且程序員寫好的類加載到虛擬機執(zhí)行的過程是佩捞?
(1)當一個classLoad啟動的時候,classLoader的生存地點在jvm中的堆
(2)然后它會去主機硬盤上將A.class裝載到JVM方法區(qū)
(3)執(zhí)行引擎讀取方法區(qū)的字節(jié)碼自適應解析蕾哟,邊解析邊運行(其中一種)
(4)方法區(qū)中的這個字節(jié)文件會被虛擬機拿來new A字節(jié)碼()
(5)在堆內(nèi)存生成了一個A字節(jié)碼的對象
(6)A字節(jié)碼的這個內(nèi)存文件有兩個引用:一個引用指向A的class對象一忱,一個指向加載自己的classLoader
問題四:一個完整的程序在JVM運行的流程(注意與之前的區(qū)別,3是說的一個類)
(1)首先在一個程序啟動之間谭确,它的class會被類裝載器裝入方法區(qū)(Permanent區(qū))帘营;
(2)執(zhí)行引擎讀取方法區(qū)的字節(jié)碼自適應解析,邊解析邊運行(其中一種)
(3)然后PC寄存器指向了main函數(shù)的所在位置琼富,JVM開始為main函數(shù)在java棧中預留一個棧幀仪吧,開始跑main函數(shù)
(4)main函數(shù)的代碼被執(zhí)行引擎映射成本地操作系統(tǒng)里相應的實現(xiàn)
(5)然后調(diào)用本地方法接口,本地方法運行的時候鞠眉,操作系統(tǒng)會為本地方法分配本地方法棧薯鼠,用來存儲一些臨時變量,然后運行本地方法械蹋,調(diào)用系統(tǒng)API等出皇。
(6)如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配需要時,將拋出.OutOfMemoryError異常
問題五:JVM虛擬機的生命周期
起點:當一個java應用main函數(shù)起動時虛擬機同時也被啟動哗戈。先加載字節(jié)碼文件到方法區(qū)郊艘,然后再找到main執(zhí)行程序
PS:main函數(shù)就是一個java應用的入口,main函數(shù)被執(zhí)行時,java虛擬機也就啟動了纱注,啟動了幾個main函數(shù)就啟動了幾個java應用畏浆,同時也啟動了幾個java虛擬機。
結(jié)束:只有當虛擬機實例中的所有非守護進程都結(jié)束時狞贱,java虛擬機實例才結(jié)束生命刻获。
JVM相關(guān)知識
JVM有兩種線程
守護線程,如GC垃圾回收線程
PS:GC垃圾回收機制不是創(chuàng)建的變量為空就立刻回收瞎嬉,而是超出變量的作用域后被自動回收
非守護線程蝎毡,即普通線程,如main函數(shù)就是一個非守護線程氧枣。只要有任何非守護線程還沒有結(jié)束沐兵,java虛擬機的實例都不會退出。
如下圖:
JVM結(jié)構(gòu)圖各模塊
可以劃分為數(shù)據(jù)區(qū)和功能區(qū)
數(shù)據(jù)區(qū):即運行時數(shù)據(jù)區(qū)中的內(nèi)容便监,其中方法區(qū)和堆是所有線程共享的扎谎,虛擬機棧、本地方法棧茬贵、程序計數(shù)器是線程私有的簿透。
(一)程序計數(shù)器
1、程序計數(shù)器(Program Counter Register):也叫PC寄存器解藻,是一塊較小的內(nèi)存空間老充,它可以看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器。在虛擬機的概念模型里螟左,字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令啡浊、分支、循環(huán)胶背、跳轉(zhuǎn)巷嚣、異常處理、線程恢復等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成钳吟。
2廷粒、區(qū)別于計算機硬件的pc寄存器,兩者不略有不同红且。計算機用pc寄存器來存放“偽指令”或地址坝茎,而相對于虛擬機,pc寄存器它表現(xiàn)為一塊內(nèi)存(一個字長暇番,虛擬機要求字長最小為32位)嗤放,虛擬機的pc寄存器的功能也是存放偽指令,更確切的說存放的是將要執(zhí)行指令的地址壁酬。
3次酌、當虛擬機正在執(zhí)行的方法是一個本地(native)方法的時候恨课,jvm的pc寄存器存儲的值是undefined。
PS:因為本地方法存放在本地方法棧中岳服,本地方法棧存放在緩存中剂公,不通過PC寄存器,所以是undefined
4吊宋、程序計數(shù)器是線程私有的诬留,它的生命周期與線程相同,每個線程都有一個贫母。
5、此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域盒刚。
(二)Java虛擬機棧
1腺劣、每個線程創(chuàng)建的同時會創(chuàng)建一個JVM棧,JVM棧中每個棧幀存放的為當前線程中局部基本類型的變量(java中定義的八種基本類型:boolean因块、char橘原、byte、short涡上、int趾断、long、float吩愧、double芋酌;和reference(32 位以內(nèi)的數(shù)據(jù)類型,具體根據(jù)JVM位數(shù)(64為還是32位)有關(guān)雁佳,因為一個solt(槽)占用32位的內(nèi)存空間?)脐帝、部分的返回結(jié)果,非基本類型的對象在JVM棧上僅存放一個指向堆上的地址糖权;
JAVA虛擬機棧的最小單位可以理解為一個個棧幀堵腹,一個方法對應一個棧幀,一個棧幀可以執(zhí)行很多指令
2星澳、棧運行原理:棧中的數(shù)據(jù)都是以棧幀(Stack Frame)的格式存在疚顷,棧幀是一個內(nèi)存區(qū)塊,是一個數(shù)據(jù)集禁偎,是一個有關(guān)方法和運行期數(shù)據(jù)的數(shù)據(jù)集腿堤,當一個方法A被調(diào)用時就產(chǎn)生了一個棧幀F(xiàn)1,并被壓入到棧中届垫,A方法又調(diào)用了B方法释液,于是產(chǎn)生棧幀F(xiàn)2也被壓入棧,B方法又調(diào)用了C方法装处,于是產(chǎn)生棧幀F(xiàn)3也被壓入椢笳…… 依次執(zhí)行完畢后浸船,先彈出后進......F3棧幀,再彈出F2棧幀,再彈出F1棧幀。
理解:理解:比如main方法伞矩,執(zhí)行main方法启昧,創(chuàng)造一個棧幀,然后壓入該class(由.java文件轉(zhuǎn)換)產(chǎn)生的JAVA虛擬機棧中节沦,然后main方法中調(diào)用A方法,入棧。如果A方法中沒有調(diào)用其它方法阔籽,執(zhí)行完畢出棧;如果A方法中有調(diào)用其它方法牲蜀,則將調(diào)用的方法壓入棧中(比如遞歸)笆制;
PS:JAVA虛擬機棧是有大小的,比如遞歸如果太深或者存在死循環(huán)涣达,會造成棧內(nèi)存溢出java.lang.StackOverflowError可以通過虛擬機參數(shù)-Xss來設置棧的大小
3在辆、線程私有,它的生命周期與線程相同度苔,每個線程都有一個匆篓。
4、執(zhí)行return命令如果當前線程對應的棧中沒有了棧幀寇窑,這個Java棧也將會被JVM撤銷鸦概。
問題六:八大基本類型+String類型的存放位置
我們平時所說的八大基本類型的在棧中的存放位置是:運行時數(shù)據(jù)區(qū)->虛擬機棧->虛擬機棧的一個棧幀->棧幀中的局部變量表;
局部變量表存放的數(shù)據(jù)除了八大基本類型外疗认,還可以存放一個局部變量表的容量的最小單位變量槽(slot)的大小完残,通常表示為reference;所以是可以放字符串類型的,但是要以 String a="aa";的形式出現(xiàn)横漏,如果是new Object()那就只能是在堆中了谨设,棧里面存的是棧執(zhí)行堆的地址。
(三)本地方法棧
1缎浇、本地方法:jvm中的本地方法是指方法的修飾符是帶有native的但是方法體不是用java代碼寫的一類方法扎拣,這類方法存在的意義當然是填補java代碼不方便實現(xiàn)的缺陷而提出的。
2素跺、作用同java虛擬機棧類似二蓝,區(qū)別是:虛擬機棧為虛擬機執(zhí)行Java方法服務,而本地方法棧則是為虛擬機使用到的Native方法服務指厌。
3刊愚、是線程私有的,它的生命周期與線程相同踩验,每個線程都有一個鸥诽。
4商玫、存儲在緩存當中,調(diào)用速度非衬到瑁快
(四)JAVA堆
1拳昌、堆是Java虛擬機所管理的內(nèi)存中最大的一塊,不同于上面3個钠龙,堆是jvm所有線程共享的炬藤。
2、在虛擬機啟動的時候創(chuàng)建碴里。
3沈矿、唯一的目的就是存放對象實例,幾乎所有的對象實例以及數(shù)組都要在這里分配內(nèi)存咬腋。
4细睡、Java堆是垃圾收集器管理的主要區(qū)域。因此很多時候java堆也被稱為“GC堆”(Garbage Collected Heap)帝火。從內(nèi)存回收的角度來看,由于現(xiàn)在收集器基本都采用分代收集算法湃缎,所以Java堆還可以細分為:新生代和老年代犀填;新生代又可以分為:Eden 空間、From Survivor空間嗓违、To Survivor空間九巡。
5、java堆是計算機物理存儲上不連續(xù)的蹂季、邏輯上是連續(xù)的冕广,也是大小可調(diào)節(jié)的(通過-Xms和-Xmx控制)。
6偿洁、如果在堆中沒有內(nèi)存完成實例的分配撒汉,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常涕滋。
PS:堆內(nèi)存大小-Xms -Xmx設置相同睬辐,因為-Xmx越大tomcat就有更多的內(nèi)存可以使用,這就意味著JVM調(diào)用垃圾回收機制的頻率就會減少(垃圾回收機制被調(diào)用是jvm內(nèi)存不夠時自動調(diào)用的)可以避免每次垃圾回收完成后JVM重新分配內(nèi)存宾肺。
(五)方法區(qū)(也稱作永久代溯饵,permanent區(qū))
1、在虛擬機啟動的時候創(chuàng)建锨用,所有JVM線程共享丰刊,除了和堆一樣不需要不連續(xù)的內(nèi)存空間和可以固定大小或者可擴展外,還可以選擇不實現(xiàn)垃圾收集增拥。
2啄巧、用于存放已被虛擬機加載的類信息寻歧、常量、靜態(tài)變量棵帽、以及編譯后的方法實現(xiàn)的二進制形式的機器指令集等數(shù)據(jù)熄求。
3、被裝載的class的信息存儲在Methodarea的內(nèi)存中逗概。當虛擬機裝載某個類型時弟晚,它使用類裝載器定位相應的class文件,然后讀入這個class文件內(nèi)容并把它傳輸?shù)教摂M機中逾苫。
4卿城、運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本铅搓、字段瑟押、方法、接口等描述信息外星掰,還有一項信息是常量池(Constant Pool Table)多望,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進入方法區(qū)的運行時常量池中存放氢烘。
5怀偷、指令集是個非常重要概念,因為程序員寫的代碼其實在jvm虛擬機中是被轉(zhuǎn)成了一條條指令集執(zhí)行的
左側(cè)的foo代碼是指令集播玖,可見就是在方法區(qū)椎工,程序計數(shù)器就不用說了,局部變量區(qū)位于虛擬機棧中蜀踏,右側(cè)最下方的求值棧(也就是操作數(shù)棧)我們從動圖中明顯可以看出存在棧頂這個關(guān)鍵詞因此也是位于java虛擬機棧的维蒙。
指令是Java代碼經(jīng)過javac編譯后得到的JVM指令,PC寄存器指向下一條該執(zhí)行的指令地址果覆,局部變量區(qū)存儲函數(shù)運行中產(chǎn)生的局部變量颅痊,棧存儲計算的中間結(jié)果和最后結(jié)果。
類加載是會先看方法區(qū)有沒有已經(jīng)加載過這個類局待,因此方法區(qū)中的類是唯一的八千。方法區(qū)中的類都是運行時的,都是正在使用的燎猛,是不能被GC的恋捆,所以可以理解成永久代。
問題七:方法區(qū)與堆的區(qū)別重绷?
方法區(qū)存放了類的信息沸停,有類的靜態(tài)變量,final類型變量昭卓、field自動信息愤钾、方法信息瘟滨,處理邏輯的指令集;
堆中存放的是對象(類的實例)和數(shù)組
可以理解為:方法區(qū)——類能颁,堆——對象
問題八:方法區(qū)的內(nèi)容是一次把一個工程的所有類信息都加載進去再去執(zhí)行還是邊加載邊執(zhí)行呢杂瘸?
邊加載邊執(zhí)行。如使用tomcat啟動一個spring工程伙菊,啟動過程會記載數(shù)據(jù)庫信息败玉,配置文件中的攔截器信息,service的注解信息镜硕,一些驗證信息等运翼,其中類信息就會率先加載到方法區(qū)。
如果我們想要程序啟動的快一點兴枯,就會設置懶加載血淌,把一些驗證去掉,等到真正使用的時候再去加載财剖。
PS:說明方法區(qū)的內(nèi)容可以先加載進去悠夯,也可以在使用到的時候加載
問題九:方法區(qū),棧躺坟、堆之間的過程
(1)類加載器加載的類信息放到方法區(qū)
(2)執(zhí)行程序后疗疟,方法區(qū)的方法壓入棧的棧頂
(3)棧執(zhí)行壓入棧頂?shù)姆椒?/p>
(4)遇到new對象的情況就在堆中開辟這個類的實例空間
功能區(qū):垃圾回收系統(tǒng)、類加載器瞳氓、執(zhí)行引擎
(一)類加載子系統(tǒng)
1、根據(jù)給定的全限定名類名(如java.lang.Object)來裝載class文件的內(nèi)容到Runtimedataarea中的methodarea(方法區(qū)域)栓袖。Java程序員可以extends java.lang.ClassLoader類來寫自己的Classloader匣摘。
2、對類的加載過程是:當一個classloader啟動時裹刮,classloader的生存地點在jvm中的堆音榜,然后它去主機硬盤上去裝載A.class到jvm的methodarea(方法區(qū)),方法區(qū)中的這個字節(jié)文件會被虛擬機拿來new A字節(jié)碼,然后在堆內(nèi)存生成了一個A字節(jié)碼的對象捧弃,然后A自己碼這個內(nèi)存文件有兩個引用赠叼,一個指向A的class對象,一個指向加載自己的classloader违霞。
雙親委派機制(JVM加載類時默認采用的機制)
通俗的講嘴办,就是某個特定的類加載器在接到加載類的請求的時候,首先講加載任務委托給父類加載器买鸽,依次遞歸涧郊,如果父類加載器可以完成類加載任務,就成功返回眼五;只有父類加載器無法完成此加載任務時妆艘,才自己去加載彤灶。
舉例:如JVM加載Test.class的時候
(1)首先會到自定義加載器中查找(其實是看運行時數(shù)據(jù)區(qū)的方法區(qū)有沒有加載),看是否已經(jīng)加載過批旺,若已經(jīng)加載過:返回字節(jié)碼
(2)未加載過:則詢問上一層加載器(AppClassLoader)是否已經(jīng)加載Test.class
(3)未加載過:則詢問上一層加載器(ExtClassLoader)是否已經(jīng)加載Test.class
(4)未加載過:則詢問上一層加載器(BoopStrap ClassLoader)是否已經(jīng)加載Test.class
(5)如果BoopStrap ClassLoader還是沒有記載過幌陕,則到自己指定類加載路徑下("sun.boot. class.path")查看是否有Test.class字節(jié)碼,有則返回沒有則通知下一層加載器ExtClassLoader到自己指定的類加載路徑下(java.ext.dirs)查看
(6)依次類推汽煮,最后到自定義類加載器指定的路徑還沒有找到Test.class字節(jié)碼搏熄,則拋出異常ClassNotFoundException
問題十:為什么采用雙親委派機制
1、類加載器代碼本身也是java類逗物,因此類加載器本身也是要被加載的搬卒,因此顯然必須有第一個類加載器不是Java類,這就是bootStrap翎卓,是使用C++寫的
2契邀、雖然bootStrap、ExtClassLoader失暴、AppClassLoader三個是父子類加載器關(guān)系坯门,但是并沒有使用繼承,而是使用了組合關(guān)系
3逗扒、優(yōu)點:具備了一種待優(yōu)先級的層次關(guān)系古戴,越是基礎(chǔ)的類,越是被上層的類加載器進行加載矩肩,如jdk自帶的幾個jar包肯定是最頂級的现恼。
(二)執(zhí)行引擎(Executionengine子系統(tǒng))
1、負責執(zhí)行來自類加載器子系統(tǒng)(class loader?subsystem)中被加載類中在方法區(qū)包含的指令集黍檩,通俗講就是類加載器子系統(tǒng)把代碼邏輯(什么時候該if叉袍,什么時候該相加,相減)都以指令的形式加載到了方法區(qū)刽酱,執(zhí)行引擎就負責執(zhí)行這些指令就行了喳逛。
2、程序在JVM主要執(zhí)行的過程是執(zhí)行引擎與運行時數(shù)據(jù)區(qū)不斷交互的過程棵里,可理解為上面“方法區(qū)中的動圖”
3润文、但是執(zhí)行引擎拿到的方法區(qū)中的指令還是人能夠看懂的,這里執(zhí)行引擎的工作就是要把指令轉(zhuǎn)成JVM執(zhí)行的語言(也可以理解成操作系統(tǒng)的語言)殿怜,最后操作系統(tǒng)語言再轉(zhuǎn)成計算機機器碼典蝌。
4、解釋器:一條一條地讀取头谜,解釋并且執(zhí)行字節(jié)碼指令赠法。因為它一條一條地解釋和執(zhí)行指令,所以它可以很快地解釋字節(jié)碼,但是執(zhí)行起來會比較慢砖织。這是解釋執(zhí)行的語言的一個缺點款侵。字節(jié)碼這種“語言”基本來說是解釋執(zhí)行的。
5侧纯、即時(Just-In-Time)編譯器:即時編譯器被引入用來彌補解釋器的缺點新锈。執(zhí)行引擎首先按照解釋執(zhí)行的方式來執(zhí)行,然后在合適的時候眶熬,即時編譯器把整段字節(jié)碼編譯成本地代碼妹笆。然后,執(zhí)行引擎就沒有必要再去解釋執(zhí)行方法了娜氏,它可以直接通過本地代碼去執(zhí)行它拳缠。執(zhí)行本地代碼比一條一條進行解釋執(zhí)行的速度快很多。編譯后的代碼可以執(zhí)行的很快贸弥,因為本地代碼是保存在緩存里的窟坐。
簡單理解jit就是當代碼中某些方法復用次數(shù)比較高的,并超過一個特定的值就成為了“熱點代碼”绵疲。那么這個這些熱點代碼就會被編譯成本地代碼(其實可以理解成緩存)加快訪問速度哲鸳。
6、本地native方法就是帶有native標識符修飾的方法盔憨。native修飾符修飾的方法并不提供方法體徙菠,但因為其實現(xiàn)體是由非java代碼在在外部實現(xiàn)的,因此不能與abstract連用郁岩;
存在的意義:不方便用java語言寫的代碼婿奔,使用更為專業(yè)的語言寫更合適;甚至有些JVM的實現(xiàn)就是用c編寫的问慎,所以只能使用c來寫萍摊。
更多的本地方法最好是與jdk的執(zhí)行引擎的解釋器語言一致(執(zhí)行引擎、解釋器)蝴乔;
Windows、Linux驮樊、UNIX薇正、Dos操作系統(tǒng)的核心代碼大部分是使用C和C++編寫,底層接口用匯編編寫.
為什么native方法修飾的修飾的方法PC程序計數(shù)器為undefined囚衔。讀懂上面的所有知識點可以就很容易自己理解了挖腰。在一開始類加載時,native修飾的方法就被保存在了本地方法棧中练湿,當需要調(diào)用native方法時猴仑,調(diào)用的是一個指向本地方法棧中某方法的地址,然后執(zhí)行方法直接與操作系統(tǒng)交互,返回運行結(jié)果辽俗。整個過程并沒有經(jīng)過執(zhí)行引擎的解釋器把字節(jié)碼解釋成操作系統(tǒng)語言(也就是沒有進入方法區(qū)疾渣?),PC計數(shù)器也就沒有起作用崖飘。
舉例:執(zhí)行引擎運行時數(shù)據(jù)區(qū)各個模塊協(xié)同工作
1榴捡、要執(zhí)行的代碼:
2、先執(zhí)行main方法
3朱浴、調(diào)用其他方法
(三)GC垃圾回收機制
堆內(nèi)存:類加載器讀取了類文件后吊圾,需要把類、方法翰蠢、常變量放到堆內(nèi)存中项乒,以方便執(zhí)行器執(zhí)行,堆內(nèi)存分為三部分:
1梁沧、新生代(分為Eden,From Survivor,ToSurvivor)
(1)絕大多數(shù)剛創(chuàng)建的對象會被分配在Eden區(qū)檀何,其中的大多數(shù)對象很快就會消亡。
a趁尼、Eden是連續(xù)的內(nèi)存空間埃碱,因此在其上分配內(nèi)存極快
b、每次新生代的垃圾回收(Minor GC)采用copy算法酥泞,每次Eden區(qū)滿的時候執(zhí)行Minor GC
(2)當Eden區(qū)滿的時候砚殿,執(zhí)行Minor GC,將消亡的對象清理掉芝囤。最初一次將剩余的對象復制到一個存活區(qū)From(此時TO是空白的似炎,兩個Survivor總有一個是空白的)
(3)下次Eden區(qū)滿的時候,執(zhí)行Minor GC悯姊,將消亡的對象清理掉羡藐。將存活的對象復制到TO中。然后清空From區(qū)
(4)之后from和to一直交換角色悯许,不管怎么樣一定有一個是空的
(5)直到TO區(qū)被填滿仆嗦,TO區(qū)被填滿后會將所有對象移動到老年代中。
PS:即便沒有填滿先壕,兩個存活區(qū)切換了幾次(每進行一個MinorGC瘩扼,會將所有存活的對象做一個標記+1,當標記值大于15的時候垃僚,用-XX:MaxTenuringThreshold控制移動到年老代中)
2集绰、老年代(新生代中經(jīng)歷了N次垃圾回收后仍然存活的對象,就會被放到老年代谆棺,該區(qū)域中對象存活率高)
(1)老年代的垃圾回收(Major GC)通常使用“標記-清理”或“標記-整理”算法
(2)當老年代空間不足時栽燕,會觸發(fā)Major GC/Full GC,速度比一般GC慢10倍以上
PS:整堆包括新生代和老年代的垃圾回收稱為Full GC。
3碍岔、持久代(永久代浴讯,也就是方法區(qū))
(1)在JDK8之前的HotSpot實現(xiàn)中,類的元數(shù)據(jù)如方法數(shù)據(jù)付秕、方法信息(字節(jié)碼兰珍,棧和變量大小)询吴、運行時常量池掠河、已確定的符號引用和虛方法表等被保存在永久代中,32位默認永久代的大小為64M猛计,64位默認為85M唠摹,可以通過參數(shù)-XX:MaxPermSize進行設置。GC不會在主程序運行期對永久區(qū)域進行清理奉瘤,這也導致了永久代的區(qū)域會隨著加載的Class的增多而脹滿勾拉,最終拋出OOM異常。
伴隨著GC的就是OOM(OutOfMemory異常)
1盗温、如果出現(xiàn)java.lang.OutOfMemoryError: Java heap space異常藕赞,說明Java虛擬機的堆內(nèi)存不夠。原因有二:
a.Java虛擬機的堆內(nèi)存設置不夠卖局,可以通過參數(shù)-Xms斧蜕、-Xmx來調(diào)整。
b.代碼中創(chuàng)建了大量大對象砚偶,并且長時間不能被垃圾收集器收集(存在被引用)批销。
2、如果出現(xiàn)java.lang.OutOfMemoryError: PermGen space染坯,說明是Java虛擬機對永久代Perm內(nèi)存設置不夠均芽。原因有二:
a.程序啟動需要加載大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用单鹿。
b.大量動態(tài)反射生成的類不斷被加載掀宋,最終導致Perm區(qū)被占滿。
說明:Jdk1.6及之前:常量池分配在永久代 仲锄;Jdk1.7:有劲妙,但已經(jīng)逐步“去永久代” 。Jdk1.8及之后:無(java.lang.OutOfMemoryError: PermGen space,這種錯誤將不會出現(xiàn)在JDK1.8中)昼窗。
JVM結(jié)構(gòu)圖各模塊生命周期總結(jié)
1是趴、啟動一個JVM虛擬機程序就是啟動了一個進程涛舍,啟動的同時就在操作系統(tǒng)的堆內(nèi)存中開辟一塊JVM內(nèi)存區(qū)
2澄惊、虛擬機棧、本地方法棧、程序計數(shù)器這三個模塊是線程私有的掸驱,有多少線程就有多少個這三個模塊肛搬,生命周期跟所屬線程的生命周期一致
PS:如程序計數(shù)器,因為多線程是通過線程輪流切換和分配執(zhí)行時間來實現(xiàn)毕贼,所以當線程切回到正確執(zhí)行位置温赔,每個線程都由獨立的程序計數(shù)器,各個線程之間的計數(shù)器互不影響鬼癣,獨立存儲
3陶贼、其余是跟JVM虛擬機的生命周期一致
其它還有本地庫接口和本地方法庫
本地方法庫接口:即操作系統(tǒng)所使用的編程語言的方法集,是歸屬于操作系統(tǒng)的
本地方法庫保存在動態(tài)鏈接庫中待秃,即.dll(windows系統(tǒng))文件中拜秧,格式是各個平臺專有的
在java代碼中會通過System.loadLibrary("")加載C語言庫(本地方法庫)直接與操作系統(tǒng)平臺交互
JDK,JRE章郁,JVM的關(guān)系
1枉氮、JDK(Java Development Kit)是JAVA語言的軟件開發(fā)工具包(SDK)。
2暖庄、在JDK的安裝目錄下有一個JRE目錄聊替,里面有兩個文件夾bin和lib,其中bin里面的就是jvm培廓,lib則是jvm工作所需要的類庫