《深入理解JAVA虚拟机》周志明 第三版 - 第二章 JAVA内存区域与内存溢出异常
一、 概述
在虛擬機(jī)自動(dòng)內(nèi)存管理機(jī)制下,不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出問題,但是一旦出現(xiàn)內(nèi)存泄漏和溢出方面的問題,如果不了解虛擬機(jī)是怎樣使用內(nèi)存的,那排查錯(cuò)誤、修正問題將會(huì)成為一項(xiàng)異常艱難的工作。
二、運(yùn)行時(shí)數(shù)據(jù)區(qū)域
1 程序計(jì)數(shù)器
Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū):
 
 程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。改變計(jì)數(shù)器的值來選取下一條要執(zhí)行的字節(jié)碼指令(和計(jì)算機(jī)CPU一樣),各條線程之間的計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),是“線程”私有的內(nèi)存。
2 Java虛擬機(jī)棧
Java虛擬機(jī)棧(Java Virtual Machine Stack)也是線程私有的,它的生命周期與線程相同。每個(gè)方法執(zhí)行的時(shí)候,java虛擬機(jī)會(huì)同步創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)局部變量表,操作數(shù)棧,動(dòng)態(tài)連接,方法出口等信息,每個(gè)方法執(zhí)行的過程,對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)中入棧和出棧的過程。
局部變量表存放編譯期可知的各種基本數(shù)據(jù)類型,引用類型和returnAddress類型(指向了一條字節(jié)碼指令的地址)。
如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;如果Java虛擬機(jī)棧容量可以動(dòng)態(tài)擴(kuò)展[2],當(dāng)棧擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存會(huì)拋出OutOfMemoryError異常。
3 本地方法棧
本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,本地方法棧則是為虛擬機(jī)使用到的本地(Native)方法服務(wù)。
4 Java堆
Java堆(Java Heap)是虛擬機(jī)所管理的內(nèi)存中最大的一塊,是被所有線程共享的,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例。
Java堆是垃圾收集器管理的內(nèi)存區(qū)域,因此一些資料中它也被稱作“GC堆”,J堆中經(jīng)常會(huì)出現(xiàn)“新生代”“老年代”“永久代”“Eden空間”“From Survivor空間”“To Survivor空間”等名詞,這些區(qū)域劃分僅僅是一部分垃圾收集器的共同特性或者說設(shè)計(jì)風(fēng)格而已,而非某個(gè)Java虛擬機(jī)具體實(shí)現(xiàn)的固有內(nèi)存布局。
5 方法區(qū)
方法區(qū)(Method Area)與Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類型信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼緩存等數(shù)據(jù)。
6 運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池表(Constant Pool Table),用于存放編譯期生成的各種字面量與符號(hào)引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中。
7 直接內(nèi)存
直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是《Java虛擬機(jī)規(guī)范》中定義的內(nèi)存區(qū)域。
三、HotSpot虛擬機(jī)對(duì)象探秘
1 對(duì)象的創(chuàng)建
(1)當(dāng)Java虛擬機(jī)遇到一條字節(jié)碼new指令時(shí),先去檢查這個(gè)指令的參數(shù)是否能在常量池中定位到一個(gè)類的符號(hào)引用,并檢查這個(gè)符號(hào)引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執(zhí)行相應(yīng)的類加載過程
(2)在類加載檢查通過后,虛擬機(jī)將為新生對(duì)象分配內(nèi)存。由Java堆是否規(guī)整決定選擇哪種分配方式:
(3)內(nèi)存分配完成之后,虛擬機(jī)必須將分配到的內(nèi)存空間(但不包括對(duì)象頭)都初始化為零值。
(4)對(duì)對(duì)象進(jìn)行必要的設(shè)置。
2 對(duì)象的內(nèi)存布局
對(duì)象在堆內(nèi)存中的存儲(chǔ)布局可以劃分為三個(gè)部分:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。
(1)對(duì)象頭:
包括兩類信息。分別是:
a.用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),官方稱它為“Mark Word”。
 
b.類型指針,即對(duì)象指向它的類型元數(shù)據(jù)的指針,Java虛擬機(jī)通過這個(gè)指針來確定該對(duì)象是哪個(gè)類的實(shí)例。
(2)實(shí)例數(shù)據(jù)
是對(duì)象真正存儲(chǔ)的有效信息,即我們?cè)诔绦虼a里面所定義的各種類型的字段內(nèi)容。
(3)對(duì)齊填充
并不是必然存在的,也沒有特別的含義,它僅僅起著占位符的作用。
3 對(duì)象的訪問定位
Java程序會(huì)通過棧上的reference數(shù)據(jù)來操作堆上的具體對(duì)象,對(duì)象訪問方式由虛擬機(jī)實(shí)
 現(xiàn)而定,主流的訪問方式主要有使用句柄和直接指針兩種:
(1)使用句柄訪問
Java堆中將可能會(huì)劃分出一塊內(nèi)存來作為句柄池,reference中存儲(chǔ)的就是對(duì)象的句柄地址,而句柄中包含了對(duì)象實(shí)例數(shù)據(jù)與類型數(shù)據(jù)各自具體的地址信息。
 
(2)使用直接指針訪問
Java堆中對(duì)象的內(nèi)存布局就必須考慮如何放置訪問類型數(shù)據(jù)的相關(guān)信息,reference中存儲(chǔ)的直接就是對(duì)象地址,如果只是訪問對(duì)象本身的話,就不需要多一次間接訪問的開銷。最大的好處是速度更快,它節(jié)省了一次指針定位的時(shí)間開銷。
 
四、實(shí)戰(zhàn):OutOfMemoryError異常
【代碼實(shí)踐,沒有跟著做,記錄一下要點(diǎn)吧】
1 Java堆溢出
2 虛擬機(jī)棧和本地方法棧溢出
1)如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverflowError異常。
 2)如果虛擬機(jī)的棧內(nèi)存允許動(dòng)態(tài)擴(kuò)展,當(dāng)擴(kuò)展棧容量無法申請(qǐng)到足夠的內(nèi)存時(shí),將拋出
 OutOfMemoryError異常。
3 方法區(qū)和運(yùn)行時(shí)常量池溢出
在JDK 8以后,HotSpot還是提供了一些參數(shù)作為元空間的防御措施,主要包括:
 ·-XX:MaxMetaspaceSize:設(shè)置元空間最大值,默認(rèn)是-1,即不限制,或者說只受限于本地內(nèi)存大小。
 ·-XX:MetaspaceSize:指定元空間的初始空間大小,以字節(jié)為單位,達(dá)到該值就會(huì)觸發(fā)垃圾收集進(jìn)行類型卸載,同時(shí)收集器會(huì)對(duì)該值進(jìn)行調(diào)整:如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過-XX:MaxMetaspaceSize(如果設(shè)置了的話)的情況下,適當(dāng)提高該值。
 ·-XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空間剩余容量的百分比,可減少因?yàn)樵臻g不足導(dǎo)致的垃圾收集的頻率。類似的還有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空間剩余容量的百分比。
4 本機(jī)直接內(nèi)存溢出
直接內(nèi)存(Direct Memory)的容量大小可通過-XX:MaxDirectMemorySize參數(shù)來指定,如果不去指定,則默認(rèn)與Java堆最大值(由-Xmx指定)一致。
五、 本章小結(jié)
本章只是講解了各個(gè)區(qū)域出現(xiàn)內(nèi)存溢出異常的原因。
先看書記錄一下要點(diǎn),之后看視頻的時(shí)候會(huì)進(jìn)行補(bǔ)充記錄。
 (如果能堅(jiān)持到看視頻的話……)
總結(jié)
以上是生活随笔為你收集整理的《深入理解JAVA虚拟机》周志明 第三版 - 第二章 JAVA内存区域与内存溢出异常的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: JVM(周志明著深入了解JVM书归纳,新
- 下一篇: 深入理解Java虚拟机(周志明第三版)-
