Java進階之JVM對象生命週期

前言

上週有反映內容對於初學者較難,其實我覺得是講的內容太多,導致自己不能生動具象地讓所有人理解透徹,之後會注意。這裏更提出兩點建議適用於學習知識:

1. 不要囫圇吞棗,理論性知識要逐字地去看

2. 選取適當且固定的知識源,官網和經典書籍優先,養成學習慣性

我寫博客的目的就是更好地讓大家理解難懂的概念,然後可以再去深入地討論問題

本期介紹JVM中對象的生命週期,應該算是一個常見且基礎的問題,這裏再拿出來重新梳理,希望能對讀者有所幫助。

目錄

前言

一,Java 對象的本質

二,如何識別“垃圾”

三,如何在回收期間拯救java對象

四,方法區可以執行GC麼


一,Java 對象的本質

什麼是Java對象

Java對象是Java類所產生的實體

概念本身沒有什麼可討論的,不過爲什麼我們要如此重視Java對象的概念呢?或者說對象對於Java來說有什麼特殊性?

這就要從內存分配和垃圾回收說起

Java和C++之間有一堵由內存動態分配和垃圾收集技術所圍成的高牆,外邊的人想進去,裏邊的人想出來。

C++編程每個生成的對象需要我們去控制它的生命週期以及分配內存,細緻管理有始有終。而Java把對象的誕生,內存分配和消亡都交給了JVM。這就導致如果你不理解JVM,其實就不理解Java對象在機器中的狀態,不是真正全面地理解Java。而且在實際生產環境中會遇到大量JVM相關的問題,所以瞭解JVM並不一定讓你去開發JVM,而是能更好理解你的Java程序。

所以當你Object o的時候創建了一個引用。

當你new Object()的時候,你已經創建了一個對象

Object o = new Object()已經創造出了一個引用爲o的Object對象,你把它託付給JVM給它分配內存空間,並通過代碼配合JVM管理它身爲Java對象的一生。

 

對象引用

對象引用並不是只有上邊一種,從JDK1.2開始,java引用共有四種(強度依次遞減):

強引用,軟引用,弱引用,虛引用

強引用就是上邊的這種形式:

Object o = new Object()

只要引用還存在,不符合JVM回收垃圾的規則,就不會被回收掉,是我們大多數使用的對象引用。

 

軟引用 

SoftReference<String> ref = new SoftReference<String>("Hello world")

通過SoftReference創建,標識如果系統將要發生內存溢出異常,將會把這些對象列進回收範圍並進行二次回收。

可以用作緩存的設計

 

弱引用

WeakReference<String> abcWeakRef = new WeakReference<String>(abc);

被弱引用所關聯的對象無論內存夠不夠用,都只能生存到下一次垃圾回收發生之前。

 

虛引用

PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc,referenceQueue);   

虛引用不會影響到實際對象的生命週期,當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之 關聯的引用隊列中。你聲明虛引用的時候是要傳入一個queue的。當你的虛引用所引用的對象已經執行完finalize函數的時候,就會把對象加到queue裏面。你可以通過判斷queue裏面是不是有對象來判斷你的對象是不是要被回收了。

 

二,如何識別“垃圾”

如何判定對象是否存活,如何處理垃圾對象清除的時間?一般有以下兩個方法。

引用計數

引用計數是基礎計算引用的方法。

簡單地說,就是對象被引用一次,在它的對象頭上加一次引用次數,如果沒有被引用(引用次數爲 0),則此對象可回收。

// 我們初始化兩個類的對象
Obj a = new Obj();
Obj b = new Obj();

// 假設Obj有成員變量member的類型也是Obj,a與b相互引用
a.member = b;
b.member = a;

// 這是當我們想回收a和b的時候 發現因爲他們互相引用 導致引用計數方法不能讓JVM回收他們
a = null;
b = null;

不過 現在的gc不會使用引用計數來判斷對象是否存活,一般使用根搜索方法,也叫可達性分析。

可達性分析

可達性算法的原理是以一系列叫做  GC Root  的對象爲起點出發,引出它們指向的下一個節點,再以下個節點爲起點,引出此節點指向的下一個結點(這樣通過 GC Root 串成的一條線就叫引用鏈),直到所有的結點都遍歷完畢,如果相關對象不在任意一個以 GC Root 爲起點的引用鏈中,則這些對象會被判斷爲垃圾對象會被 GC 回收。

可作爲GC Root的四種對象:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象 //引用範圍?

  • 方法區中類靜態屬性引用的對象// static

  • 方法區中常量引用的對象 // constant

  • 本地方法棧中 JNI(即一般說的 Native 方法)引用的對象 //JNI?

理解GC Root需要掌握JVM的內存模型,線程非共享的虛擬機棧中存儲的是此線程中正在被需要的對象引用,本地方法棧中存儲native方法(用C++或其他語言的轉化的方法)的引用對象。至於靜態和常量的生命週期是Java程序中伴隨着程序消亡的對象引用,所以以上引用可以作爲GC Root。

 

三,如何在回收期間拯救java對象

在深入理解JVM中,闡述過關於回收期間拯救Java對象這麼一個情節。其原理是如果被回收的對象覆蓋了finalize方法,實現可達性分析標記GC Root引用鏈後,會把這個對象放到一個特殊隊列中,開啓另外一個線程等到主回收GC完成後,再標記特殊隊列中的預回收對象進行回收,我們可以利用這個時間差控制對象重新添加GC Root引用鏈來實現挽救。不過這樣做控制不好可能導致JVM崩潰,不建議這樣實現。

 

四,方法區可以執行GC麼

永久代與方法區

方法區用於存放class的相關信息。永久代和方法區的關係相當於Java的類和接口的關係。很多垃圾回收器沒有永久代概念,永久代是HotSpot虛擬機對虛擬機規範中方法區的一種實現方式。JDK8之後方法區的實現交給了元空間,永久代有一個JVM本身設置固定大小上線,無法進行調整,而元空間使用的是直接內存,受本機可用內存的限制,並且永遠不會得到OutOfMemoryError。

 

永久代的垃圾回收

在方法區實現垃圾回收效率比較低,一般會回收兩類數據:

 

廢棄常量

什麼時候回收廢棄常量?

廢棄常量 和堆中的對象回收類似,當常量池中的常量沒有其他地方引用的時候,執行方法區的GC就會被回收

 

無用的類

怎麼判斷無用的類?

該類的所有實例都已經被回收,堆中不存在該類的任何實例

加載該類的classloader已經被回收

該類對應的java.lang.class對象沒有地方引用,即無法通過反射訪問該類的方法

不設置虛擬機-Xnoclassgc參數

 

關於Xnoclassgc參數,在大量使用發射、動態代理、cglib等框架比如Spring、hibernate等,都需要虛擬機具備類卸載的功能,以保證方法區不會溢出。如果限制類卸載功能及限制 PermSize大小,方法區很快就會溢出,所以常規不建議設置。

 

參考:

1. 深入理解JVM - 周志明

2. 垃圾回收講解:https://mp.weixin.qq.com/s/_AKQs-xXDHlk84HbwKUzOw

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章