1. 程式人生 > >JVM整體結構與垃圾回收演算法介紹問題

JVM整體結構與垃圾回收演算法介紹問題

1.類載入器(ClassLoader):在JVM啟動時或者在類執行時將需要的class載入到JVM中。

2.執行引擎:負責執行class檔案中包含的位元組碼指令(執行引擎的工作機制,這裡也不細說了,這裡主要介紹JVM結構);

3.記憶體區(也叫執行時資料區):是在JVM執行的時候操作所分配的記憶體區。執行時記憶體區主要可以劃分為5個區域,如圖:

方法區(Method Area):用於儲存類結構資訊的地方,包括常量池、靜態變數、建構函式等。雖然JVM規範把方法區描述為堆的一個邏輯部分, 但它卻有個別名non-heap(非堆),所以大家不要搞混淆了。方法區還包含一個執行時常量池。

java堆(Heap):儲存java例項或者物件的地方。這塊是GC的主要區域(後面解釋)。從儲存的內容我們可以很容易知道,方法區和堆是被所有java執行緒共享的。

java棧(Stack):java棧總是和執行緒關聯在一起,每當建立一個執行緒時,JVM就會為這個執行緒建立一個對應的java棧。在這個java棧中又會包含多個棧幀,每執行一個方法就建立一個棧幀,用於儲存區域性變量表、操作棧、方法返回值等。每一個方法從呼叫直至執行完成的過程,就對應一個棧幀在java棧中入棧到出棧的過程。所以java棧是現成私有的。

程式計數器(PC Register):用於儲存當前執行緒執行的記憶體地址。由於JVM程式是多執行緒執行的(執行緒輪流切換),所以為了保證執行緒切換回來後,還能恢復到原先狀態,就需要一個獨立的計數器,記錄之前中斷的地方,可見程式計數器也是執行緒私有的。

本地方法棧(Native Method Stack):和java棧的作用差不多,只不過是為JVM使用到的native方法服務的。

之前看過有說java棧的資料是共享的,是指的當前執行緒的資料是共享的嗎?這個是指執行緒內共享

其實對於ThreadLocal來說,它就是一個Map,它的key已經預設為Thread,而你只需要設定它的value值即可使用

用ThreadLocal實現如下

public class ThreadScopeShareData {

private static ThreadLocal<Integer> threadData = new ThreadLocal<Integer>();

public static void main(String[] args) {

for(int i=0;i<6;i++) {

new Thread(new Runnable() {

@Override

public void run() {

int data = new Random().nextInt();

System.out.println(Thread.currentThread().getName() + " has put data: " + data);

threadData.set(data);

new A().get();

new B().get();

}

}).start();

}

}

static class A{

public void get(){

int data = threadData.get();

System.out.println("A from " + Thread.currentThread().getName()+" get data: " + data);

}

}

static class B{

public void get(){

int data = threadData.get();

System.out.println("B from " + Thread.currentThread().getName()+" get data: " + data);

}

}

}

JVM的執行時資料,方法區是存靜態變數,不存非靜態的變數理解非靜態變數可以放到堆裡,非靜態的成員變數隨物件一起放在堆裡

從區域性變數1中裝載int 型別的值,是指 這個值在運算元棧中入棧操作。

出入棧如圖

垃圾檢測、回收演算法

垃圾收集器一般必須完成兩件事:檢測出垃圾;回收垃圾。怎麼檢測出垃圾?一般有以下幾種方法:

引用計數法:給一個物件新增引用計數器,每當有個地方引用它,計數器就加1;引用失效就減1。

好了,問題來了,如果我有兩個物件A和B,互相引用,除此之外,沒有其他任何物件引用它們,實際上這兩個物件已經無法訪問,即是我們說的垃圾物件。但是互相引用,計數不為0,導致無法回收,所以還有另一種方法:

可達性分析演算法:以根集物件為起始點進行搜尋,如果有物件不可達的話,即是垃圾物件。這裡的根集一般包括java棧中引用的物件、方法區常良池中引用的物件

本地方法中引用的物件等。

總之,JVM在做垃圾回收的時候,會檢查堆中的所有物件是否會被這些根集物件引用,不能夠被引用的物件就會被垃圾收集器回收。一般回收演算法也有如下幾種:

1.標記-清除(Mark-sweep)

演算法和名字一樣,分為兩個階段:標記和清除。標記所有需要回收的物件,然後統一回收。這是最基礎的演算法,後續的收集演算法都是基於這個演算法擴充套件的。

不足:效率低;標記清除之後會產生大量碎片。

2.複製(Copying)

此演算法把記憶體空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的物件複製到另外一個區域中。此演算法每次只處理正在使用中的物件,因此複製成本比較小,同時複製過去以後還能進行相應的記憶體整理,不會出現“碎片”問題。當然,此演算法的缺點也是很明顯的,就是需要兩倍記憶體空間。

3.標記-整理(Mark-Compact)

此演算法結合了“標記-清除”和“複製”兩個演算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用物件,第二階段遍歷整個堆,把清除未標記物件並且把存活物件“壓縮”到堆的其中一塊,按順序排放。此演算法避免了“標記-清除”的碎片問題,同時也避免了“複製”演算法的空間問題。

4.分代收集演算法

這是當前商業虛擬機器常用的垃圾收集演算法。分代的垃圾回收策略,是基於這樣一個事實:不同的物件的生命週期是不一樣的。因此,不同生命週期的物件可以採取不同的收集方式,以便提高回收效率。

為什麼要運用分代垃圾回收策略?在java程式執行的過程中,會產生大量的物件,因每個物件所能承擔的職責不同所具有的功能不同所以也有著不一樣的生命週期,有的物件生命週期較長,比如Http請求中的Session物件,執行緒,Socket連線等;有的物件生命週期較短,比如String物件,由於其不變類的特性,有的在使用一次後即可回收。試想,在不進行物件存活時間區分的情況下,每次垃圾回收都是對整個堆空間進行回收,那麼消耗的時間相對會很長,而且對於存活時間較長的物件進行的掃描工作等都是徒勞。因此就需要引入分治的思想,所謂分治的思想就是因地制宜,將物件進行代的劃分,把不同生命週期的物件放在不同的代上使用不同的垃圾回收方式。

如何一起學習,有沒有免費資料?

在程式設計師這條路上遇到瓶頸的朋友可以加WX:daxigua012 大家一起來提升進步 但要備註好“555” ,分享知識

關注下面公眾號"Java這點事"獲取BATJ等一線網際網路企業面試題目和答案還有java技術乾貨知識等你領取