對于從事C,C++程序的開發(fā)人員來說,即擁有每一個(gè)對象的”所有權(quán)”,有擔(dān)負(fù)著每一個(gè)對象開始到終結(jié)的維護(hù)責(zé)任.
對于Java程序員來說,在虛擬機(jī)自動(dòng)內(nèi)存管理機(jī)制的幫助下,不再需要為每一個(gè)new操作去寫配對的delete/free代碼,不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出問題.
1.運(yùn)行時(shí)數(shù)據(jù)區(qū)域
1.1 先看一下Java程序具體執(zhí)行過程:
Java源代碼文件(.java)—(java Compilerjava編譯器)—>java字節(jié)碼文件(.class)—(Class Loader類加載器)—>加載到(Running TIME Area運(yùn)行時(shí)數(shù)據(jù)區(qū)域),
首先Java源代碼文件(.java后綴)會(huì)被Java編譯器編譯為字節(jié)碼文件(.class后綴)软舌,然后由JVM中的類加載器加載各個(gè)類的字節(jié)碼文件,加載完畢之后誉碴,交由JVM執(zhí)行引擎執(zhí)行泥栖。在整個(gè)程序執(zhí)行過程中,JVM會(huì)用一段空間來存儲(chǔ)程序執(zhí)行期間需要用到的數(shù)據(jù)和相關(guān)信息狼电,這段空間一般被稱作為Runtime Data Area(運(yùn)行時(shí)數(shù)據(jù)區(qū))蜘渣,也就是我們常說的JVM內(nèi)存菇用。因此疾就,在Java中我們常常說到的內(nèi)存管理就是針對這段空間進(jìn)行管理(如何分配和回收內(nèi)存空間)澜术。
1.2運(yùn)行時(shí)數(shù)據(jù)包括哪幾個(gè)部分
1.2.1 程序計(jì)數(shù)器
(Program counter Register)較小的一塊區(qū)域,可以看做是程序所執(zhí)行的代碼指示器,字節(jié)碼工作時(shí)就是通過改變這個(gè)計(jì)數(shù)器的值來選取嚇一跳需要執(zhí)行的字節(jié)碼指令.分支,循環(huán),跳轉(zhuǎn),異常處理,線程回復(fù)等基礎(chǔ)功能都西藥依賴這個(gè)計(jì)數(shù)器來完成(所有從本次執(zhí)行調(diào)到下一個(gè)字節(jié)碼執(zhí)行都需要計(jì)數(shù)器的幫助).線程私有的.
程序計(jì)數(shù)器的核心用處在于被阻塞保存現(xiàn)場的線程二次喚醒后沿著上次被阻塞的地方繼續(xù)向下執(zhí)行
如果線程執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址,如果執(zhí)行的是Native方法,為空(undefined).此方法是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域.
保留上一次運(yùn)行的地方.
1.2.2 Java虛擬機(jī)棧
(Java Virtual Machine Stacks)也是線程私有的.他的生命周期和線程相同.每個(gè)方法執(zhí)行的時(shí)候會(huì)創(chuàng)建一個(gè)站幀(stack frame),用于存儲(chǔ)局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息.每一個(gè)方法從調(diào)用直至執(zhí)行完成的過程.就對應(yīng)著一個(gè)棧幀在虛擬機(jī)中入棧和出棧的過程.
局部變量表存放了編譯器可知的所有數(shù)據(jù)類型,對象應(yīng)用,和returnAddress類型.
局部變量所需空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法在棧幀中需要分配多大空間已經(jīng)完全確定,方法去運(yùn)行時(shí)不會(huì)改變局部變量的大小.
這個(gè)區(qū)域有兩種異常情況:如果線程請求的棧深度大于虛擬機(jī)允許的最大深度,將拋出StackOverFlowError錯(cuò)誤,如果虛擬機(jī)允許擴(kuò)展,當(dāng)擴(kuò)展的時(shí)候無法申請到足夠的內(nèi)存的時(shí)候會(huì)拋出OutOfMemoryError錯(cuò)誤.
1.2.3 本地方法棧
和虛擬機(jī)棧的作用相同,只不過是位本地方法所用,當(dāng)前的hotspot虛擬機(jī)將這兩塊區(qū)域合二為一.
1.2.4 Java堆
是所有線程共享的一塊區(qū)域,虛擬啟動(dòng)的時(shí)候創(chuàng)建.存放對象示例—唯一目的
所有的對象實(shí)例以及數(shù)組都要在堆上分配.
Java堆可以處于不連續(xù)的物理空間,只要邏輯上連續(xù)即可.大小可以通過-Xmx和-Xms控制.如果堆中沒有內(nèi)存來分配示例,將拋出OutOfMemoryError異常.
1.2.5 方法區(qū)
也是線程共享的一塊區(qū)域,用于存儲(chǔ)已被虛擬機(jī)加載的類信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù).
GC分代用永久帶代替方法區(qū),也會(huì)出現(xiàn)OutOfMemoryError異常.
1.2.6運(yùn)行時(shí)常量池
(RunTime Constant Pool)是方法區(qū)的一部分,用于存放編輯器生成的各種字面量和符號(hào)引用.也會(huì)出現(xiàn)OutOfMemoryError異常
1.2.7 直接內(nèi)存
并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分.收到本機(jī)內(nèi)存的限制,會(huì)拋出也會(huì)出現(xiàn)OutOfMemoryError異常.
2. 關(guān)于異常
2.1Java堆溢出:不停的新建對象即可OutOfMemoryError
2.2
虛擬機(jī)棧和本地方法棧溢出
HotSpot中并不區(qū)分虛擬機(jī)棧和本地方法棧
測試:
兩種異常:
2.2.1 如果線程請求棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverFlowError
2.2.2如果虛擬機(jī)在擴(kuò)展時(shí)無法申請到足夠的內(nèi)存空間,將拋出OutOfMemoryError
測試:使用-Xss參數(shù)減少棧內(nèi)存容量
定義了大量本地變量,增大方法棧中本地變量表中的長度
2.2.3 方法區(qū)和運(yùn)行時(shí)常量池溢出
運(yùn)行時(shí)常量區(qū)是方法去的一部分,兩個(gè)放在一起測試.
String.intern()是一個(gè)Native方法,她的作用是:如果字符串常量池中已經(jīng)包含一個(gè)等于此String對象的字符串,則返回代表池中這個(gè)字符串的String對象,否則將次String對象包含的字符串添加到常量池中,并且返回此String對象的引用.
1.6之前,常量池分配在永久代中.
使用-XX:PermSize和-XX:MaxPermSize限制方法區(qū)大小,從而間接限制其中常量池的容量
2.2.4 本地內(nèi)存直接溢出
DirectMemory 容量可通過-XX:MaxDirectMemorySize 指定,