1. 程式人生 > >對象與運行時內存

對象與運行時內存

時間 font 出了 在線 設置 是否 初始化 run 發送

和大多數猴子一樣,我原來也抵觸對原理的學習,

後來發現掌握了原理才有了那種了然於胸,運籌帷幄的感覺,也就是頓悟。

這裏主要介紹Java對象與運行時內存的知識。

java運行時內存

Program Counter Registe(程序計數器):

記錄當前線程執行字節碼的位置,相當於行號指示器,為線程私有的。

Java Virtual Machine Stacks(java虛擬機棧):

存放方法出口信息、局部變量表(對象引用、基本類型數據等),為線程私有的。

Native Method Stack(本地方法棧):

和java虛擬機棧一樣,只不過是為本地方法服務的。

Java Heap(堆):

存放new出來的對象,是線程共享的,也是垃圾收集器管理的主要區域(可以細分為:新生代和老年代),堆的大小通過-Xmx和-Xms控制。

Method Area(方法區):

存放類信息(版本、字段、方法、接口等描述信息)、常量、靜態變量等,是線程共享的。

(Runtime Constant Pool)運行時常量區:

存放編譯時產生的字面量和符號引用,屬於方法區的一部分。

內存分配的方式

指針碰撞


假設Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閑的內存放在另一邊,中間放著一個指針作為分界點的指示器。分配內存時就把指針向空閑空間那邊挪動一段與對象大小相等的距離;

一些新生代GC收集器使用的是復制算法,所以采用指針碰撞方式分配內存。

空閑列表


如果Java堆中的內存並不是規整的,已使用的內存和空閑的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例, 並更新列表上的記錄;

老年代垃圾收集器使用了標記清理、整理算法,所以配合空閑列表方式分配內存。

訪問對象的方式

句柄訪問


Java堆中將會劃分出一塊內存來作為句柄池,reference中存儲的就是對象的句柄地址,句柄中包含了對象實例數據與類型數據各自的具體地址信息;

使用句柄來訪問的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數據指針,而reference本身不需要修改。

技術分享

直接指針


reference中存儲的直接就是對象地址,Java堆對象的布局中就必須考慮如何放置訪問類型數據的相關信息;

使用直接指針訪問方式的最大好處就是速度更快,它節省了一次指針定位的時間開銷,HotSpot采用了直接指針方式。

技術分享

對象在java堆的過程

普通對象在堆中從創建到銷毀的整個過程大概可分為:new > 類加載 > 分配內存 > 初始化 > 使用 > 回收

new:

向虛擬機發送一條new指令

類加載:

方法區內存進行校驗(檢查該對象所屬類是否加載、解析、初始化),在確定是否進行類加載;

對象所需內存的大小在類加載完成後便可完全確定。

分配內存:

在新生代的Eden區分配內存空間,如果Eden區內存空間不夠就從FromSurvivor內存分配,還是不夠的話分配到老年代內存區:

分配方式有指針碰撞、空閑列表,分配內存存在線程安全問題,有兩種解決方案:同步、TLAB線程私有空間。

初始化:

將分配的內存空間初始化為默認值, 比如int類型的為0,String為null;

設置對象頭信息(對象是哪個類的實例、如何才能找到類的元數據信息、哈希碼等)。

使用:

通過引用訪問對象,訪問的方式有指針碰撞、句柄訪問兩種,取決於虛擬機。

回收:

當JVM內存不夠時,如果該對象超出了作用域,並且在GCRoots搜索不到,就進行回收標記;
如果下一次GC之前,該對象還是沒有被使用,就回收內存。

對象與運行時內存