導航:首頁 > 源碼編譯 > java虛擬機標記演算法

java虛擬機標記演算法

發布時間:2023-01-28 19:46:41

A. 壓縮指針

java虛擬機中每個Java對象都有一個對象頭,對象頭由標記欄位和類型指針構成。其中標記欄位用以存儲Java虛擬機有關對象的運行數據,如哈希碼、GC信息及鎖信息,而指針類型指向該對象的類。

在64位的虛擬機中,對象頭的標記欄位佔64位,而類型指針又佔64位。也就是說一個對象額外佔用的位元組就是16個位元組。以Integer對象為例,它僅有一個int類型的私有欄位,佔4個位元組。因此,每個Integer的額外開銷至少400%,這也就是Java為什麼要引入基本數據類型的原因之一。為了減少內存開銷,64位Java虛擬機引入了壓縮指針概念(對應虛擬機選項 -XX:+UseCompressedOops,默認開啟),將堆中原本64位的Java對象指針壓縮成32位的。
這樣一來,對象頭的類型指針也會被壓縮成32位,使得對象頭大小從16位元組降低為12位元組。壓縮指針不僅可以作用對象頭的類型指針,還可以作用引用類型的欄位,引用類型的數組。

默認情況下,Java虛擬機中對象的起始地址需要對齊至8的倍數(這個概念我們稱之為內存對齊(對應虛擬機選項 -XX:ObjectAlignmentInBytes,默認值為 8)。如果一個對象用不到8N位元組,那麼空白的那部分空間就白白浪費掉了。這些浪費掉的空間我們稱之為對象之間的填充。默認情況下,Java虛擬機中32位的指針可以定址到2的35次方,也就是32GB的內存空間(超過32位會關閉壓縮指針)。在對壓縮指針解引用時,我們需要將其左移3位,再加上一個固定的偏移量,便可以定址到32GB地址空間偽64位指針了。

此外,我們可以配置剛剛提到的內存對齊選項(-XX:ObjectAlignmentInBytes)來進一步提升內存定址范圍。但是,這也可能增加對象填充,導致壓縮指針沒有打到節省空間效果。

就算關閉了壓縮指針,Java虛擬機也會進行內存對齊。內存對齊不僅在於對象和對象之間,也存在於對象的各個欄位之間。比如說,Java虛擬機中的long欄位、double欄位,以及非壓縮指針狀態下的引用欄位為8的倍數。

內存對齊的一個原因是讓欄位出現在同一CPU的緩存中。如果欄位不對齊,那麼就有可能出現跨緩存行的欄位。也就是說,該欄位的讀取的讀取可能需要跨兩個緩存行,而改欄位的存儲也可能同時污染兩個緩存行。這種情況對程序的執行效率是不利的。

B. 深入理解Java虛擬機:JVM高級特性與最佳實踐的內容簡介

作為一位java程序員,你是否也曾經想深入理解java虛擬機,但是卻被它的復雜和深奧拒之門外?沒關系,《深入理解java虛擬機:jvm高級特性與最佳實踐》極盡化繁為簡之妙,能帶領你在輕松中領略java虛擬機的奧秘。《深入理解java虛擬機:jvm高級特性與最佳實踐》是近年來國內出版的唯一一本與java虛擬機相關的專著,也是唯一一本同時從核心理論和實際運用這兩個角度去探討java虛擬機的著作,不僅理論分析得透徹,而且書中包含的典型案例和最佳實踐也極具現實指導意義。
全書共分為五大部分。第一部分從宏觀的角度介紹了整個java技術體系的過去、現在和未來,以及如何獨立地編譯一個openjdk7,這對理解後面的內容很有幫助。第二部分講解了jvm的自動內存管理,包括虛擬機內存區域的劃分原理以及各種內存溢出異常產生的原因;常見的垃圾收集演算法以及垃圾收集器的特點和工作原理;常見的虛擬機的監控與調試工具的原理和使用方法。第三部分分析了虛擬機的執行子系統,包括class的文件結構以及如何存儲和訪問class中的數據;虛擬機的類創建機制以及類載入器的工作原理和它對虛擬機的意義;虛擬機位元組碼的執行引擎以及它在實行代碼時涉及的內存結構。第四部分講解了程序的編譯與代碼的優化,闡述了泛型、自動裝箱拆箱、條件編譯等語法糖的原理;講解了虛擬機的熱點探測方法、hotspot的即時編譯器、編譯觸發條件,以及如何從虛擬機外部觀察和分析jit編譯的數據和結果。第五部分探討了java實現高效並發的原理,包括jvm內存模型的結構和操作;原子性、可見性和有序性在java內存模型中的體現;先行發生原則的規則和使用;線程在java語言中的實現原理;虛擬機實現高效並發所做的一系列鎖優化措施。
《深入理解java虛擬機:jvm高級特性與最佳實踐》適合所有java程序員、系統調優師和系統架構師閱讀。

C. java虛擬機的數據類型

Java虛擬機支持Java語言的基本數據類型有8種,注意String不是基本數據類型如下:
boolean://1位元組有符號整數的補碼
byte://1位元組有符號整數的補碼
short://2位元組有符號整數的補碼
int://4位元組有符號整數的補碼
long://8位元組有符號整數的補碼
float://4位元組IEEE754單精度浮點數
double://8位元組IEEE754雙精度浮點數
char://2位元組無符號Unicode字元
幾乎所有的Java類型檢查都是在編譯時完成的。上面列出的原始數據類型的數據在Java執行時不需要用硬體標記。操作這些原始數據類型數據的位元組碼(指令)本身就已經指出了操作數的數據類型,例如iadd、ladd、fadd和dadd指令都是把兩個數相加,其操作數類型別是int、long、float和double。虛擬機沒有給boolean(布爾)類型設置單獨的指令。boolean型的數據是由integer指令,包括integer返回來處理的。boolean型的數組則是用byte數組來處理的。虛擬機使用IEEE754格式的浮點數。不支持IEEE格式的較舊的計算機,在運行Java數值計算程序時,可能會非常慢。
虛擬機支持的其它數據類型包括:
object//對一個Javaobject(對象)的4位元組引用
returnAddress//4位元組,用於jsr/ret/jsr-w/ret-w指令
註:Java數組被當作object處理。
虛擬機的規范對於object內部的結構沒有任何特殊的要求。在Sun公司的實現中,對object的引用是一個句柄,其中包含一對指針:一個指針指向該object的方法表,另一個指向該object的數據。用Java虛擬機的位元組碼表示的程序應該遵守類型規定。Java虛擬機的實現應拒絕執行違反了類型規定的位元組碼程序。Java虛擬機由於位元組碼定義的限制似乎只能運行於32位地址空間的機器上。但是可以創建一個Java虛擬機,它自動地把位元組碼轉換成64位的形式。從Java虛擬機支持的數據類型可以看出,Java對數據類型的內部格式進行了嚴格規定,這樣使得各種Java虛擬機的實現對數據的解釋是相同的,從而保證了Java的與平台無關性和可移植性。

D. 什麼是 Java 虛擬機

您好,提問者:

Java虛擬機簡稱JVM,它的作用如下:

1、其實Java不可跨平台,真正實現跨平台的是JVM虛擬機。

2、JVM其實就是一個編譯java、運行class的一個跟操作系統的一個軟體。

3、JVM的作用只針對於Java,而系統中的東西與它無關。

4、其實說白了就是一個軟體,就像VMware一樣。

Java虛擬機


一、什麼是Java虛擬機


Java虛擬機是一個想像中的機器,在實際的計算機上通過軟體模擬來實現。Java虛擬機有自己想像中的硬體,如處理器、堆棧、寄存器等,還具有相應的指令系統。


  1. 為什麼要使用Java虛擬機

Java語言的一個非常重要的特點就是與平台的無關性。而使用Java虛擬機是實現這一特點的關鍵。一般的高級語言如果要在不同的平台上運行,至少需要編譯成不同的目標代碼。而引入Java語言虛擬機後,Java語言在不同平台上運行時不需要重新編譯。Java語言使用模式Java虛擬機屏蔽了與具體平台相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(位元組碼),就可以在多種平台上不加修改地運行。Java虛擬機在執行位元組碼時,把位元組碼解釋成具體平台上的機器指令執行。


2.誰需要了解Java虛擬機


Java虛擬機是Java語言底層實現的基礎,對Java語言感興趣的人都應對Java虛擬機有個大概的了解。這有助於理解Java語言的一些性質,也有助於使用Java語言。對於要在特定平台上實現Java虛擬機的軟體人員,Java語言的編譯器作者以及要用硬體晶元實現Java虛擬機的人來說,則必須深刻理解Java虛擬機的規范。另外,如果你想擴展Java語言,或是把其它語言編譯成Java語言的位元組碼,你也需要深入地了解Java虛擬機。


3.Java虛擬機支持的數據類型


Java虛擬機支持Java語言的基本數據類型如下:


byte://1位元組有符號整數的補碼

short://2位元組有符號整數的補碼

int://4位元組有符號整數的補碼

long://8位元組有符號整數的補碼

float://4位元組IEEE754單精度浮點數

double://8位元組IEEE754雙精度浮點數

char://2位元組無符號Unicode字元


幾乎所有的Java類型檢查都是在編譯時完成的。上面列出的原始數據類型的數據在Java執行時不需要用硬體標記。操作這些原始數據類型數據的位元組碼(指令)本身就已經指出了操作數的數據類型,例如iadd、ladd、fadd和dadd指令都是把兩個數相加,其操作數類型別是int、long、float和double。虛擬機沒有給boolean(布爾)類型設置單獨的指令。boolean型的數據是由integer指令,包括integer返回來處理的。boolean型的數組則是用byte數組來處理的。虛擬機使用IEEE754格式的浮點數。不支持IEEE格式的較舊的計算機,在運行Java數值計算程序時,可能會非常慢。


虛擬機支持的其它數據類型包括:

object//對一個Javaobject(對象)的4位元組引用

returnAddress//4位元組,用於jsr/ret/jsr-w/ret-w指令

注:Java數組被當作object處理。


虛擬機的規范對於object內部的結構沒有任何特殊的要求。在Sun公司的實現中,對object的引用是一個句柄,其中包含一對指針:一個指針指向該object的方法表,另一個指向該object的數據。用Java虛擬機的位元組碼表示的程序應該遵守類型規定。Java虛擬機的實現應拒絕執行違反了類型規定的位元組碼程序。Java虛擬機由於位元組碼定義的限制似乎只能運行於32位地址空間的機器上。但是可以創建一個Java虛擬機,它自動地把位元組碼轉換成64位的形式。從Java虛擬機支持的數據類型可以看出,Java對數據類型的內部格式進行了嚴格規定,這樣使得各種Java虛擬機的實現對數據的解釋是相同的,從而保證了Java的與平台無關性和可

移植性。


二、Java虛擬機體系結構


Java虛擬機由五個部分組成:一組指令集、一組寄存器、一個棧、一個無用單元收集堆(Garbage-collected-heap)、一個方法區域。這五部分是Java虛擬機的邏輯成份,不依賴任何實現技術或組織方式,但它們的功能必須在真實機器上以某種方式實現。


  1. Java指令集

Java虛擬機支持大約248個位元組碼。每個位元組碼執行一種基本的CPU運算,例如,把一個整數加到寄存器,子程序轉移等。Java指令集相當於Java程序的匯編語言。

Java指令集中的指令包含一個單位元組的操作符,用於指定要執行的操作,還有0個或多個操作數,提供操作所需的參數或數據。許多指令沒有操作數,僅由一個單位元組的操作符構成。


虛擬機的內層循環的執行過程如下:


do{

取一個操作符位元組;

根據操作符的值執行一個動作;

}while(程序未結束)


由於指令系統的簡單性,使得虛擬機執行的過程十分簡單,從而有利於提高執行的效率。指令中操作數的數量和大小是由操作符決定的。如果操作數比一個位元組大,那麼它存儲的順序是高位位元組優先。例如,一個16位的參數存放時佔用兩個位元組,其值為:


第一個位元組*256+第二個位元組位元組碼指令流一般只是位元組對齊的。指令tabltch和lookup是例外,在這兩條指令內部要求強制的4位元組邊界對齊。


2.寄存器


Java虛擬機的寄存器用於保存機器的運行狀態,與微處理器中的某些專用寄存器類似。


Java虛擬機的寄存器有四種:

pc:Java程序計數器。

optop:指向操作數棧頂端的指針。

frame:指向當前執行方法的執行環境的指針。

vars:指向當前執行方法的局部變數區第一個變數的指針。


Java虛擬機


Java虛擬機是棧式的,它不定義或使用寄存器來傳遞或接受參數,其目的是為了保證指令集的簡潔性和實現時的高效性(特別是對於寄存器數目不多的處理器)。

所有寄存器都是32位的。


3.棧


Java虛擬機的棧有三個區域:局部變數區、運行環境區、操作數區。


(1)局部變數區 每個Java方法使用一個固定大小的局部變數集。它們按照與vars寄存器的字偏移量來定址。局部變數都是32位的。長整數和雙精度浮點數占據了兩個局部變數的空間,卻按照第一個局部變數的索引來定址。(例如,一個具有索引n的局部變數,如果是一個雙精度浮點數,那麼它實際占據了索引n和n+1所代表的存儲空間。)虛擬機規范並不要求在局部變數中的64位的值是64位對齊的。虛擬機提供了把局部變數中的值裝載到操作數棧的指令,也提供了把操作數棧中的值寫入局部變數的指令。


(2)運行環境區 在運行環境中包含的信息用於動態鏈接,正常的方法返回以及異常傳播。


·動態鏈接

運行環境包括對指向當前類和當前方法的解釋器符號表的指針,用於支持方法代碼的動態鏈接。方法的class文件代碼在引用要調用的方法和要訪問的變數時使用符號。動態鏈接把符號形式的方法調用翻譯成實際方法調用,裝載必要的類以解釋還沒有定義的符號,並把變數訪問翻譯成與這些變數運行時的存儲結構相應的偏移地址。動態鏈接方法和變數使得方法中使用的其它類的變化不會影響到本程序的代碼。


·正常的方法返回

如果當前方法正常地結束了,在執行了一條具有正確類型的返回指令時,調用的方法會得到一個返回值。執行環境在正常返回的情況下用於恢復調用者的寄存器,並把調用者的程序計數器增加一個恰當的數值,以跳過已執行過的方法調用指令,然後在調用者的執行環境中繼續執行下去。


·異常和錯誤傳播

異常情況在Java中被稱作Error(錯誤)或Exception(異常),是Throwable類的子類,在程序中的原因是:①動態鏈接錯,如無法找到所需的class文件。②運行時錯,如對一個空指針的引用


·程序使用了throw語句。

當異常發生時,Java虛擬機採取如下措施:

·檢查與當前方法相聯系的catch子句表。每個catch子句包含其有效指令范圍,能夠處理的異常類型,以及處理異常的代碼塊地址。

·與異常相匹配的catch子句應該符合下面的條件:造成異常的指令在其指令范圍之內,發生的異常類型是其能處理的異常類型的子類型。如果找到了匹配的catch子句,那麼系統轉移到指定的異常處理塊處執行;如果沒有找到異常處理塊,重復尋找匹配的catch子句的過程,直到當前方法的所有嵌套的catch子句都被檢查過。

·由於虛擬機從第一個匹配的catch子句處繼續執行,所以catch子句表中的順序是很重要的。因為Java代碼是結構化的,因此總可以把某個方法的所有的異常處理器都按序排列到一個表中,對任意可能的程序計數器的值,都可以用線性的順序找到合適的異常處理塊,以處理在該程序計數器值下發生的異常情況。

·如果找不到匹配的catch子句,那麼當前方法得到一個"未截獲異常"的結果並返回到當前方法的調用者,好像異常剛剛在其調用者中發生一樣。如果在調用者中仍然沒有找到相應的異常處理塊,那麼這種錯誤傳播將被繼續下去。如果錯誤被傳播到最頂層,那麼系統將調用一個預設的異常處理塊。

(3)操作數棧區 機器指令只從操作數棧中取操作數,對它們進行操作,並把結果返回到棧中。選擇棧結構的原因是:在只有少量寄存器或非通用寄存器的機器(如Intel486)上,也能夠高效地模擬虛擬機的行為。操作數棧是32位的。它用於給方法傳遞參數,並從方法接收結果,也用於支持操作的參數,並保存操作的結果。例如,iadd指令將兩個整數相加。相加的兩個整數應該是操作數棧頂的兩個字。這兩個字是由先前的指令壓進堆棧的。這兩個整數將從堆棧彈出、相加,並把結果壓回到操作數棧中。


每個原始數據類型都有專門的指令對它們進行必須的操作。每個操作數在棧中需要一個存儲位置,除了long和double型,它們需要兩個位置。操作數只能被適用於其類型的操作符所操作。例如,壓入兩個int類型的數,如果把它們當作是一個long類型的數則是非法的。在Sun的虛擬機實現中,這個限制由位元組碼驗證器強制實行。但是,有少數操作(操作符pe和swap),用於對運行時數據區進行操作時是不考慮類型的。


4.無用單元收集堆


Java的堆是一個運行時數據區,類的實例(對象)從中分配空間。Java語言具有無用單元收集能力:它不給程序員顯式釋放對象的能力。Java不規定具體使用的無用單元收集演算法,可以根據系統的需求使用各種各樣的演算法。


5.方法區


方法區與傳統語言中的編譯後代碼或是Unix進程中的正文段類似。它保存方法代碼(編譯後的java代碼)和符號表。在當前的Java實現中,方法代碼不包括在無用單元收集堆中,但計劃在將來的版本中實現。每個類文件包含了一個Java類或一個Java界面的編譯後的代碼。可以說類文件是Java語言的執行代碼文件。為了保證類文件的平台無關性,Java虛擬機規范中對類文件的格式也作了詳細的說明。其具體細節請參考Sun公司的Java虛擬機規范。

E. JVM垃圾回收的「三色標記演算法」實現,內容太干

三色標記法是一種垃圾回收法,它可以讓JVM不發生或僅短時間發生STW(Stop The World),從而達到清除JVM內存垃圾的目的。JVM中的 CMS、G1垃圾回收器 所使用垃圾回收演算法即為三色標記法。

三色標記法將對象的顏色分為了黑、灰、白,三種顏色。

白色 :該對象沒有被標記過。(對象垃圾)

灰色 :該對象已經被標記過了,但該對象下的屬性沒有全被標記完。(GC需要從此對象中去尋找垃圾)

黑色 :該對象已經被標記過了,且該對象下的屬性也全部都被標記過了。(程序所需要的對象)

從我們main方法的根對象(JVM中稱為GC Root)開始沿著他們的對象向下查找,用黑灰白的規則,標記出所有跟GC Root相連接的對象,掃描一遍結束後,一般需要進行一次短暫的STW(Stop The World),再次進行掃描,此時因為黑色對象的屬性都也已經被標記過了,所以只需找出灰色對象並順著繼續往下標記(且因為大部分的標記工作已經在第一次並發的時候發生了,所以灰色對象數量會很少,標記時間也會短很多), 此時程序繼續執行,GC線程掃描所有的內存,找出掃描之後依舊被標記為白色的對象(垃圾),清除。

具體流程:

在JVM虛擬機中有兩種常見垃圾回收器使用了該演算法:CMS(Concurrent Mark Sweep)、G1(Garbage First) ,為了解決三色標記法對對象漏標問題各自有各自的法:

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用集中在互聯網網站或者基於瀏覽器的B/S系統的服務端上,這類應用通常都會較為關注服務的響應速度,希望系統停頓時間盡可能短,以給用戶帶來良好的交互體驗。CMS收集器就非常符合這類應用的需求(但是實際由於某些問題,很少有使用CMS作為主要垃圾回收器的)。

從名字(包含「Mark Sweep」)上就可以看出CMS收集器是基於標記-清除演算法實現的,它的運作過程相對於前面幾種收集器來說要更復雜一些,整個過程分為四個步驟,包括:1)初始標記(CMS initial mark) 2)並發標記(CMS concurrent mark) 3)重新標記(CMS remark) 4)並發清除(CMS concurrent sweep)

其中初始標記、重新標記這兩個步驟仍然需要「Stop The World」。初始標記僅僅只是標記一下GCRoots能直接關聯到的對象,速度很快;

並發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集線程一起並發運行;

重新標記階段則是為了修正並發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但也遠比並發標記階段的時間短;

最後是並發清除階段,清理刪除掉標記階段判斷的已經死亡的對象,由於不需要移動存活對象,所以這個階段也是可以與用戶線程同時並發的。由於在整個過程中耗時最長的並發標記和並發清除階段中,垃圾收集器線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內存回收過程是與用戶線程一起並發執行的。

在應對漏標問題時,CMS使用了增量更新(Increment Update)方法來做:

在一個未被標記的對象(白色對象)被重新引用後, 引用它的對象若為黑色則要變成灰色,在下次二次標記時讓GC線程繼續標記它的屬性對象

但是就算是這樣,其仍然是存在漏標的問題:

G1(Garbage First)物理內存不再分代,而是由一塊一塊的Region組成,但是邏輯分代仍然存在。G1不再堅持固定大小以及固定數量的分代區域劃分,而是把連續的Java堆劃分為多個大小相等的獨立區域(Region),每一個Region都可以根據需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。收集器能夠對扮演不同角色的Region採用不同的策略去處理,這樣無論是新創建的對象還是已經存活了一段時間、熬過多次收集的舊對象都能獲取很好的收集效果。

Region中還有一類特殊的Humongous區域,專門用來存儲大對象。G1認為只要大小超過了一個Region容量一半的對象即可判定為大對象。每個Region的大小可以通過參數-XX:G1HeapRegionSize設定,取值范圍為1MB~32MB,且應為2的N次冪。而對於那些超過了整個Region容量的超級大對象,將會被存放在N個連續的Humongous Region之中,G1的大多數行為都把Humongous Region作為老年代的一部分來進行看待,如圖所示

Card Table(多種垃圾回收器均具備)

RSet(Remembered Set)

是輔助GC過程的一種結構,典型的空間換時間工具,和Card Table有些類似。

後面說到的CSet(Collection Set)也是輔助GC的,它記錄了GC要收集的Region集合,集合里的Region可以是任意年代的。

在GC的時候,對於old->young和old->old的跨代對象引用,只要掃描對應的CSet中的RSet即可。邏輯上說每個Region都有一個RSet,RSet記錄了其他Region中的對象引用本Region中對象的關系,屬於points-into結構(誰引用了我的對象)。

而Card Table則是一種points-out(我引用了誰的對象)的結構,每個Card 覆蓋一定范圍的Heap(一般為512Bytes)。G1的RSet是在Card Table的基礎上實現的:每個Region會記錄下別的Region有指向自己的指針,並標記這些指針分別在哪些Card的范圍內。這個RSet其實是一個Hash Table,Key是別的Region的起始地址,Value是一個集合,裡面的元素是Card Table的Index。每個Region中都有一個RSet,記錄其他Region到本Region的引用信息;使得垃圾回收器不需要掃描整個堆找到誰引用當前分區中的對象,只需要掃描RSet即可。

CSet(Collection Set)

一組可被回收的分區Region的集合, 是多個對象的集合內存區域。

新生代與老年代的比例

5% - 60%,一般不使用手工指定,因為這是G1預測停頓時間的基準,這地方簡要說明一下,G1可以指定一個預期的停頓時間,然後G1會根據你設定的時間來動態調整年輕代的比例,例如時間長,就將年輕代比例調小,讓YGC盡早行。

SATB(Snapshot At The Beginning), 在應對漏標問題時,G1使用了SATB方法來做,具體流程:

因為SATB在重新標記環節只需要去重新掃描那些被推到堆棧中的引用,並配合Rset來判斷當前對象是否被引用來進行回收;

並且在最後G1並不會選擇回收所有垃圾對象,而是根據Region的垃圾多少來判斷與預估回收價值(指回收的垃圾與回收的STW時間的一個預估值),將一個或者多個Region放到CSet中,最後將這些Region中的存活對象壓縮並復制到新的Region中,清空原來的Region。

會,當內存滿了的時候就會進行Full GC;且JDK10之前的Full GC,為單線程的,所以使用G1需要避免Full GC的產生。

解決方案:

F. java為什麼不提供類似c的析構函數和delete

回收機制有分代復制垃圾回收和標記垃圾回收,增量垃圾回收。
一.誰在做Garbage Collection?

一種流行的說法:在C++里,是系統在做垃圾回收;而在Java里,是Java自身在做。
在C++里,釋放內存是手動處理的,要用delete運算符來釋放分配的內存。這是流行的說法。確切地說,是應用認為不需要某實體時,就需用delete告訴系統,可以回收這塊空間了。這個要求,對編碼者來說,是件很麻煩、很難做到的事。隨便上哪個BBS,在C/C++版塊里總是有一大堆關於內存泄漏的話題。
Java採用一種不同的,很方便的方法:Garbage Collection。垃圾回收機制放在JVM里。JVM完全負責垃圾回收事宜,應用只在需要時申請空間,而在拋棄對象時不必關心空間回收問題。

二.對象在啥時被丟棄?

在C++里,當對象離開其作用域時,該對象即被應用拋棄。
在Java里,對象的生命期不再與其作用域有關,而僅僅與引用有關。
Java的垃圾回收機制一般包含近十種演算法。對這些演算法中的多數,我們不必予以關心。只有其中最簡單的一個:引用計數法,與編碼有關。
一個對象,可以有一個或多個引用變數指向它。當一個對象不再有任何一個引用變數指向它時,這個對象就被應用拋棄了。或者說,這個對象可以被垃圾回收機制回收了。這就是說,當不存在對某對象的任何引用時,就意味著,應用告訴JVM:我不要這個對象,你可以回收了。
JVM的垃圾回收機制對堆空間做實時檢測。當發現某對象的引用計數為0時,就將該對象列入待回收列表中。但是,並不是馬上予以銷毀。

三.丟棄就被回收?

該對象被認定為沒有存在的必要了,那麼它所佔用的內存就可以被釋放。被回收的內存可以用於後續的再分配。
但是,並不是對象被拋棄後當即被回收的。JVM進程做空間回收有較大的系統開銷。如果每當某應用進程丟棄一個對象,就立即回收它的空間,勢必會使整個系統的運轉效率非常低下。前面說過,JVM的垃圾回收機制有多個演算法。除了引用計數法是用來判斷對象是否已被拋棄外,其它演算法是用來確定何時及如何做回收。JVM的垃圾回收機制要在時間和空間之間做個平衡。
因此,為了提高系統效率,垃圾回收器通常只在滿足兩個條件時才運行:即有對象要回收且系統需要回收。切記垃圾回收要佔用時間,因此,Java運行時系統只在需要的時候才使用它。因此你無法知道垃圾回收發生的精確時間。

四.沒有引用變數指向的對象有用嗎?

前面說了,沒掛上引用變數的對象是被應用丟棄的,這意味著,它在堆空間里是個垃圾,隨時可能被JVM回收。不過,這里有個不是例外的例外。對於一次性使用的對象(有些書稱之為臨時對象),可以不用引用變數指向它。舉個最簡單也最常見的例子:System.out.println(「I am Java!」);就是創建了一個字元串對象後,直接傳遞給println()方法。

五.應用能幹預垃圾回收嗎?

許多人對Java的垃圾回收不放心,希望在應用代碼里控制JVM的垃圾回收運作。這是不可能的事。對垃圾回收機制來說,應用只有兩個途徑發消息給JVM。第一個前面已經說了,就是將指向某對象的所有引用變數全部移走。這就相當於向JVM發了一個消息:這個對象不要了。第二個是調用庫方法System.gc(),多數書里說調用它讓Java做垃圾回收。
第一個是一個告知,而調用System.gc()也僅僅是一個請求。JVM接受這個消息後,並不是立即做垃圾回收,而只是對幾個垃圾回收演算法做了加權,使垃圾回收操作容易發生,或提早發生,或回收較多而已。
希望JVM及時回收垃圾,是一種需求。其實,還有相反的一種需要:在某段時間內最好不要回收垃圾。要求運行速度最快的實時系統,特別是嵌入式系統,往往希望如此。
Java的垃圾回收機制是為所有Java應用進程服務的,而不是為某個特定的進程服務的。因此,任何一個進程都不能命令垃圾回收機製做什麼、怎麼做或做多少。

六.對象被回收時要做的事

一個對象在運行時,可能會有一些東西與其關連。因此,當對象即將被銷毀時,有時需要做一些善後工作。可以把這些操作寫在finalize()方法(常稱之為終止器)里。

protectedvoidfinalize()
{
//finalization code here
}
這個終止器的用途類似於C++里的析構函數,而且都是自動調用的。但是,兩者的調用時機不一樣,使兩者的表現行為有重大區別。C++的析構函數總是當對象離開作用域時被調用。這就是說,C++析構函數的調用時機是確定的,且是可被應用判知的。但是,Java終止器卻是在對象被銷毀時調用。一旦垃圾收集器准備好釋放無用對象佔用的存儲空間,它首先調用那些對象的finalize()方法,然後才真正回收對象的內存。由上所知,被丟棄的對象何時被銷毀,應用是無法獲知的。而且,對於大多數場合,被丟棄對象在應用終止後仍未銷毀。
在編碼時,考慮到這一點。譬如,某對象在運作時打開了某個文件,在對象被丟棄時不關閉它,而是把文件關閉語句寫在終止器里。這樣做對文件操作會造成問題。如果文件是獨占打開的,則其它對象將無法訪問這個文件。如果文件是共享打開的,則另一訪問該文件的對象直至應用終結仍不能讀到被丟棄對象寫入該文件的新內容。
至少對於文件操作,編碼者應認清Java終止器與C++析構函數之間的差異。
那麼,當應用終止,會不會執行應用中的所有finalize()呢?據Bruce Eckel在Thinking in Java里的觀點:「到程序結束的時候,並非所有收尾模塊都會得到調用」。這還僅僅是指應用正常終止的場合,非正常終止呢?因此,哪些收尾操作可以放在finalize()里,是需要酌酎的。七.Thinking ing java 一書中也對垃圾回收做了一些小結

java垃圾回收,主要是靠一個低優先順序的進程負責回收,注意,不是後台的進程,他的優點是邊回收,邊調整堆使其緊湊。
主要有以下幾種演算法:
1.引用計數該演算法在java虛擬機沒被使用過,主要是循環引用問題,因為計數並不記錄誰指向他,無法發現這些交互自引用對象。
怎麼計數?
當引用連接到對象時,對象計數加1
當引用離開作用域或被置為null時減1
怎麼回收?
遍歷對象列表,計數為0就釋放有什麼問題?
循環引用問題。
2.標記演算法標記演算法的思想是從堆棧和靜態存儲區的對象開始,遍歷所有引用,標記活得對象
對於標記後有兩種處理方式
(1)停止-復制
所謂停止,就是停止在運行的程序,進行垃圾回收所謂復制,就是將活得對象復制到另外一個堆上,以使內存更緊湊優點在於,當大塊內存釋放時,有利於整個內存的重分配有什麼問題?
一、停止,干擾程序的正常運行,二,復制,明顯耗費大量時間,三,如果程序比較穩定,垃圾比較少,那麼每次重新復制量是非常大的,非常不合算什麼時候啟動停止-復制?
內存數量較低時,具體多低我也不知道
(2)清除
也稱標記-清除演算法
也就是將標記為非活得對象釋放,也必須暫停程序運行優點就是在程序比較穩定,垃圾比較少的時候,速度比較快有什麼問題?
很顯然停止程序運行是一個問題,只清除也會造成很對內存碎片。
為什麼這2個演算法都要暫停程序運行?
這是因為,如果不暫停,剛才的標記會被運行的程序弄亂,
(3)分代收集
分代收集是利用程序有大量臨時對象的特點,對象每被引用一次,代數就增加,代數小的小型對象會被回收整理,大對象只會代數增加,不會被整理。
優點在於對於處理大量臨時的變數很有幫助
(4)自適應
jvm會監測垃圾回收的效率,在(1),(2)演算法之間切換。
3.增量收集,增量回收的主要演算法還是分代(Young Objects 回收)與Train演算法(Mature Object回收),所謂增量回收的關鍵問題是如何實現有序的增量回收而不會導致混亂(引用及其的增加與減少),分代可以逐代回收,Train演算法可以逐個車廂回收,這樣每次一代或每次一廂可以實現短停頓回收。

G. Java虛擬機怎麼判斷對象沒被引用從而回收,什麼時候會回收,什麼時候會銷毀

1. 引用計數器演算法
解釋
系統給每個對象添加一個引用計數器,每當有一個地方引用這個對象的時候,計數器就加1,當引用失效的時候,計數器就減1,在任何一個時刻計數器為0的對象就是不可能被使用的對象,因為沒有任何地方持有這個引用,這時這個對象就被視為內存垃圾,等待被虛擬機回收
優點
客觀的說,引用計數器演算法,他的實現很簡單,判定的效率很高,在大部分情況下這都是相當不錯的演算法
其實,很多案例中都使用了這種演算法,比如 IOS 的Object-C , 微軟的COM技術(用於給window開發驅動,.net裡面的技術幾乎都是建立在COM上的),Python語言等.
缺陷
無法解決循環引用的問題.
這就好像是懸崖邊的人採集草葯的人, 想要活下去就必須要有一根繩子綁在懸崖上. 如果有兩個人, 甲的手拉著懸崖, 乙的手拉著甲, 那麼這兩個人都能活, 但是, 如果甲的手拉著乙, 乙的手也拉著甲, 雖然這兩個人都認為自己被別人拉著, 但是一樣會掉下懸崖.
比如說 A對象的一個屬性引用B,B對象的一個屬性同時引用A A.b = B() B.a = A(); 這個A,B對象的計數器都是1,可是,如果沒有其他任何地方引用A,B對象的時候,A,B對象其實在系統中是無法發揮任何作用的,既然無法發揮作用,那就應該被視作內存垃圾予以清理掉,可是因為此時A,B的計數器的值都是1,虛擬機就無法回收A,B對象,這樣就會造成內存浪費,這在計算機系統中是不可容忍的.
解決辦法
在語言層面處理, 例如Object-C 就使用強弱引用類型來解決問題.強引用計數器加1 ,弱引用不增加
Java中也有強弱引用
2. 可達性分析演算法
解釋
這種演算法通過一系列成為 "GC Roots " 的對象作為起始點,從這些節點開始向下搜索所有走過的路徑成為引用鏈(Reference Chain) , 當一個對象GC Roots沒有任何引用鏈相連(用圖論的話來說就是從GC Roots到這個對象不可達),則證明此對象是不可用的
優點
這個演算法可以輕松的解決循環引用的問題
大部分的主流java虛擬機使用的都是這種演算法
3. Java語言中的GC Roots
在虛擬機棧(其實是棧幀中的本地變數表)中引用的對象
在方法區中的類靜態屬性引用對象
在方法區中的常量引用的對象
在本地方法棧中JNI(即一般說的Native方法)的引用對象

H. java有哪些垃圾回收演算法

常用的垃圾回收演算法有:
(1).引用計數演算法:
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的對象就是不再被使用的,垃圾收集器將回收該對象使用的內存。
引用計數演算法實現簡單,效率很高,微軟的COM技術、ActionScript、Python等都使用了引用計數演算法進行內存管理,但是引用計數演算法對於對象之間相互循環引用問題難以解決,因此java並沒有使用引用計數演算法。
(2).根搜索演算法:
通過一系列的名為「GC Root」的對象作為起點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則該對象不可達,該對象是不可使用的,垃圾收集器將回收其所佔的內存。
主流的商用程序語言C#、java和Lisp都使用根搜素演算法進行內存管理。
在java語言中,可作為GC Root的對象包括以下幾種對象:
a. java虛擬機棧(棧幀中的本地變數表)中的引用的對象。
b.方法區中的類靜態屬性引用的對象。
c.方法區中的常量引用的對象。
d.本地方法棧中JNI本地方法的引用對象。
java方法區在Sun HotSpot虛擬機中被稱為永久代,很多人認為該部分的內存是不用回收的,java虛擬機規范也沒有對該部分內存的垃圾收集做規定,但是方法區中的廢棄常量和無用的類還是需要回收以保證永久代不會發生內存溢出。
判斷廢棄常量的方法:如果常量池中的某個常量沒有被任何引用所引用,則該常量是廢棄常量。
判斷無用的類:
(1).該類的所有實例都已經被回收,即java堆中不存在該類的實例對象。
(2).載入該類的類載入器已經被回收。
(3).該類所對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射機制訪問該類的方法。
Java中常用的垃圾收集演算法:
(1).標記-清除演算法:
最基礎的垃圾收集演算法,演算法分為「標記」和「清除」兩個階段:首先標記出所有需要回收的對象,在標記完成之後統一回收掉所有被標記的對象。
標記-清除演算法的缺點有兩個:首先,效率問題,標記和清除效率都不高。其次,標記清除之後會產生大量的不連續的內存碎片,空間碎片太多會導致當程序需要為較大對象分配內存時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
(2).復制演算法:
將可用內存按容量分成大小相等的兩塊,每次只使用其中一塊,當這塊內存使用完了,就將還存活的對象復制到另一塊內存上去,然後把使用過的內存空間一次清理掉。這樣使得每次都是對其中一塊內存進行回收,內存分配時不用考慮內存碎片等復雜情況,只需要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
復制演算法的缺點顯而易見,可使用的內存降為原來一半。
(3).標記-整理演算法:
標記-整理演算法在標記-清除演算法基礎上做了改進,標記階段是相同的標記出所有需要回收的對象,在標記完成之後不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,在移動過程中清理掉可回收的對象,這個過程叫做整理。
標記-整理演算法相比標記-清除演算法的優點是內存被整理以後不會產生大量不連續內存碎片問題。
復制演算法在對象存活率高的情況下就要執行較多的復制操作,效率將會變低,而在對象存活率高的情況下使用標記-整理演算法效率會大大提高。
(4).分代收集演算法:
根據內存中對象的存活周期不同,將內存劃分為幾塊,java的虛擬機中一般把內存劃分為新生代和年老代,當新創建對象時一般在新生代中分配內存空間,當新生代垃圾收集器回收幾次之後仍然存活的對象會被移動到年老代內存中,當大對象在新生代中無法找到足夠的連續內存時也直接在年老代中創建。

閱讀全文

與java虛擬機標記演算法相關的資料

熱點內容
區域鏈加密幣怎麼樣 瀏覽:339
查找命令符 瀏覽:95
壓縮工具zar 瀏覽:735
白盤怎麼解壓 瀏覽:474
辰語程序員學習筆記 瀏覽:47
程序員被公司勸退 瀏覽:523
java三子棋 瀏覽:692
加密空間怎麼強制進入 瀏覽:345
ug分割曲線命令 瀏覽:209
學碼思程序員 瀏覽:609
自考雲學習app為什麼登不上 瀏覽:410
domcer伺服器晝夜更替怎麼搞 瀏覽:436
plc和單片機哪個好 瀏覽:535
帝國神話組建雲伺服器 瀏覽:827
鄧散木pdf 瀏覽:199
方舟怎麼直連伺服器圖片教程 瀏覽:563
假相pdf 瀏覽:336
找對象找程序員怎麼找 瀏覽:976
怎麼投訴蘋果商店app 瀏覽:470
華為手機如何看有多少個app 瀏覽:734