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