一宝恶、java虛擬機運行時數據取
1涕俗、程序計數器(Program Counter Register)
? ? ? 程序計數器是一塊較小的內存,他可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器欧芽。在虛擬機的概念模型里浓体,字節(jié)碼解釋器工作時就是通過改變這個這個計數器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,分支泣刹、循環(huán)、跳轉犀被、異常處理椅您、線程恢復等基礎功能都需要依賴這個計數器來完成。由于java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現的寡键,在任何一個時刻掀泳,一個處理器一個內核只會執(zhí)行一條線程中的指令。因此每條線程都需要有一個獨立的程序計數器西轩,各條線程之間計數器互不影響员舵,獨立存儲,這類內存為“線程私有”內存藕畔。
2马僻、Java虛擬機棧(Java Virtual Machine Stacks)
? ? Java虛擬機棧也是線程私有的,生命周期與線程相同注服。其描述的是Java方法執(zhí)行的內存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表韭邓、操作數棧、動態(tài)鏈接溶弟、方法出口等信息女淑。每一個方法從調用直至執(zhí)行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程辜御。
3鸭你、本地方法棧(Native Method Stack)
? ? ? 與虛擬機棧很相似,區(qū)別:虛擬機棧為虛擬機執(zhí)行java方法服務,本地方法棧是為虛擬機使用到的Native方法服務袱巨。
4阁谆、Java堆
? ? ? Java堆(Java Heap)是java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區(qū)域瓣窄,在虛擬機啟動時創(chuàng)建笛厦。此內存區(qū)域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存俺夕。Java堆是垃圾收集器管理的主要區(qū)域裳凸。Java堆中還可以細分為:新生代和老年代;再細一點的有:Eden空間劝贸,From Survivor空間姨谷,To Survivor空間等。
5映九、方法區(qū)(Method Area)
? ? 與Java堆一樣梦湘,是各個線程共享的內存區(qū)域,它用于存儲已被虛擬機加載的類消息件甥、常量捌议、靜態(tài)變量、即時編譯器編譯后的代碼等數據引有。
6瓣颅、運行時常量池(Runtime Constant Pool)
? ? 這是方法區(qū)的一部分。Class文件中除了有類的版本譬正、字段宫补、方法、接口等描述信息外曾我,還有是常量池(Constant Pool Table)粉怕,用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區(qū)的運行時常量池中存放抒巢。
7贫贝、直接內存(Direct Memory)
? ? 本機物理機內存。
二蛉谜、探討HotSpot虛擬機在Java堆中對象分配平酿、布局和訪問的過程
1、對象的創(chuàng)建
? ? ? Object obj=new Object();
? ? ? jvm遇到一條new指令時悦陋,首先會去檢查這個指令的參數是否能在常量池中定位到一個類的符號蜈彼,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過俺驶。如果沒有幸逆,那么必須先執(zhí)行相應的類加載過程棍辕。
? ? ? 在類加載檢查通過后,接下來jvm將為新生對象分配內存还绘,并且對象所需的內存已經確定了楚昭。如果java堆中內存是絕對規(guī)整的,則用“指針碰撞”分配方式拍顷。如果java堆中的內存并不是絕對規(guī)整的抚太,已使用的內存和空閑的內存相互交錯,jvm就必須維護一個列表昔案,記錄上哪些內存塊是可用的尿贫,在分配的時候從列表中找到一塊足夠大的空間規(guī)劃給對象實例,并更新列表上的記錄踏揣。這種分配方式叫“空閑列表”庆亡。java堆是否規(guī)整決定了內存的分配方式,而java堆規(guī)整與否捞稿,由所采用的垃圾收集器是否帶有壓縮整理功能決定的又谋。使用Serial、ParNew 等帶Compact過程的收集器時娱局,系統(tǒng)采用指針碰撞分配彰亥,而使用CMS這種基于Mark-Sweep算法的收集器時,通常采用空閑列表衰齐。
? ? ? 分配空間之外剩愧,我們還應該考慮是否線程安全,而jvm有兩種解決方案娇斩,一種是對分配內存空間的動作進行同步處理---實際上jvm采用CAS配上失敗重試的方式保證更新操作的原子性;另一種是把內存分配的動作按照線程劃分在不同的空間之中進行穴翩,即每個線程在Java堆中預先分配一小塊內存犬第,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)
? ? ? 內存分配完后,虛擬機需要將分配到內存空間都初始化為零值芒帕。
? ? ? 以上工作都完成之后歉嗓,從jvm的視角來看,一個新的對象已經產生了背蟆,但是從java來看鉴分,對象創(chuàng)建才剛剛開始,----方法還沒有執(zhí)行,所有的字段都為零带膀。執(zhí)行完init方法后志珍,這樣一個真正的對象才算完全產生出來。
2垛叨、對象的內存布局
? ? 在HotSpot虛擬機中伦糯,對象在內存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。
? ? ? HotSpot虛擬機的對象頭包括兩部分信息敛纲,第一部分用于存儲對象自身的運行時數據喂击,如哈希碼(HashCode),GC分代年齡淤翔、鎖狀態(tài)標志翰绊、線程持有的鎖、偏向線程ID旁壮、偏向時間戳等监嗜。另一部分是類型指針,即對象指向它的類元數據的指針寡具,虛擬機通過這個指針來確定這個對象是哪個類的實例秤茅。
? ? ? 實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容童叠。
? ? ? 第三部分對其填充并不是必然存在的框喳,也沒有特別的含義,它僅僅起著占位符的作用厦坛。
3五垮、 對象的訪問定位
? ? ? java程序需要通過棧上的reference數據來操作堆上的具體對象。目前主流的訪問方式有使用句柄和直接指針兩種杜秸。
? ? ? 1放仗、句柄訪問,java堆中將會規(guī)劃分出一塊內存來作為句柄池撬碟,reference中存儲的就是對象的句柄地址诞挨,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。如圖:
? ? ? 2呢蛤、指針訪問惶傻,java堆對象的布局中就必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接就是對象地址其障。如圖:
三银室、OutOfMemoryError異常
1、Java堆異常
? ? ? Java用于存儲對象實例励翼。
? ? ? 通過參數 --XX:+HeapDumpOnOutOfMemoryError蜈敢,異常時可以Dump出當前內存堆轉儲快照以便分析。-Xms20m:設置java堆最小內存汽抚, -Xmx20m:設置Java堆最大內存
eg:
/**
* VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
**/
public class HeapOOM{
? ? ? static class OOMObject{
? ? ? }
? ? ? public static void main(){
? ? ? ? ? List list =new ArrayList();
? ? ? ? ? while(true){
? ? ? ? ? ? ? list.add(new OOMObject());
? ? ? ? ? ? }
? ? ? }
}
2抓狭、虛擬機棧和本地方法棧溢出
? ? -Xss 參數控制棧內存的大小,eg:-Xss128k造烁。結果拋出StackOverFlowError異常辐宾。
3狱从、方法區(qū)和運行時常量池溢出
? ? ? 可以通過 -XX:PermSize和 -XX:MaxPermSize,限制方法區(qū)大小叠纹。
eg:
? ? ? -XX:PermSize=10M? -XX:MaxPermSize=10M
4季研、本機直接內存溢出
? ? ? DirectMemory容量可以通過 -XX:MaxDirectMemorySize指定,如果不指定誉察,則默認與Java堆最大值(-Xmx指定)一樣与涡。
本文來自于《深入Java虛擬機-JVM高級特性與最佳實踐》---周志明。如果侵權持偏,請聯系作者刪除驼卖。