【JVM系列8】JVM經(jīng)典面試問題(內(nèi)存溢出和內(nèi)存泄露)解答及調(diào)優(yōu)實(shí)戰(zhàn)分析

前言

JVM系列介紹到這里,其實(shí)理論知識(shí)和基本工具的使用基本上都介紹過了瘸洛,當(dāng)然玻募,JVM的理論知識(shí)也不僅僅只是這些,如果想要更深入的里面還是會(huì)有很多細(xì)節(jié)值得深入了解滑肉,但是就目前來說包各,掌握了前面幾篇文章介紹的內(nèi)容,我們已經(jīng)可以對(duì)JVM進(jìn)行基本的調(diào)優(yōu)工作了靶庙,所以本篇文章會(huì)以一些常見問題并結(jié)合實(shí)際例子來進(jìn)行分析问畅。

常見問題及調(diào)優(yōu)實(shí)戰(zhàn)

1、內(nèi)存泄漏與內(nèi)存溢出的區(qū)別

內(nèi)存泄漏(Memory Leak):指的是對(duì)象無法得到及時(shí)的回收六荒,導(dǎo)致其持續(xù)占用內(nèi)存空間护姆,造成了內(nèi)存空間的浪費(fèi)。 內(nèi)存泄露一般是強(qiáng)引用才會(huì)出現(xiàn)問題掏击,其他像軟引用卵皂,弱引用和虛引用影響不大
內(nèi)存溢出(Out Of Memory):內(nèi)存泄漏到一定的程度就會(huì)導(dǎo)致內(nèi)存溢出砚亭,但是內(nèi)存溢出也有可能是大對(duì)象導(dǎo)致的灯变。

這兩個(gè)區(qū)別結(jié)合下面的問題2可以更好的理解。

2钠惩、如何防止內(nèi)存泄露

我們先來看下面一個(gè)簡(jiǎn)單的例子:

package com.zwx.jvm;

public class JVMTuningDemo {
    public static void main(String[] args) {
        {
            byte[] bytes = new byte[1024 * 1024 * 64];
        }
        System.gc();
    }
}

調(diào)用之后打開gc日志柒凉,如果不知道怎么獲取gc日志的,可以點(diǎn)擊這里篓跛。

image.png

可以看到GC之后膝捞,對(duì)象并沒有回收掉,從代碼上來說,因?yàn)橛衶}蔬咬,所以理論上已經(jīng)離開作用域了鲤遥,bytes會(huì)被回收(如果不加{}是肯定不會(huì)被回收的,因?yàn)闆]有離開作用域)林艘,但是這里為什么還是沒有被回收盖奈?
回答這個(gè)問題之前我們先對(duì)上面的代碼改進(jìn)一下

package com.zwx.jvm;

public class JVMTuningDemo {
    public static void main(String[] args) {
        {
            byte[] bytes = new byte[1024 * 1024 * 64];
            bytes = null;
        }
        System.gc();
    }
}

這時(shí)候再來看,會(huì)發(fā)現(xiàn)已經(jīng)被回收了

image.png

這是因?yàn)橹半m然已經(jīng)離開作用域了狐援,但是卻并沒有收回引用钢坦,也就是說棧幀中的局部變量表數(shù)組中所對(duì)應(yīng)的slot(局部變量表中數(shù)組的每一個(gè)位置都被稱之為slot)還是有值的,并沒有被切斷引用啥酱,而將其置為Null就等于切斷了引用爹凹,所以可以被回收。

如果看過我的并發(fā)編程系列文章中對(duì)AQS同步隊(duì)列以及阻塞隊(duì)列的源碼分析镶殷,那么也應(yīng)該可以看到禾酱,這些源碼中也是大量使用了這種方式來幫助虛擬機(jī)進(jìn)行g(shù)c:

image.png

在有些場(chǎng)景這種設(shè)置為null的方式確實(shí)是一種解決方式,但是其實(shí)最優(yōu)雅的方式還是以恰當(dāng)?shù)淖兞孔饔糜騺砜刂苹厥兆兞俊?br> 我們?cè)賹?duì)上面的例子進(jìn)行改寫:

package com.zwx.jvm;

public class JVMTuningDemo {
    public static void main(String[] args) {
        {
            byte[] bytes = new byte[1024 * 1024 * 64];
        }
        int i = 0;
        System.gc();
    }
}

運(yùn)行之后打開gc日志:

image.png

我們會(huì)發(fā)現(xiàn)绘趋,bytes對(duì)象確實(shí)也被回收了颤陶,這又是為什么呢?

這是因?yàn)闂械木植孔兞勘韮?nèi)的每一個(gè)slot都是可以復(fù)用的陷遮,當(dāng)bytes變量離開了其作用域之后滓走,Java虛擬機(jī)知道這個(gè)slot已經(jīng)無效了,但是雖然無效拷呆,引用卻還在闲坎,所以如果沒有新的變量過來占用bytes變量所在的slot,是無法將bytes回收的茬斧,而一旦有新的變量過來占用slot腰懂,自然而然bytes對(duì)象的引用就被切斷了,從而被gc掉项秉。

3绣溜、GCRoot不可達(dá)的對(duì)象一定會(huì)被回收嗎

答案是不一定的。

即使在可達(dá)性分析法中被判定不可達(dá)的對(duì)象娄蔼,也并非是“非死不可”的怖喻,這時(shí)候它們暫時(shí)處于“緩刑階段”,對(duì)象依然有“逃生”的機(jī)會(huì)岁诉。
一個(gè)對(duì)象在第一次被標(biāo)記為不可達(dá)對(duì)象時(shí)锚沸,并不會(huì)立刻被回收,而是會(huì)進(jìn)行判斷是否有必要執(zhí)行?nalize()方法涕癣,那么什么時(shí)候會(huì)執(zhí)行?nalize()方法呢哗蜈?有兩種情況:

  • 1、Java虛擬機(jī)已經(jīng)調(diào)用過當(dāng)前對(duì)象的?nalize()方法

  • 2、?nalize()方法被我們重寫了
    如果不滿足這兩種情況距潘,那么對(duì)象就相當(dāng)于是“死刑立即執(zhí)行”炼列,沒有機(jī)會(huì)逃生,但是一旦滿足執(zhí)行?nalize()方法的條件音比,而我們又在?nalize()方法中將對(duì)象重新和引用鏈中的對(duì)象進(jìn)行了關(guān)聯(lián)俭尖,這時(shí)候?qū)ο缶涂梢皂樌疤由薄?br> 我們來看下面一個(gè)例子:

    package com.zwx.jvm;

    import java.util.ArrayList; import java.util.List;

    public class ObjEscapeByFinalize { public static ObjEscapeByFinalize objEscapeByFinalize = null;

    public static void main(String[] args) throws InterruptedException {
        objEscapeByFinalize = new ObjEscapeByFinalize();
        //首次自救
        objEscapeByFinalize = null;
        System.gc();
        Thread.sleep(1000);//?nalize()方法優(yōu)先級(jí)比較低,稍微停頓一會(huì)等一等
        print();
    
        //再次自救
        objEscapeByFinalize = null;
        System.gc();
        Thread.sleep(1000);
        print();
    }
    
    static void print(){
        if (null == objEscapeByFinalize){
            System.out.println("obj has been gc");
        }else{
            System.out.println("obj escape success");
        }
    }
    
    @Override
    protected void finalize() throws Throwable {
        System.out.println("come in method:finalize");
        super.finalize();
        objEscapeByFinalize = this;
    }
    

    }

運(yùn)行結(jié)果為:

come in method:finalize
obj escape success
obj has been gc

從結(jié)果可以看到洞翩,第一次自救成功稽犁,而第二次已經(jīng)沒有了自救機(jī)會(huì),因?yàn)楫?dāng)前對(duì)象已經(jīng)執(zhí)行過一次finalize()方法了菱农,而如果我們把finalize()方法中的:

objEscapeByFinalize = this;

替換為:

objEscapeByFinalize = new ObjEscapeByFinalize();

這時(shí)候就可以一直自救成功缭付,因?yàn)槊看巫跃戎缶彤a(chǎn)生了一個(gè)新的對(duì)象,新的對(duì)象并沒有執(zhí)行過finalize()方法循未。

上面的demo還有一點(diǎn)需要注意的是,finalize()方法針對(duì)的是對(duì)象秫舌,假如上面的靜態(tài)對(duì)象換成一個(gè)其他對(duì)象的妖,而finalize()方法又寫在當(dāng)前對(duì)象,那么是無效的足陨,例如如下例子:

package com.zwx.jvm;

import java.util.ArrayList;
import java.util.List;

public class ObjEscapeByFinalize1 {
    public static List<Object> list = null;

    public static void main(String[] args) throws InterruptedException {
        list = new ArrayList<>();
        //首次自救
        list = null;
        System.gc();
        Thread.sleep(1000);
        print();

    }

    static void print(){
        if (null == list){
            System.out.println("obj has been gc");
        }else{
            System.out.println("obj escape success");
        }
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println("come in method:finalize");
        super.finalize();
        list = new ArrayList<>();
    }
}

這里是無法實(shí)現(xiàn)自救的嫂粟,因?yàn)檫@里要救的對(duì)象是List,而finalize()并不屬于List墨缘,是屬于ObjEscapeByFinalize1對(duì)象星虹,所以這一點(diǎn)也是需要明確地。

不過雖然finalize()可以完成對(duì)象自救镊讼,但是由于這個(gè)方法的代價(jià)比較大而且運(yùn)行時(shí)有不確定性宽涌,一般情況下還是不建議使用

4、Young GC會(huì)有STW嗎

不管是什么類型的GC蝶棋,都會(huì)有 stop-the-world卸亮,只是發(fā)生時(shí)間的長(zhǎng)短,目前Java中所有的垃圾回收器均需要STW玩裙,唯一的區(qū)別只是時(shí)間的長(zhǎng)短問題兼贸。

5、Major GC和Full GC的區(qū)別

之前我們提到了吃溅,Major GC通常會(huì)伴隨著Minor GC溶诞,也就等于觸發(fā)了Full GC,但是雖然如此,Major GC和Full GC并不是完全等價(jià)的决侈,因?yàn)镕ull GC 的同時(shí)會(huì)對(duì)方法區(qū)(jdk1.8的metaspace螺垢,jdk1.7的永久代)進(jìn)行GC,所以嚴(yán)格來說:Full GC=Major GC+Minor GC+方法區(qū)GC

6、方法區(qū)會(huì)發(fā)生GC嗎

答案是肯定的甩苛。雖然方法區(qū)中的回收收益一般都不高蹂楣,但是也是會(huì)被GC的,而方法區(qū)中被回收的最主要的就是對(duì)廢棄常量無用類的回收讯蒲,判定一個(gè)廢棄常量比較簡(jiǎn)單痊土,但是判定一個(gè)類是無用類是比較困難的,那么方法區(qū)中的怎么判斷一個(gè)類是無用類呢墨林?
判斷一個(gè)類是否無用赁酝,需要達(dá)到以下三個(gè)條件:

  • 1殖属、該類所有的實(shí)例都已經(jīng)被回收智亮,也就是 Java 堆中不存在該類的任何實(shí)例。
  • 2坊谁、加載該類的類加載器ClassLoader已經(jīng)被回收(從這個(gè)條件可以看出搔耕,一般只有大量使用了反射隙袁,動(dòng)態(tài)代理或者字節(jié)碼框架等場(chǎng)景條件下才會(huì)滿足這個(gè)條件)。
  • 3弃榨、該類對(duì)應(yīng)的 java.lang.Class對(duì)象沒有在任何地方被引用菩收,無法在任何地方通過反射訪問該類的方法。

這三個(gè)條件實(shí)際上是非尘ňΓ苛刻的娜饵,而即使達(dá)到以上三個(gè)條件,無用類也僅僅是可以被回收官辈,但是是不是一定會(huì)被回收箱舞,還是取決于Java虛擬機(jī)。HotSpot虛擬機(jī)中提供了參數(shù)-Xnoclassgc來控制拳亿。

7晴股、什么是直接內(nèi)存

直接內(nèi)存(Direct Memory)不屬于運(yùn)行時(shí)數(shù)據(jù)區(qū),也被稱之為堆外內(nèi)存风瘦,通常訪問直接內(nèi)存的速度會(huì)優(yōu)于Java堆队魏。直接沒存也有可能會(huì)發(fā)生OutOfMemoryError異常,Java 1.4中新加入的nio包下的ByteBuffer就操作了直接內(nèi)存万搔,直接內(nèi)存可以通過參數(shù)-XX:MaxDirectMemorySize控制大小胡桨。

8、CMS收集器和G1收集器的區(qū)別

作為同樣是并行的2款垃圾收集器瞬雹,G1的目前是用來取代CMS收集器的昧谊,其主要有如下區(qū)別:

  • 1、CMS收集器是老年代的收集器酗捌,需要和其他新生代收集器配合使用呢诬,而G1同時(shí)適用于新生代和老年代涌哲,不需要和其他收集器配合使用
  • 2、CMS收集器以最小的停頓時(shí)間為目標(biāo)的并發(fā)收集器尚镰,G1收集器是一種可預(yù)測(cè)垃圾回收的停頓時(shí)間
  • 3阀圾、CMS收集器是使用“標(biāo)記-清除”算法進(jìn)行的垃圾回收,容易產(chǎn)生內(nèi)存碎片狗唉,G1使用了 Region方式對(duì)堆內(nèi)存進(jìn)行了劃分初烘,且基于標(biāo)記整理算法實(shí)現(xiàn),整體減少了垃圾碎片的產(chǎn)生

9分俯、類加載機(jī)制經(jīng)過哪些步驟

類加載機(jī)制主要經(jīng)過了:加載(Loading)肾筐,連接(Linking)初始化(Initialization)缸剪,使用(Using)吗铐,卸載(Unloading) 五個(gè)大階段,而其中連接(Linking)又分為:驗(yàn)證(Verification),準(zhǔn)備(Preparation)杏节,解析(Resolution)三個(gè)階段唬渗。

10、系統(tǒng)CPU經(jīng)常100%拢锹,如何定位

  • 1谣妻、首先要確認(rèn)哪個(gè)進(jìn)程占用CPU高,可以使用top命令
image.png

2卒稳、找到之后可以繼續(xù)執(zhí)行top -Hp PID命令查詢出占用最大的線程

image.png

3、執(zhí)行jstack命令生成線程快照信息:
jstack -l 進(jìn)程PID >jstack.log
1

輸出之后他巨,我們找到上面占用CPU最高的一個(gè)線程pid=11566充坑,將其轉(zhuǎn)換為16進(jìn)制,得到的結(jié)果是2d2e染突,然后進(jìn)入生成的jstack.log文件找到這個(gè)線程可以查看線程信息捻爷。

image.png

4、上面就可以定位到了線程調(diào)用的方法了份企,接下來就可以去分析對(duì)應(yīng)的代碼尋找問題了

總結(jié)

本文主要列舉了一些其他比較經(jīng)典的也榄,而前面在JVM系列其他文章中又沒有過多進(jìn)行說明的問題,JVM學(xué)習(xí)之后需要不斷實(shí)戰(zhàn)積累調(diào)優(yōu)經(jīng)驗(yàn)司志,雖然還有一些理論在JVM系列中沒有提及甜紫,但是我想如果可以認(rèn)真把我 JVM系列至本篇為止的8篇文章相關(guān)知識(shí)和理論都掌握的話,那至少可以說已經(jīng)具備了調(diào)優(yōu)的理論基礎(chǔ)了骂远,剩下的就是不斷積累經(jīng)驗(yàn)囚霸,當(dāng)然,推薦大家可以去通讀一下JVM規(guī)范激才,畢竟所有的Java虛擬機(jī)都是按照J(rèn)VM規(guī)范來實(shí)現(xiàn)的拓型,或者有必要的可以自己去編譯JDK來進(jìn)行更深一步的研究额嘿。
請(qǐng)關(guān)注我,一起學(xué)習(xí)進(jìn)步

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劣挫,一起剝皮案震驚了整個(gè)濱河市册养,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌压固,老刑警劉巖球拦,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異邓夕,居然都是意外死亡刘莹,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門焚刚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來点弯,“玉大人,你說我怎么就攤上這事矿咕∏栏兀” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵碳柱,是天一觀的道長(zhǎng)捡絮。 經(jīng)常有香客問我,道長(zhǎng)莲镣,這世上最難降的妖魔是什么福稳? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮瑞侮,結(jié)果婚禮上的圆,老公的妹妹穿的比我還像新娘。我一直安慰自己半火,他們只是感情好越妈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钮糖,像睡著了一般梅掠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上店归,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天阎抒,我揣著相機(jī)與錄音,去河邊找鬼娱节。 笑死挠蛉,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的肄满。 我是一名探鬼主播谴古,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼质涛,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了掰担?” 一聲冷哼從身側(cè)響起汇陆,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎带饱,沒想到半個(gè)月后毡代,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡勺疼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年教寂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片执庐。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酪耕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轨淌,到底是詐尸還是另有隱情迂烁,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布递鹉,位于F島的核電站盟步,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏躏结。R本人自食惡果不足惜却盘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望媳拴。 院中可真熱鬧谷炸,春花似錦、人聲如沸禀挫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)语婴。三九已至,卻和暖如春驶睦,著一層夾襖步出監(jiān)牢的瞬間砰左,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工场航, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缠导,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓溉痢,卻偏偏與公主長(zhǎng)得像僻造,于是被迫代替她去往敵國(guó)和親憋他。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354