淺談Java GC

一個(gè)java新手的摸索之路

1.GC 基礎(chǔ)

1.1什么是GC?

GC(Garbage Collection)——垃圾收集耕魄,GC就是找到內(nèi)存空間的垃圾画恰,然后回收垃圾,讓程序員能夠再次利用這部分空間的一種自動(dòng)管理內(nèi)存的機(jī)制吸奴。

圖1.1

如圖1.1所示:當(dāng)我們new 一個(gè)Object的時(shí)候允扇,系統(tǒng)就為我們?cè)趦?nèi)存中分配了一塊空間。并且我們讓Object類型的引用obj指向了這塊空間则奥。之后又將obj的引用置為null(置為null其實(shí)是讓這個(gè)引用在棧中分配空間考润,但是沒有指向堆中的某塊空間)。此時(shí)之前new Object()的時(shí)候分配的那塊空間就無法被引用了读处,就成為了垃圾糊治,我們稱他為死亡的對(duì)象。GC就是回收這種無法被引用的對(duì)象的機(jī)制罚舱。

1.2 為什么我們要學(xué)GC井辜?

使用CG只有一個(gè)目的,就是“偷懶”——簡化開發(fā)管闷。提高編程效率粥脚。

我們先來看看不使用GC的語言(比如C++),如果沒有GC渐北,程序員需要手動(dòng)管理內(nèi)存阿逃,然而人為的手動(dòng)釋放資源容易出現(xiàn)問題,比如內(nèi)存泄漏以及垂懸指針:

內(nèi)存泄漏: 內(nèi)存空間在使用完畢后未釋放
垂懸指針(迷途指針): 當(dāng)所指向的對(duì)象被釋放或者收回赃蛛,但是對(duì)該指針沒有作任何的修改恃锉,以至于該指針仍舊指向已經(jīng)回收的內(nèi)存地址

有GC的語言(比如Java、Python呕臂、 Lisp 破托、Perl、Ruby歧蒋、Haskell)土砂,自動(dòng)管理內(nèi)存州既;省去手動(dòng)管理內(nèi)存的麻煩,減少因內(nèi)存分配引發(fā)的Bug萝映。

既然GC都幫我們管理好了內(nèi)存吴叶,為啥我們還要學(xué)習(xí)GC?是不是可以不用學(xué)習(xí)GC了序臂?
——不蚌卤,GC是Java程序員進(jìn)階必備知識(shí)之一。雖然GC能自動(dòng)幫我們管理內(nèi)存奥秆,但是GC不是萬能的逊彭,GC知識(shí)一個(gè)輔助工具,一旦程序出現(xiàn)了內(nèi)存方面的BUG构订,如果我們不理解GC機(jī)制侮叮,就很難定位BUG,并且悼瘾,有效的整理GC囊榜,可以提高程序的運(yùn)行效率。所以分尸,我們需要學(xué)習(xí)GC锦聊。

2 對(duì)象的生存判定

既然GC要回收對(duì)象,那么GC就需要判定對(duì)象是否存活箩绍,這就是對(duì)象的生存判定孔庭。

我們先來簡單研究一下GC中對(duì)象的結(jié)構(gòu)。

對(duì)象這個(gè)詞材蛛,在不同的使用場合其意思各不相同圆到。比如,在面向?qū)ο缶幊讨斜翱裕浮熬哂袑傩院托袨榈氖挛铩毖康欢?GC 的世界中,對(duì)象表示的是“通過應(yīng)用程序利用的數(shù)據(jù)的集合”豆赏。 ——《垃圾回的算法與實(shí)現(xiàn)》

我們將對(duì)象中保存對(duì)象本身信息的部分稱為“頭”挣菲。頭主要含有以下信息:對(duì)象的基本信息(大小、種類等)掷邦、哈希碼白胀、GC分代年齡、線程持有鎖等抚岗。
對(duì)象頭可能包含類型指針或杠,通過該指針能確定對(duì)象屬于哪個(gè)類。如果對(duì)象是一個(gè)數(shù)組宣蔚,那么對(duì)象頭還會(huì)包括數(shù)組長度向抢。

我們把對(duì)象使用者在對(duì)象中可訪問的部分稱為“域”认境,域中可以分為指針和非指針。

圖2.1

注:在Hotspot中挟鸠,將對(duì)象分為對(duì)象頭叉信,實(shí)例數(shù)據(jù)以及對(duì)齊填充(可選)如圖2.2:

圖2.2

2.1引用計(jì)數(shù)法

GC是釋放無法被引用的對(duì)象的機(jī)制,那么我們可不可以記錄下有多少指針指向自己兄猩?記錄下對(duì)象的人氣指數(shù)茉盏,從而讓沒有人氣的對(duì)象消失——這就是引用計(jì)數(shù)法。

引用計(jì)數(shù)法的每個(gè)對(duì)象的對(duì)象頭中有一個(gè)計(jì)數(shù)器枢冤,如圖2.3,記錄著這個(gè)對(duì)象被引用的次數(shù)铜秆,當(dāng)這個(gè)計(jì)數(shù)器的值為0時(shí)淹真,就會(huì)被判定為無用的對(duì)象,等待被回收连茧。(下一次GC觸發(fā)時(shí)就會(huì)回收這個(gè)對(duì)象)

圖2.3

如圖:將A指向B的引用改為A指向C核蘸,B的計(jì)數(shù)器就為0,就會(huì)回收B所占的內(nèi)存啸驯。(如使用“空閑鏈表”的內(nèi)存分配方法就會(huì)加入空閑鏈表)客扎。


image.png

新建對(duì)象時(shí),會(huì)初始化對(duì)象的計(jì)數(shù)器為1:

new_obj(size){
  obj = pickup_chunk(size, $free_list)  //遍歷空閑鏈表$free_list罚斗,尋找>=size的分塊
  if(obj == NULL)
    allocation_fail()//分配失敗徙鱼,銷毀至今為止所有計(jì)算成功
  else
    obj.ref_cnt = 1
    return obj
}

更新對(duì)象指針時(shí):

//更新指針 ptr 指向?qū)ο?obj,并更新計(jì)數(shù)器
update_ptr(ptr, obj){
  inc_ref_cnt(obj)   // 先新引用對(duì)象的計(jì)數(shù)器+1
  dec_ref_cnt(*ptr) //后舊引用對(duì)象的計(jì)數(shù)器-1
  *ptr = obj
}
// 增加對(duì)象計(jì)數(shù)器值
inc_ref_cnt(obj){
  obj.ref_cnt++
}
// 減少對(duì)象計(jì)數(shù)器值
dec_ref_cnt(obj){
  obj.ref_cnt--
  if(obj.ref_cnt == 0)
    for(child : children(obj))
      dec_ref_cnt(*child)
    reclaim(obj)
}

為什么要先調(diào)用 inc_ref_cnt() 函數(shù)针姿,后調(diào)用dec_ref_cnt() 函數(shù)呢袱吆?
從引用計(jì)數(shù)算法的角度來考慮,先調(diào)用 dec_ref_cnt() 函數(shù)距淫,后調(diào)用 inc_ref_cnt() 函數(shù)才合適吧绞绒。答案就是“為了處理 ptr 和 obj 是同一對(duì)象時(shí)的情況”。如果按照先 dec_ref_cnt() 后 inc_ref_cnt() 函數(shù)的順序調(diào)用榕暇,ptr 和 obj 又是同一對(duì)象的話蓬衡,執(zhí)行 dec_ref_cnt(*ptr) 時(shí) ptr 的計(jì)數(shù)器的值就有可能變?yōu)?0 而被回收。這樣一來彤枢,下面再想執(zhí)行 inc_ref_cnt(obj) 時(shí) obj 早就被回收了狰晚,可能會(huì)引發(fā)重大的 BUG。因此我們要通過先對(duì) obj 的計(jì)數(shù)器進(jìn)行增量操作來回避這種 BUG堂污。

引用計(jì)數(shù)器雖然簡單家肯,但是有著一些不可避免的缺點(diǎn):

  • 計(jì)數(shù)器的寬度應(yīng)該設(shè)置為多少合適?
    計(jì)數(shù)器位數(shù)太少盟猖,當(dāng)存在被多次引用的對(duì)象時(shí)讨衣,就會(huì)溢出换棚;當(dāng)計(jì)數(shù)器位數(shù)太多,如果存在很多占空間比較小的對(duì)象時(shí)反镇,空間利用率就大大下降固蚤。
  • 引用計(jì)數(shù)器最大的缺點(diǎn)就是循環(huán)引用無法回收。
class Person{                      // 定義Person類
  string name                      // 
  Person lover                     // 
}
taro = new Person("太郎 ")          // 生成 Person類的實(shí)例太郎
hanako = new Person("花子 ")        // 生成 Person類的實(shí)例花子
taro.lover = hanako                 // 太郎喜歡花子
hanako.lover = taro                 // 花子喜歡太郎
taro = null                         // 將 taro轉(zhuǎn)換為 null 
hanako = null                       // 將 hanako轉(zhuǎn)換為 null
圖2.4

如圖2.4歹茶,因?yàn)閮蓚€(gè)對(duì)象互相引用夕玩,所以各對(duì)象的計(jì)數(shù)器的值都是 1。但是這些對(duì)象組并沒有被其他任何對(duì)象引用惊豺。因此想一并回收這兩個(gè)對(duì)象都不行燎孟,只要它們的計(jì)數(shù)器值都是 1,就無法回收尸昧。

2.2可達(dá)性分析

另一種對(duì)象生存判定的方法就是可達(dá)性分析揩页;其基本思想是:通過一系列的“GC Roots”對(duì)象作為起點(diǎn)進(jìn)行搜索(搜索走過的路徑稱為“引用鏈”),如果在“GC Roots”和一個(gè)對(duì)象之間沒有可達(dá)路徑烹俗,則稱該對(duì)象是不可達(dá)的爆侣,比如圖2.5中的object5、object6幢妄、object7兔仰。


圖2.5 可達(dá)性分析

在Java語言中,可作為GC Roots的節(jié)點(diǎn)主要在執(zhí)行上下文(例如棧幀中的本地變量表)與全局性的引用(例如常量或類靜態(tài)屬性)中蕉鸳,可分為以下幾種:

  • 虛擬機(jī)棧(棧楨中的本地變量表)中的引用的對(duì)象
  • 本地方法棧中JNI的引用的對(duì)象
  • 方法區(qū)中的類靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)中的常量引用的對(duì)象

不過要注意的是被判定為不可達(dá)的對(duì)象不一定就會(huì)成為可回收對(duì)象乎赴。被判定為不可達(dá)的對(duì)象要成為可回收對(duì)象必須至少經(jīng)歷兩次標(biāo)記過程,如果在這兩次標(biāo)記過程中仍然沒有逃脫成為可回收對(duì)象的可能性置吓,則基本上就真的成為可回收對(duì)象了无虚。

2.3 一些題外話補(bǔ)充

2.3.1兩次標(biāo)記過程

標(biāo)記的前提是對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈。
1).第一次標(biāo)記并進(jìn)行一次篩選衍锚。
篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法友题。
當(dāng)對(duì)象沒有覆蓋finalize方法,或者finzlize方法已經(jīng)被虛擬機(jī)調(diào)用過戴质,虛擬機(jī)將這兩種情況都視為“沒有必要執(zhí)行”度宦,對(duì)象被回收。
2).第二次標(biāo)記
如果這個(gè)對(duì)象被判定為有必要執(zhí)行finalize()方法告匠,那么這個(gè)對(duì)象將會(huì)被放置在一個(gè)名為:F-Queue的隊(duì)列之中戈抄,并在稍后由一條虛擬機(jī)自動(dòng)建立的、低優(yōu)先級(jí)的Finalizer線程去執(zhí)行后专。這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)觸發(fā)這個(gè)方法划鸽,但并不承諾會(huì)等待它運(yùn)行結(jié)束。這樣做的原因是,如果一個(gè)對(duì)象finalize()方法中執(zhí)行緩慢裸诽,或者發(fā)生死循環(huán)(更極端的情況)嫂用,將很可能會(huì)導(dǎo)致F-Queue隊(duì)列中的其他對(duì)象永久處于等待狀態(tài),甚至導(dǎo)致整個(gè)內(nèi)存回收系統(tǒng)崩潰丈冬。
Finalize()方法是對(duì)象脫逃死亡命運(yùn)的最后一次機(jī)會(huì)嘱函,稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模標(biāo)記,如果對(duì)象要在finalize()中成功拯救自己----只要重新與引用鏈上的任何的一個(gè)對(duì)象建立關(guān)聯(lián)即可埂蕊,譬如把自己賦值給某個(gè)類變量或?qū)ο蟮某蓡T變量往弓,那在第二次標(biāo)記時(shí)它將移除出“即將回收”的集合。如果對(duì)象這時(shí)候還沒逃脫蓄氧,那基本上它就真的被回收了函似。
如圖2.6:


圖 2.6

2.3.2 finalize()

上面的兩次標(biāo)記過程和finalize()方法有關(guān),所以我們來看一下finalize()方法喉童。

  • Finalize方法是什么缴淋?
    • finalize()是Object的protected方法,
    • 子類可以覆蓋該方法以實(shí)現(xiàn)資源清理工作泄朴,GC在回收對(duì)象之前調(diào)用該方法,在此方法中對(duì)象可以將自己與一個(gè)有效引用綁定露氮,這是對(duì)象自救的唯一方式
  • finalize()與C++中的析構(gòu)函數(shù)不是對(duì)應(yīng)的祖灰。
    • Java中的finalize的調(diào)用具有不確定性。并且不一定能夠執(zhí)行
    • C++中的析構(gòu)函數(shù)調(diào)用的時(shí)機(jī)是確定的(對(duì)象離開作用域或delete掉)

finalize()的使用:

  • 對(duì)象自救的唯一方式
public class FinalizeEscapeGC {  
    public static FinalizeEscapeGC SAVE_HOOK = null;  
    public static void main(String[] args) throws InterruptedException {  
        SAVE_HOOK = new FinalizeEscapeGC ();  
        SAVE_HOOK = null;  //1.第一次取消引用SAVE_HOOK與其實(shí)例的關(guān)聯(lián)
        System.gc();  //2.觸發(fā)GC
        Thread.sleep(500);  
        if (null != SAVE_HOOK) {//3.finalize()是否執(zhí)行
            System.out.println(“Yes , I am still alive”); //4.對(duì)象執(zhí)行了finalize()方法
        } else {  
            System.out.println("No , I am dead");  
        }  
        SAVE_HOOK = null; //5.二次.取消關(guān)聯(lián)
        System.gc(); 6.二次觸發(fā)GC
        Thread.sleep(500);  
        if (null != SAVE_HOOK) {//7.finalize()是否執(zhí)行
            System.out.println("Yes , I am still alive");  
        } else {  
            System.out.println(“No , I am dead”); //8.finalize()最多執(zhí)行一次
        }  
    }  
    @Override  
    protected void finalize() throws Throwable {  
        super.finalize();  
        System.out.println("execute method finalize()");  
        SAVE_HOOK = this;  
    }  
} 

輸出

execute method finalize()
Yes , I am still alive
No , I am dead
  • 清理本地對(duì)象(通過JNI創(chuàng)建的對(duì)象)畔规;或作為確保某些非內(nèi)存資源釋放的一個(gè)補(bǔ)充局扶。如FileInputStream中的一段代碼:
    /**
     * Ensures that the <code>close</code> method of this file input stream is
     * called when there are no more references to it.
     *
     * @exception  IOException  if an I/O error occurs.
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            /* if fd is shared, the references in FileDescriptor
             * will ensure that finalizer is only called when
             * safe to do so. All references using the fd have
             * become unreachable. We can call close()
             */
            close();
        }
    }
}

類似的用法還存在于FileOutPutStream、Connection類中叁扫。

2.3.3 引用

對(duì)象的存活和對(duì)象是否被引用有關(guān)三妈,下面我們來看一下java中的引用。引用可以簡要定義為:

數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址莫绣,就稱這塊內(nèi)存代表著一個(gè)引用畴蒲。

但是java中定義了四大引用攒驰,除了強(qiáng)引用外都是Reference類的子類

  • 強(qiáng)引用
  • 軟引用
  • 弱引用
  • 虛引用


    image.png
  1. 強(qiáng)引用在程序內(nèi)存不足(OOM)的時(shí)候也不會(huì)被回收
public class MyReferenceTest {

    public static final int _1M =  1024 * 1024;
    // -Xms10m  -Xmx10m
    public static void main(String[] args) {
        // 使用List保證著對(duì)象的引用辱士,避免因?yàn)闊o法引用而被GC
        List<Byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new Byte[_1M]);
            System.gc();
        }
    }
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.seaxll.learn.jvm.reference.MyReferenceTest.main(MyReferenceTest.java:25)

Process finished with exit code 1

像這樣直接new一個(gè)對(duì)象就是一個(gè)強(qiáng)引用甚颂,這里既是發(fā)生了OOM異常也不收集這部分垃圾玩敏。

  1. 當(dāng)內(nèi)存不足時(shí)充蓝,回收軟引用:它的作用是告訴垃圾回收器芽突,程序中的哪些對(duì)象是不那么重要忧便,當(dāng)內(nèi)存不足的時(shí)候是可以被回收的青灼。
public class SoftReferenceTest {

    public static final int _1M = 1024 * 1024;

    // -Xms10m -Xmx10m
    public static void main(String[] args) {
        SoftReference<Byte[]> softReference = new SoftReference<>(new Byte[_1M]);

        // 使用List保證著對(duì)象的引用
        List<Byte[]> list = new ArrayList<>();
        while (true) {
            if (softReference.get() != null) {
                list.add(new Byte[_1M]);
                System.out.println("list.add");
            } else {
                System.out.println("---------軟引用已被回收---------");
                break;
            }
            System.gc();
        }
    }
}
list.add
list.add
list.add
list.add
list.add
list.add
list.add
---------軟引用已被回收---------

軟引用非常適合于創(chuàng)建緩存牺汤。當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候辽旋,緩存中的內(nèi)容是可以被釋放的。

  1. 弱引用就是只要JVM垃圾回收器發(fā)現(xiàn)了它,就會(huì)將之回收
    弱引用只能生存到下一次GC补胚,作用在于解決強(qiáng)引用所帶來的對(duì)象之間在存活時(shí)間上的耦合關(guān)系码耐。
public class WeakReferenceTest {
    static class TestObject {
    }
    public static void main(String[] args) throws InterruptedException {
        WeakReference<TestObject> weakReference =
                new WeakReference<>(new TestObject());
        System.out.println(weakReference.get() == null);//false
        System.gc();
        TimeUnit.SECONDS.sleep(1);//暫停一秒鐘
        System.out.println(weakReference.get() == null);//true
    }
}
false
true

Process finished with exit code 0

弱引用最常見的用處是在集合類中,尤其在哈希表中糖儡。(WeakHashMap伐坏、ThreadLocal)

  1. 虛(幽靈)引用的回收機(jī)制跟弱引用差不多,但它不能單獨(dú)使用握联,虛必須和引用隊(duì)列聯(lián)合使用桦沉。虛引用的主要作用是跟蹤對(duì)象被垃圾回收的狀態(tài),大多被用于引用銷毀前的處理工作金闽。不能通過虛引用獲取到關(guān)聯(lián)對(duì)象纯露,只是用于獲取對(duì)象被回收的通知。
public class PhantomReferenceTest {
    public static void main(String[] args) throws Exception {
        String str = new String("SuperMap");
        ReferenceQueue rq = new ReferenceQueue();
        PhantomReference pr = new PhantomReference(str, rq);
        str = null;
        System.out.println(pr.get()); // null
        System.gc();
        System.runFinalization();
        System.out.println(rq.poll() == pr);    // true
    }
}
null
true

Process finished with exit code 0

如下代芜,JDK中虛引用唯一的構(gòu)造函數(shù)必須傳入一個(gè)ReferenceQueue

public class PhantomReference<T> extends Reference<T> {

    /**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }

    /**
     * Creates a new phantom reference that refers to the given object and
     * is registered with the given queue.
     *
     * <p> It is possible to create a phantom reference with a <tt>null</tt>
     * queue, but such a reference is completely useless: Its <tt>get</tt>
     * method will always return null and, since it does not have a queue, it
     * will never be enqueued.
     *
     * @param referent the object the new phantom reference will refer to
     * @param q the queue with which the reference is to be registered,
     *          or <tt>null</tt> if registration is not required
     */
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}

3.垃圾回收算法

了解了GC如何判定存活埠褪,我們?cè)賮砜匆幌吕厥账惴ǎS玫睦厥账惴ㄓ腥N:

  • 標(biāo)記清除算法
  • 標(biāo)記整理算法
  • 復(fù)制算法

3.1標(biāo)記清除算法

  • 算法描述:
    • 先標(biāo)記出所有需要回收的對(duì)象(圖中深色區(qū)域)挤庇;
    • 標(biāo)記完后钞速,統(tǒng)一回收所有被標(biāo)記對(duì)象(留下狗啃似的可用內(nèi)存區(qū)域……)。
  • 不足:
    • 效率問題:標(biāo)記和清理兩個(gè)過程的效率都不高嫡秕。
    • 空間碎片問題:標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片渴语,導(dǎo)致以后為較大的對(duì)象分配內(nèi)存時(shí)找不到足夠的連續(xù)內(nèi)存,會(huì)提前觸發(fā)另一次 GC昆咽。如圖3.1即使剩余16塊空間驾凶,但是卻無法完整的分配大小為3的對(duì)象。


      圖3.1

3.2解決碎片化——標(biāo)記整理算法

  • 算法描述:

    • 標(biāo)記方法與 “標(biāo)記 - 清除算法” 一樣掷酗;

    • 標(biāo)記完后调违,將所有存活對(duì)象向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存泻轰。

  • 不足: 存在效率問題技肩,適合老年代。

圖3.2 標(biāo)記整理算法

3.3解決效率問題——復(fù)制算法

  • 算法描述:
  • 將可用內(nèi)存分為大小相等的兩塊糕殉,每次只使用其中一塊亩鬼;
    • 當(dāng)一塊內(nèi)存用完時(shí),將這塊內(nèi)存上還存活的對(duì)象復(fù)制到另一塊內(nèi)存上去阿蝶,將這一塊內(nèi)存全部清理掉雳锋。
  • 不足: 可用內(nèi)存縮小為原來的一半,適合GC過后只有少量對(duì)象存活的新生代羡洁。
    圖3.3 復(fù)制算法

3.4 回收策略——分代回收算法

  • 新生代: GC 過后只有少量對(duì)象存活 —— 修改的復(fù)制算法
  • 老年代: GC 過后對(duì)象存活率高 —— 標(biāo)記 - 整理算法

新生代改進(jìn)版復(fù)制算法:

  • 新生代中的對(duì)象 98% 都是朝生夕死的玷过,所以不需要按照 1:1 的比例對(duì)內(nèi)存進(jìn)行劃分;
  • 把內(nèi)存劃分為:
    • 1 塊比較大的 Eden 區(qū);
    • 2 塊較小的 Survivor 區(qū)辛蚊;
  • 說明:
    • 每次使用 Eden 區(qū)和 1 塊 Survivor 區(qū)粤蝎;
    • 回收時(shí),將以上 2 部分區(qū)域中的存活對(duì)象復(fù)制到另一塊 Survivor 區(qū)中袋马,然后將以上兩部分區(qū)域清空初澎;
    • JVM 參數(shù)設(shè)置:-XX:SurvivorRatio=8 表示 Eden 區(qū)大小 / 1 塊 Survivor 區(qū)大小 = 8
  • 好處:
    • 減少進(jìn)入老年代的幾率虑凛,降低Full GC 觸發(fā)的頻率(Full GC的觸發(fā)伴隨著Stop The World)

如下圖:為一個(gè)新生代搜集的大致流程碑宴,當(dāng)對(duì)象年齡到達(dá)一定年齡(默認(rèn)為16)時(shí),會(huì)將其升代到老年代桑谍。并且延柠,大對(duì)象也會(huì)直接分配到老年代。


圖3.5 新生代的復(fù)制算法

新生代的GC是一個(gè)循環(huán)的過程:
Eden + From -> To 然后 交換From 與 To
Eden + From -> To 然后 交換From 與 To
..........
或理解為:
Eden + S0 -> S1
Eden + S1 -> S0

Eden + S0 -> S1
Eden + S1 -> S0
...........

4.HotSpot 中 GC 算法的實(shí)現(xiàn)

通過前對(duì)于判斷對(duì)象生死和垃圾收集算法的介紹锣披,我們已經(jīng)對(duì)虛擬機(jī)進(jìn)行 GC 的流程有了一個(gè)大致的了解贞间。但是,在 HotSpot 虛擬機(jī)中雹仿,高效的實(shí)現(xiàn)這些算法也是一個(gè)需要考慮的問題增热。所以,接下來胧辽,我們將研究一下 HotSpot 虛擬機(jī)到底是如何高效的實(shí)現(xiàn)這些算法的钓葫,以及在實(shí)現(xiàn)中有哪些需要注意的問題。

通過之前的分析票顾,GC 算法的實(shí)現(xiàn)流程簡單的來說分為以下兩步:

  1. 找到死掉的對(duì)象;
  2. 把它清了帆调。

想要找到死掉的對(duì)象奠骄,我們就要進(jìn)行可達(dá)性分析,也就是從 GC Root 找到引用鏈的這個(gè)操作番刊。

也就是說含鳞,進(jìn)行可達(dá)性分析的第一步,就是要枚舉 GC Roots芹务,這就需要虛擬機(jī)知道哪些地方存放著對(duì)象引用蝉绷。如果每一次枚舉 GC Roots 都需要把整個(gè)棧上位置都遍歷一遍,那可就費(fèi)時(shí)間了枣抱,畢竟并不是所有位置都存放著引用熔吗。所以為了提高 GC 的效率,HotSpot 使用了一種 OopMap 的數(shù)據(jù)結(jié)構(gòu)佳晶,OopMap 記錄了棧上本地變量到堆上對(duì)象的引用關(guān)系桅狠,也就是說,GC 的時(shí)候就不用遍歷整個(gè)棧只遍歷每個(gè)棧的 OopMap 就行了。

在 OopMap 的幫助下中跌,HotSpot 可以快速準(zhǔn)確的完成 GC 枚舉了咨堤,不過,OopMap 也不是萬年不變的漩符,它也是需要被更新的一喘,當(dāng)內(nèi)存中的對(duì)象間的引用關(guān)系發(fā)生變化時(shí),就需要改變 OopMap 中的相應(yīng)內(nèi)容嗜暴⊥箍耍可是能導(dǎo)致引用關(guān)系發(fā)生變化的指令非常之多,如果我們執(zhí)行完一條指令就改下 OopMap灼伤,這 GC 成本實(shí)在太高了触徐。

因此,HotSpot 采用了一種在 “安全點(diǎn)” 更新 OopMap 的方法狐赡,安全點(diǎn)的選取既不能讓 GC 等待的時(shí)間過長撞鹉,也不能過于頻繁增加運(yùn)行負(fù)擔(dān),也就是說颖侄,我們既要讓程序運(yùn)行一段時(shí)間鸟雏,又不能讓這個(gè)時(shí)間太長。我們知道览祖,JVM 中每條指令執(zhí)行的是很快的孝鹊,所以一個(gè)超級(jí)長的指令流也可能很快就執(zhí)行完了,所以 真正會(huì)出現(xiàn) “長時(shí)間執(zhí)行” 的一般是指令的復(fù)用展蒂,例如:方法調(diào)用又活、循環(huán)跳轉(zhuǎn)、異常跳轉(zhuǎn)等锰悼,虛擬機(jī)一般會(huì)將這些地方設(shè)置為安全點(diǎn)更新 OopMap 并判斷是否需要進(jìn)行 GC 操作柳骄。

此外,在進(jìn)行枚舉根節(jié)點(diǎn)的這個(gè)操作時(shí)箕般,為了保證準(zhǔn)確性耐薯,我們需要在一段時(shí)間內(nèi) “凍結(jié)” 整個(gè)應(yīng)用,即 Stop The World(傳說中的 GC 停頓)丝里,因?yàn)槿绻谖覀兎治隹蛇_(dá)性的過程中曲初,對(duì)象的引用關(guān)系還在變來變?nèi)ィ鞘遣豢赡艿玫秸_的分析結(jié)果的杯聚。即便是在號(hào)稱幾乎不會(huì)發(fā)生停頓的 CMS 垃圾收集器中臼婆,枚舉根節(jié)點(diǎn)時(shí)也是必須要停頓的。這里就涉及到了一個(gè)問題:

如何讓所有線程跑到最近的安全點(diǎn)再停頓下來進(jìn)行 GC 操作呢幌绍?

主要有以下兩種方式:

  • 搶先式中斷:

    • 先中斷所有線程目锭;

    • 發(fā)現(xiàn)有線程沒中斷在安全點(diǎn)评汰,恢復(fù)它,讓它跑到安全點(diǎn)痢虹。

  • 主動(dòng)式中斷: (主要使用)

    • 設(shè)置一個(gè)中斷標(biāo)記被去;

    • 每個(gè)線程到達(dá)安全點(diǎn)時(shí),檢查這個(gè)中斷標(biāo)記奖唯,選擇是否中斷自己惨缆。

除此安全點(diǎn)之外,還有一個(gè)叫做 “安全區(qū)域” 的東西丰捷,一個(gè)一直在執(zhí)行的線程可以自己 “走” 到安全點(diǎn)去坯墨,可是一個(gè)處于 Sleep 或者 Blocked 狀態(tài)的線程是沒辦法自己到達(dá)安全點(diǎn)中斷自己的,我們總不能讓 GC 操作一直等著這些個(gè) ”不執(zhí)行“ 的線程重新被分配資源吧病往。對(duì)于這種情況捣染,我們要依靠安全區(qū)域來解決。

安全區(qū)域是指在一段代碼片段之中停巷,引用關(guān)系不會(huì)發(fā)生變化耍攘,因此在這個(gè)區(qū)域中的任意位置開始 GC 都是安全的。

當(dāng)線程執(zhí)行到安全區(qū)域時(shí)畔勤,它會(huì)把自己標(biāo)識(shí)為 Safe Region蕾各,這樣 JVM 發(fā)起 GC 時(shí)是不會(huì)理會(huì)這個(gè)線程的。當(dāng)這個(gè)線程要離開安全區(qū)域時(shí)庆揪,它會(huì)檢查系統(tǒng)是否在 GC 中式曲,如果不在,它就繼續(xù)執(zhí)行缸榛,如果在吝羞,它就等 GC 結(jié)束再繼續(xù)執(zhí)行。

至于如何清理垃圾内颗,我們來看一下垃圾收集器

4.1 七大垃圾收集器

垃圾收集器

4.1.1 新生代垃圾收集器

  1. Serial 收集器:最古老的的收集器脆贵,一種單線程收集器
    單線程的意義并不僅僅說明它只會(huì)使用一個(gè)CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進(jìn)行垃圾收集時(shí)起暮,必須暫停其他所有的工作線程,直到它收集結(jié)束会烙;


    圖4.1Serial垃圾收集器
  • 優(yōu)點(diǎn):簡單高效负懦,Client模式下新生代收集器常用
  • 缺點(diǎn):GC時(shí)Stop The World,用戶體驗(yàn)感差柏腻。
  1. ParNew 收集器:多線程收集器纸厉,Serial 收集器的多線版本


    圖4.2 ParNew收集器
  • 優(yōu)點(diǎn):多CPU環(huán)境收集效率比Serial收集器強(qiáng), 單CPU下線程切換開銷會(huì)降低其效率.
  • 缺點(diǎn):GC時(shí)Stop The World,用戶體驗(yàn)感差
  1. Parallel Scavenge 收集器:專注于吞吐量控制的多線程收集器
    無法與老年代的CMS收集器搭配工作五嫂,Parallel Old出現(xiàn)之前颗品,只能與Serial Old搭配使用
    吞吐量:CPU用于運(yùn)行用戶代碼的時(shí)間與CPU消耗的總時(shí)間(用戶+GC)的比值

可以通過參數(shù)來打開自適應(yīng)調(diào)節(jié)策略:

  • -XX:MaxGCPauseMillis :最大停頓的時(shí)間肯尺,但最大停頓時(shí)間過短必然會(huì)導(dǎo)致新生代的內(nèi)存大小變小,垃圾回收頻率變高躯枢,效率可能降低则吟。
  • -XX:CGTIMERatio :吞吐量大小(0-100)锄蹂,默認(rèn)為99氓仲。
  • -XX:+UseAdaptiveSizePolicy:自動(dòng)調(diào)節(jié)開關(guān)
圖4.3 Parallel Scavenge收集器
  • 優(yōu)點(diǎn):可以精確控制吞吐量
  • 缺點(diǎn):可能會(huì)停頓時(shí)間變短, 但收集次數(shù)變多.原本10s收集一次, 每次停頓100ms, 設(shè)置完參數(shù)之后可能變成5s收集一次, 每次停頓70ms.

為什么Parallel Scavenge不能與CMS搭配使用?

  • HotSpot VM里多個(gè)GC有部分共享的代碼得糜。有一個(gè)分代式GC框架敬扛,Serial/Serial Old/ParNew/CMS都在這個(gè)框架內(nèi);在該框架內(nèi)的young collector和old collector可以任意搭配使用而ParallelScavenge與G1則不在這個(gè)框架內(nèi)朝抖,而是各自采用了自己特別的框架啥箭。這是因?yàn)樾碌腉C實(shí)現(xiàn)時(shí)發(fā)現(xiàn)原本的分代式GC框架用起來不順手。
    Parallel Scavenge沒有使用原本HotSpot其它GC通用的那個(gè)GC框架治宣,所以不能跟使用了那個(gè)框架的CMS搭配使用急侥。

4.1.2 老年代垃圾收集器

老年代的收集器有部分新生代對(duì)應(yīng)的:如

  1. Serial Old——對(duì)應(yīng)新生代的Serial,都是串行收集器


    圖4.4 Serial Old
  • 優(yōu)點(diǎn):簡單高效
  • 缺點(diǎn):停頓時(shí)間長
  1. Parallel Old——對(duì)應(yīng)新生代的Parallel Scavenge收集器炼七,都是關(guān)注吞吐量的多線程收集器


    圖 4.5 Parallel Scavenge
  • 優(yōu)點(diǎn):
    1.多線程收集
    2.彌補(bǔ)了之前Parallel Scavenge + Serial Old的尷尬組合.
  • 缺點(diǎn):GC時(shí)停頓
  1. CMS收集器( 重視服務(wù)的響應(yīng)速度)

執(zhí)行過程分為四個(gè)階段:

  • 初始標(biāo)記:標(biāo)記老年代中所有的GC Roots對(duì)象和年輕代中活著的對(duì)象引用到的老年代的對(duì)象缆巧,時(shí)間短;
  • 并發(fā)標(biāo)記:從“初始標(biāo)記”階段標(biāo)記的對(duì)象開始找出所有存活的對(duì)象豌拙;
  • 重新標(biāo)記:用來處理前一個(gè)階段因?yàn)橐藐P(guān)系改變導(dǎo)致沒有標(biāo)記到的存活對(duì)象陕悬,時(shí)間短;
  • 并發(fā)清理:清除那些沒有標(biāo)記的對(duì)象并且回收空間按傅。
圖4.6 CMS
  • 優(yōu)勢:并發(fā)捉超、低停頓
  • 缺點(diǎn):
    1.CMS對(duì)CPU資源非常敏感
    2.CMS無法收集浮動(dòng)垃圾
    3.使用標(biāo)記-清除算法,產(chǎn)生碎片

4.1.3 G1收集器(Garbage First)

從最早的串行到高頓吞吐量的并行唯绍,為了解決高延遲又演化出了CMS(Concurrent Mark Sweep)拼岳,為了解決碎片的問題,后來又開發(fā)了G1.


圖4.7 G1

相比CMS收集器, G1收集器有兩個(gè)改進(jìn)點(diǎn)

  1. G1基于標(biāo)記-整理算法, 不會(huì)產(chǎn)生空間碎片
  2. G1可以精確控制停頓, 能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段內(nèi), 消耗在垃圾收集器上的時(shí)間不得超過N毫秒.

G1收集器和CMS收集器類似况芒,分為四個(gè)步驟

  • 初始標(biāo)記:僅僅只是標(biāo)記一下GC. Roots能直接關(guān)聯(lián)到的對(duì)象惜纸,且修改TAMs( Next Top at Mark, Start)的值,讓下一階段用戶程序并發(fā)運(yùn)行時(shí)绝骚,能在正確可用的 Region中創(chuàng)建新對(duì)象耐版,這階段需要停頓線程,但耗時(shí)很短
  • 并發(fā)標(biāo)記:是從 GC Root開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析压汪,找出存活的對(duì)象粪牲,這階段耗時(shí)較長,但可與用戶程序并發(fā)執(zhí)行止剖。
  • 最終標(biāo)記:修正在并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那部分標(biāo)記記錄腺阳;虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程 Remembered Set. Logs里面落君,最終標(biāo)記階段需要把 Remembered Set Logs的數(shù)據(jù)合并到 Remembered Set p中,這階段需要停頓線程亭引,但是可并行執(zhí)行绎速。
  • 篩選回收:首先對(duì)各個(gè) Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時(shí)間來制定回收計(jì)劃痛侍,從Sun公司透露出來的信懸來看朝氓,這個(gè)階段其實(shí)也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因?yàn)橹换厥杖糠?Region主届,時(shí)間是用戶可控制的赵哲,而且停頓用戶線程將大幅提高收集效率。

G1的優(yōu)點(diǎn):

  • 并行(多CPU)與并發(fā)
  • 分代收集(新生代和老年代區(qū)分不明顯)適用于整個(gè)堆區(qū)君丁。
  • 空間整合
  • 限制收集范圍枫夺,可預(yù)測的停頓

4.2 垃圾收集器的搭配使用

  • UseSerialGC是“ Serial” +“ Serial Old”
  • UseParNewGC是“ ParNew” +“ Serial Old”
  • UseConcMarkSweepGC是“ ParNew” +“ CMS” +“ Serial Old”。大多數(shù)情況下绘闷,使用“ CMS”來收集保有權(quán)代橡庞。并發(fā)模式失敗時(shí)使用“ Serial Old”。
  • UseParallelGC是“ Parallel Scavenge” +“ Serial Old”
  • UseParallelOldGC是“ Parallel Scavenge” +“ Parallel Old”

5.內(nèi)存調(diào)試工具

圖5-1 命令行調(diào)試工具

jps印蔗、jstat扒最、jinfo、jstack用得較多华嘹,另外兩個(gè)少用吧趣,所以這里記錄四種常用的

5.1 命令行調(diào)試工具

5.1.1 jps 虛擬機(jī)進(jìn)程狀態(tài)工具

命令格式:jps [options ] [ hostid ]

[options]選項(xiàng) :
-q:僅輸出VM標(biāo)識(shí)符,
-m:輸出main method的參數(shù)
-l:輸出完全的包名耙厚,應(yīng)用主類名强挫,jar的完全路徑名
-v:輸出jvm參數(shù)
-V:輸出通過flag文件傳遞到JVM中的參數(shù)
-Joption:傳遞參數(shù)到vm,例如:-J-Xms512m

image.png

5.1.2 jstat 虛擬機(jī)統(tǒng)計(jì)信息監(jiān)視器

命令格式:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

option:

  • class:統(tǒng)計(jì)classloader的行為
  • compiler:統(tǒng)計(jì)hotspot just-in-time編譯器的行為
  • gc:統(tǒng)計(jì)gc行為
  • gccapacity:統(tǒng)計(jì)堆中代的容量、空間
  • gccause:垃圾收集統(tǒng)計(jì)薛躬,包括最近引用垃圾收集的事件俯渤,基本同gcutil,比gcutil多了兩列
  • gcnew:統(tǒng)計(jì)新生代的行為
  • gcnewcapacity:統(tǒng)計(jì)新生代的大小和空間
  • gcold:統(tǒng)計(jì)舊生代的行為
  • gcoldcapacity:統(tǒng)計(jì)舊生代的大小和空間
  • gcpermcapacity:統(tǒng)計(jì)永久代的大小和空間
  • gcutil:垃圾收集統(tǒng)計(jì)
  • printcompilation:hotspot編譯方法統(tǒng)計(jì)

其他選項(xiàng)

  • -h n 每n個(gè)樣本型宝,顯示header一次
  • -t n 在第一列顯示時(shí)間戳列八匠,時(shí)間戳?xí)r從jvm啟動(dòng)開始計(jì)算
  • <vmid> 就是進(jìn)程號(hào)
  • <interval> interval是監(jiān)控時(shí)間間隔,單位為微妙趴酣,不提供就意味著單次輸出
  • <count> count是最大輸出次數(shù),不提供且監(jiān)控時(shí)間間隔有值的話梨树, 就無限打印
image.png

內(nèi)存分析規(guī)律總結(jié):
S0/S1:幸存者0/1區(qū);E:Eden价卤;O:Old;P:永久代渊涝;M:元空間…

  • 默認(rèn)為已使用的占當(dāng)前容量百分比
  • 加C:總?cè)萘可麒担瑔挝蛔止?jié)
  • 加U:已使用容量床嫌,單位字節(jié)
  • 加MN:初始(最小)
  • 加MX:最大

GC/YGC/FGC/…

  • 默認(rèn)次數(shù)
  • 加T:所用時(shí)間
  • NGC/OGC/PGC…
  • 默認(rèn)大小
  • 加MN:初始(最行厮健)
  • 加MX:最大

其他

  • DSS:當(dāng)前需要survivor(幸存區(qū))的容量 (字節(jié))(Eden區(qū)已滿)
  • TT: 持有次數(shù)限制
  • MTT : 最大持有次數(shù)限制

比如下面詳細(xì)的一些說明:

S0C:年輕代中第一個(gè)survivor(幸存區(qū))的容量 (字節(jié))
S1C:年輕代中第二個(gè)survivor(幸存區(qū))的容量 (字節(jié))
S0U:年輕代中第一個(gè)survivor(幸存區(qū))目前已使用空間 (字節(jié))
S1U:年輕代中第二個(gè)survivor(幸存區(qū))目前已使用空間 (字節(jié))
EC:年輕代中Eden(伊甸園)的容量 (字節(jié))
EU:年輕代中Eden(伊甸園)目前已使用空間 (字節(jié))
OC:Old代的容量 (字節(jié))
OU:Old代目前已使用空間 (字節(jié))
PC:Perm(持久代)的容量 (字節(jié))
PU:Perm(持久代)目前已使用空間 (字節(jié))
YGC:從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中g(shù)c次數(shù)
YGCT:從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中g(shù)c所用時(shí)間(s)
FGC:從應(yīng)用程序啟動(dòng)到采樣時(shí)old代(全gc)gc次數(shù)
FGCT:從應(yīng)用程序啟動(dòng)到采樣時(shí)old代(全gc)gc所用時(shí)間(s)
GCT:從應(yīng)用程序啟動(dòng)到采樣時(shí)gc用的總時(shí)間(s)
NGCMN:年輕代(young)中初始化(最小)的大小 (字節(jié))
NGCMX:年輕代(young)的最大容量 (字節(jié))
NGC:年輕代(young)中當(dāng)前的容量 (字節(jié))
OGCMN:old代中初始化(最小)的大小 (字節(jié))
OGCMX:old代的最大容量 (字節(jié))
OGC:old代當(dāng)前新生成的容量 (字節(jié))
PGCMN:perm代中初始化(最小)的大小 (字節(jié))
PGCMX:perm代的最大容量 (字節(jié))
PGC:perm代當(dāng)前新生成的容量 (字節(jié))
S0:年輕代中第一個(gè)survivor(幸存區(qū))已使用的占當(dāng)前容量百分比
S1:年輕代中第二個(gè)survivor(幸存區(qū))已使用的占當(dāng)前容量百分比
E:年輕代中Eden(伊甸園)已使用的占當(dāng)前容量百分比
O:old代已使用的占當(dāng)前容量百分比
P:perm代已使用的占當(dāng)前容量百分比
S0CMX:年輕代中第一個(gè)survivor(幸存區(qū))的最大容量 (字節(jié))
S1CMX :年輕代中第二個(gè)survivor(幸存區(qū))的最大容量 (字節(jié))
ECMX:年輕代中Eden(伊甸園)的最大容量 (字節(jié))
DSS:當(dāng)前需要survivor(幸存區(qū))的容量 (字節(jié))(Eden區(qū)已滿)
TT: 持有次數(shù)限制
MTT : 最大持有次數(shù)限制

5.1.3 jinfo

Jinfo的作用是實(shí)時(shí)查看虛擬機(jī)的各項(xiàng)參數(shù)信息jps –v可以查看虛擬機(jī)在啟動(dòng)時(shí)被顯式指定的參數(shù)信息厌处,但是如果你想知道默認(rèn)的一些參數(shù)信息呢?除了去查詢對(duì)應(yīng)的資料以外岁疼,jinfo就顯得很重要了阔涉。

命令格式:jinfo [option] pid,詳細(xì)如圖


命令格式
  • pid: 對(duì)應(yīng)jvm的進(jìn)程id
  • executable core :產(chǎn)生core dump文件
  • [server-id@]remote server IP or hostname :遠(yuǎn)程的ip或者h(yuǎn)ostname捷绒,server-id標(biāo)記服務(wù)的唯一性id

option:

  • -flag name 輸出對(duì)應(yīng)名稱的參數(shù)
  • -flag [+|-]name 開啟或者關(guān)閉對(duì)應(yīng)名稱的參數(shù)
  • -flag name=value 設(shè)定對(duì)應(yīng)名稱的參數(shù)
  • -flags 輸出全部的參數(shù)
  • -sysprops 輸出系統(tǒng)屬性
  • no option 輸出全部的參數(shù)和系統(tǒng)屬性

Javacore瑰排,也可以稱為“threaddump”或是“javadump”,它是 Java 提供的一種診斷特性暖侨,能夠提供一份可讀的當(dāng)前運(yùn)行的 JVM 中線程使用情況的快照椭住。即在某個(gè)特定時(shí)刻,JVM 中有哪些線程在運(yùn)行字逗,每個(gè)線程執(zhí)行到哪一個(gè)類京郑,哪一個(gè)方法。
應(yīng)用程序如果出現(xiàn)不可恢復(fù)的錯(cuò)誤或是內(nèi)存泄露葫掉,就會(huì)自動(dòng)觸發(fā) Javacore 的生成些举。


image.png

如上圖舉例- flag參數(shù) :開啟了PrintDetails,沒有卡其PrintHeapAtGC參數(shù)

5.1.4 jstack

jstack,用于JVM當(dāng)前時(shí)刻的線程快照疆导,又稱threaddump文件闲孤,它是JVM當(dāng)前每一條線程正在執(zhí)行的堆棧信息的集合。生成線程快照的主要目的是為了定位線程出現(xiàn)長時(shí)間停頓的原因绪抛,如線程死鎖、死循環(huán)电禀、請(qǐng)求外部時(shí)長過長導(dǎo)致線程停頓的原因幢码。通過jstack我們就可以知道哪些進(jìn)程在后臺(tái)做些什么

  • -F:當(dāng)正常輸出的請(qǐng)求不響應(yīng)時(shí)強(qiáng)制輸出線程堆棧
  • -l:除堆棧信息外,顯示關(guān)于鎖的附加信息
  • -m:顯示native方法的堆棧信息

如下圖利用jstack分析死鎖


image.png

image.png
/**
 * 死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中尖飞,因爭奪資源而造成的一種互相等待的現(xiàn)象症副,若無外力干涉那他們都將無法推進(jìn)下去,
 */
public class Demo15_DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new HoldThread(lockA,lockB),"Thread-AAA").start();
        new Thread(new HoldThread(lockB,lockA),"Thread-BBB").start();

        /**
         * linux ps -ef|grep xxxx
         * windows下的java運(yùn)行程序也有類似ps的查看進(jìn)程的命令政基,但是目前我們需要查看的
         */
    }
}

class HoldThread implements Runnable {

    private String lockA;
    private String lockB;

    public HoldThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockA + "\t嘗試獲得:" + lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockB + "\t嘗試獲得:" + lockA);
            }
        }
    }
}

5.2 可視化工具

5.2.1 jconsole

運(yùn)行:


image.png

或雙擊


image.png

打開jconsole
image.png

可以選擇本地連接與遠(yuǎn)程連接贞铣,這里連接剛剛分析的產(chǎn)生死鎖的進(jìn)程,界面如下沮明,可以查看進(jìn)程概覽辕坝、各個(gè)區(qū)內(nèi)存使用情況、線程分析等


image.png

image.png

這里利用jconsole分析死鎖:


image.png

可以點(diǎn)擊線程窗口下面的檢測死鎖直接找到產(chǎn)生死鎖的線程


image.png

參考(推薦)書目

垃圾回收的算法與實(shí)現(xiàn)

深入理解Java虛擬機(jī)

謝謝觀看荐健,如果發(fā)現(xiàn)什么錯(cuò)誤以及疑問酱畅,歡迎指正琳袄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纺酸,隨后出現(xiàn)的幾起案子窖逗,更是在濱河造成了極大的恐慌,老刑警劉巖餐蔬,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碎紊,死亡現(xiàn)場離奇詭異,居然都是意外死亡樊诺,警方通過查閱死者的電腦和手機(jī)仗考,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啄骇,“玉大人痴鳄,你說我怎么就攤上這事「准校” “怎么了痪寻?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長虽惭。 經(jīng)常有香客問我橡类,道長,這世上最難降的妖魔是什么芽唇? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任顾画,我火速辦了婚禮,結(jié)果婚禮上匆笤,老公的妹妹穿的比我還像新娘研侣。我一直安慰自己,他們只是感情好炮捧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布庶诡。 她就那樣靜靜地躺著,像睡著了一般咆课。 火紅的嫁衣襯著肌膚如雪末誓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天书蚪,我揣著相機(jī)與錄音喇澡,去河邊找鬼。 笑死殊校,一個(gè)胖子當(dāng)著我的面吹牛晴玖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼呕屎,長吁一口氣:“原來是場噩夢啊……” “哼宪萄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起榨惰,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎静汤,沒想到半個(gè)月后琅催,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虫给,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年藤抡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抹估。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缠黍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出药蜻,到底是詐尸還是另有隱情瓷式,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布语泽,位于F島的核電站贸典,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏踱卵。R本人自食惡果不足惜廊驼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惋砂。 院中可真熱鬧妒挎,春花似錦、人聲如沸西饵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罗标。三九已至庸队,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闯割,已是汗流浹背彻消。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宙拉,地道東北人宾尚。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親煌贴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子御板,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • 了解本章之前需要對(duì)jmm有基本的了解,然后對(duì)于垃圾回收感興趣的朋友可以細(xì)看一下這篇文章 一牛郑、按代實(shí)現(xiàn)垃圾回收 先看...
    倪寶華閱讀 744評(píng)論 1 3
  • 靈魂拷問 為什么需要怠肋? 對(duì)什么東西? 在什么時(shí)候淹朋? 做什么事情笙各? 一、為什么需要 應(yīng)用程序?qū)Y源操作础芍,通常簡單分為...
    街上太擁擠閱讀 718評(píng)論 0 3
  • 我們已經(jīng)知道Java堆是被所有線程共享的一塊內(nèi)存區(qū)域杈抢,所有對(duì)象實(shí)例和數(shù)組都在堆上進(jìn)行內(nèi)存分配。為了進(jìn)行高效的垃圾回...
    Java架構(gòu)閱讀 9,442評(píng)論 3 24
  • 一仑性、垃圾收集的意義 ?相對(duì)于C++來說惶楼,Java預(yù)言顯著的特點(diǎn)就是引入了垃圾回收機(jī)制,它使得Java程序員在編寫程...
    SunnyMore閱讀 2,147評(píng)論 0 50
  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理诊杆,因此不免有一些不準(zhǔn)確的地方歼捐,同時(shí)不同JDK版本的...
    高廣超閱讀 15,567評(píng)論 3 83