分享一套之前整理的JVM入門試題(含答案)

簡書 慢黑八
轉(zhuǎn)載請(qǐng)注明原創(chuàng)出處,謝謝!
如果讀完覺得有收獲的話,歡迎點(diǎn)贊加關(guān)注

1、(5分)下題方法localVar1()中變量b所占槽位index為( ),localVar2()中變量b所占槽位index為 ( )

private void localVar1() {
        int a=0;
        System.out.println(a);
        int b=0;
    }
    private void localVar2() {
        {
            int a = 0;
            System.out.println(a);
        }
        int b = 0;
    }

答:使用jclasslib查看class信息 ,此題考的是棧上數(shù)據(jù)局部變量表的槽位復(fù)用,如下圖localVar1()方法中this占0位,a的index是1,b的index是2,localVar2()方法中,當(dāng)a處于代碼塊中的時(shí)候,代碼塊結(jié)束a的槽位釋放,b的槽位將會(huì)使用index=1的槽位,這樣做的好處是節(jié)省了空間,localVar2方法塊執(zhí)行后,a的槽位釋放可以提供給b使用,a引用的對(duì)象也可以在滿足垃圾回收的條件。


image.png

image.png

2、(20分)在java7中,畫圖描述一下程序,在類加載器、方法區(qū)、堆、解釋執(zhí)行器、pc寄存器、java棧、本地方法棧、CodeCache、垃圾回收器中是如何關(guān)聯(lián)執(zhí)行

public class A {
    public static void main(String[] args) {
        A a = new A();
        a.fn();
    }
    public void fn() {
        System.out.println("Hello World");
    }
}

答:如下圖所示


image.png
  1. A.java編譯后生成A.class字節(jié)碼文件
  2. 類加載子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或網(wǎng)絡(luò)中加載Class信息,加載的類信息存放于一塊稱為方法區(qū)(JDK1.7叫永久代,JDK1.8中叫做元空間)的內(nèi)存空間。除了類的信息外,方法區(qū)中還會(huì)存放運(yùn)行時(shí)的常量池信息,包括字符串常量和數(shù)字常量等。
  3. Java字節(jié)碼對(duì)于JVM就相當(dāng)于匯編語言對(duì)于計(jì)算機(jī)。JVM啟動(dòng)后會(huì)查找?guī)в衜ain方法開始執(zhí)行,字節(jié)碼會(huì)被JVM最后翻譯為計(jì)算機(jī)可以看懂的機(jī)器碼在CUP中運(yùn)行,這個(gè)過程由“解釋執(zhí)行器”進(jìn)行解釋執(zhí)行。還有一部分熱點(diǎn)方法字節(jié)碼由JIT“即時(shí)編譯器”(即時(shí)編譯器的解釋參見第7題)進(jìn)行了編譯優(yōu)化,編譯優(yōu)化后的機(jī)器碼可以更加高效的執(zhí)行。
  4. PC寄存器(也叫程序計(jì)數(shù)器)也是每個(gè)線程的私有空間,Java虛擬機(jī)會(huì)為每一個(gè)Java線程創(chuàng)建PC寄存器。在任意時(shí)刻,每一個(gè)線程總是在執(zhí)行一個(gè)方法,這個(gè)被執(zhí)行的方法叫當(dāng)前方法,如果當(dāng)前方法不是本地方法,PC寄存器就會(huì)指向當(dāng)前正在被執(zhí)行的指令。它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
  5. Java棧是一塊私有的空間,先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),只支持出棧和入棧兩種操縱。主要保存的內(nèi)容為棧幀,每一次函數(shù)調(diào)用(例如:調(diào)用main方法,fn方法)都會(huì)有一個(gè)對(duì)應(yīng)的棧幀被壓入Java棧。當(dāng)前正在執(zhí)行的函數(shù)所對(duì)應(yīng)的幀就是當(dāng)前的幀(棧頂),它保存著當(dāng)前函數(shù)的局部變量、棧幀數(shù)據(jù)(例如,對(duì)象A()的引用)和中間運(yùn)算結(jié)果數(shù)據(jù)。在方法執(zhí)行結(jié)束、異常、return之后Java棧會(huì)把方法從棧上彈出。
  6. Java堆在虛擬機(jī)啟動(dòng)的時(shí)候建立,它是Java程序最主要的內(nèi)存工作區(qū)域。在字節(jié)碼執(zhí)行到new A()的時(shí)候,對(duì)象在堆空間被創(chuàng)建,嚴(yán)格意義上來說對(duì)象創(chuàng)建的過程要更復(fù)雜一些。簡單來說,虛擬機(jī)會(huì)先把對(duì)象嘗試在棧上分配(棧上分配的解釋參見第4題),當(dāng)不滿足棧上分配的條件后,對(duì)象會(huì)在在堆中分配。
    分配的優(yōu)先級(jí)是:棧上分配 > TLAB分配 > 大對(duì)象直接老年代分配 > 新生代分配
  7. 垃圾回收系統(tǒng)是Java虛擬機(jī)的重要組成部分,垃圾回收器可以對(duì)方法區(qū)、Java堆和直接內(nèi)存進(jìn)行回收。其中,Java堆就是垃圾收集器的工作重點(diǎn)。(垃圾回收器的特點(diǎn)參見第3題)

3、(10分)在Java7或8下,簡述Serial、ParNew、parallel、cms、G1,五種垃圾回收器的特點(diǎn)與垃圾回收算法的區(qū)別。針對(duì)串行垃圾回收器、parallel、cms垃圾回收器,新生代(含from、to區(qū))、老年代的默認(rèn)內(nèi)存分配比例是?

答:

垃圾回收器 新生代垃圾收集器(算法) 老年代收集器(算法)
SerialGC Copy / 復(fù)制算法 MarkSweepCompact / 標(biāo)記壓縮算法
ParNewGC ParNew / 復(fù)制算法 MarkSweepCompact / 標(biāo)記壓縮算法
ParallelGC PS Scavenge / 復(fù)制算法 PS MarkSweep / 標(biāo)記整理
ConcMarkSweepGC ParNew / 復(fù)制算法 ConcurrentMarkSweep+serial old / 并發(fā)標(biāo)記整理+壓縮 +老年代串行垃圾回收
UseG1GC G1 Eden Space(分區(qū)) G1 Old Space(分區(qū))

以上五種垃圾收集器,可以出除了G1分區(qū)回收之外,另外四種垃圾回收器都是分代回收的。

  1. G1的特點(diǎn)是把堆分成若干個(gè)區(qū),每次只回收若干個(gè)區(qū),用于控制停頓時(shí)間。 G1中仍然包含著分代的概念,例如:最大堆是1024m,G1默認(rèn)1m一個(gè)區(qū),1024m=1024個(gè)區(qū),堆區(qū)數(shù)=新生代區(qū)數(shù)+幸存者區(qū)數(shù)+老年代區(qū)數(shù),1024個(gè)=24(新生代)+7(幸存者)+993(老年代),對(duì)應(yīng)的內(nèi)存容量也是這樣1024m=24m(新生代)+7m(幸存者)+993m(老年代)
    =24(新生代)+7(幸存者)+993(老年代)。
  2. SerialGC 比較適合在cup比較少的場合,性能往往最好。
  3. ParNewGC大概就是在串行的基礎(chǔ)上多線程化。
  4. ParallelGC和parnew類似,但是Parallelgc更關(guān)注系統(tǒng)的吞吐,可以根據(jù)吞吐與停頓時(shí)間自動(dòng)調(diào)節(jié)新生代、老年代的使用比例。
  5. 與ParallelGC不同的是ConcMarkSweepGC主要關(guān)注系統(tǒng)停頓,是一個(gè)多線程并行垃圾回收的老年代垃圾回收器,新生代只能與ParNew組合。

串行垃圾回收器、parallel、cms垃圾回收器新生代,老年代、from、to的比例:

SerialGC 新生代:老年代 = \color{red}{1:2} eden:from:to = \color{red}{8:1:1}
ParallelGC 新生代:老年代 = \color{red}{1:2} eden:from:to = \color{red}{6:1:1} 比例可自動(dòng)調(diào)節(jié)
ConcMarkSweepGC 新生代:老年代 = \color{red}{1:2} eden:from:to = \color{red}{8:1:1}

4、(5分)使用JVM參數(shù)-server -Xms10m -Xmx10m啟動(dòng)如下程序,是否會(huì)出現(xiàn)內(nèi)存溢出或垃圾回收失敗,為什么?

public static class User{
        public int id =0 ;
        public String name ="";
    }
    public static void alloc(){
        User user = new User();
        user.id=5;
        user.name="manheiba";
    }
    public static void main(String[] args) {
        long b = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            alloc();
        }
        long e = System.currentTimeMillis();
        System.out.println(e-b);
    }

答:不會(huì)出現(xiàn)內(nèi)存溢出,因?yàn)閍lloc方法內(nèi)創(chuàng)建的對(duì)象會(huì)在棧上分配,棧上分配是java虛擬機(jī)的一項(xiàng)優(yōu)化技術(shù),它的基本思想是,對(duì)于那些線程私有的對(duì)象(這里指不可能被其他線程訪問的對(duì)象),可以將他們打散分配在棧上,而不是分配在堆上。棧上分配的好處是可以再函數(shù)調(diào)用結(jié)束后自行銷毀,而不需要垃圾回收器的介入,從而提高系統(tǒng)的性能。對(duì)于大量在循環(huán)中創(chuàng)建對(duì)象的的服務(wù)器可以開啟棧上分配從而提升系統(tǒng)性能。棧上分配需要在-server模式下開啟內(nèi)存逃逸分析和標(biāo)量替換,這兩個(gè)參數(shù)默認(rèn)是打開的。
上面代碼中,循環(huán)alloc()方法,在alloc方法中創(chuàng)建對(duì)象,創(chuàng)建的user對(duì)象并alloc方法外其他對(duì)象引用,java虛擬機(jī)會(huì)判定user對(duì)象不會(huì)逃逸,可以棧上分配,隨著alloc方法執(zhí)行結(jié)束user對(duì)象就會(huì)被垃圾回收,對(duì)象回收的過程不需要垃圾回收器的介入,也不會(huì)出現(xiàn)內(nèi)存溢出的情況。

5、(10分)簡述如何觸發(fā)永久代溢出、棧溢出、java堆溢出、直接內(nèi)存溢出、創(chuàng)建本地線程溢出以及解決辦法?

答:

  1. 永久帶是存放class的區(qū)域,可以通過cglib或者asm動(dòng)態(tài)不停創(chuàng)建class讓永久帶溢出。
  2. 棧上存放棧幀信息,可以通過方法的無限遞歸調(diào)用,讓棧幀上方法無限增加導(dǎo)致棧溢出。
  3. Java堆溢出:簡單來說循環(huán)中不停創(chuàng)建不被回收的內(nèi)存對(duì)象即可導(dǎo)致堆的溢出。
  4. 直接內(nèi)存溢出:可以循環(huán)分配直接內(nèi)存 \color{red}{unsafe.allocateMemory(1024*1024)} 造成直接內(nèi)存溢出。

6、(5分)簡述類加載器雙親委派模式?

答:在類加載的時(shí)候,系統(tǒng)會(huì)判斷當(dāng)前類是否已經(jīng)加載,如果已經(jīng)被加載,就會(huì)直接返回可用的類,否則就會(huì)嘗試加載,在嘗試加載時(shí),會(huì)先請(qǐng)求雙親處理,如果雙親請(qǐng)求失敗,則會(huì)自己加載。自定義類加載器的雙親是應(yīng)用類加載器,應(yīng)用類加載器的雙親是擴(kuò)展類加載器,擴(kuò)展類加載器的雙親是啟動(dòng)類加載器。

7、(10分)簡述解釋執(zhí)行器、即時(shí)編譯器(JIT)在java程序執(zhí)行時(shí)的工作流程,為什么有時(shí)候Java方法執(zhí)行的比C快 ?

image.png

答:如上圖所示,class字節(jié)碼會(huì)被JVM最后翻譯為計(jì)算機(jī)可以看懂的機(jī)器碼在CUP中運(yùn)行,這個(gè)過程由“解釋執(zhí)行器”進(jìn)行解釋執(zhí)行。HotSpot通過循環(huán)回邊計(jì)數(shù)器統(tǒng)計(jì)熱點(diǎn)方法,即時(shí)編譯器會(huì)根據(jù)JVM進(jìn)程的運(yùn)行時(shí)信息(profiler、hprof)進(jìn)行“編譯優(yōu)化”,接著把“編譯優(yōu)化”后的熱點(diǎn)方法放在codeCache中,Java8中默認(rèn)開啟了分層編譯,方法調(diào)用1500次以下解釋執(zhí)行,達(dá)到1500次調(diào)用時(shí)候調(diào)C1編譯器,達(dá)到10000次時(shí)調(diào)C2編譯器。C1、C2編譯后的機(jī)器碼執(zhí)行效率更高。通常C2代碼的執(zhí)行效率要比C1高出30%以上。
解釋執(zhí)行相當(dāng)于同聲傳譯,你說一句我翻一句給觀眾(CPU)聽。JIT是線下翻譯,可以花時(shí)間精簡掉你的口語話表達(dá)(做編譯優(yōu)化)。相對(duì)比C程序, 執(zhí)行java程序時(shí),cpu拿到的是C1、C2編譯優(yōu)化后的機(jī)器碼直接執(zhí)行,所以執(zhí)行效率更快一些。

8、(10分)簡述如下JVM參數(shù)含義:

-XX:+PrintHeapAtGC 打印垃圾回收時(shí)的堆信息
-XX:+PrintGCTimeStamps gclog中輸出gc發(fā)生時(shí)間
-XX:+PrintGCApplicationStoppedTime 打印GC停頓時(shí)間
-Xloggc 指定gclog的文件位置
-XX:HeapDumpPath 指定導(dǎo)出堆文件的文件路徑
-verbose:class 跟蹤類的加載和卸載
-XX:+PrintCommandLineFlags 打印虛擬機(jī)顯式和隱式參數(shù)
-Xms 設(shè)置初始堆大小
-Xmx 設(shè)置最大堆大小
-Xmn 設(shè)置新生代大小
-XX:SurvivorRatio 設(shè)置eden/from的比例
-XX:MaxPermSize java7下設(shè)置永久代大小
-XX:MaxDirectMemorySize 設(shè)置直接內(nèi)存大小
–server 設(shè)置為server模式
-XX:+UseConcMarkSweepGC 使用cms垃圾回收器
-XX:CMSInitiatingOccupanyFraction 出發(fā)老年代垃圾回收內(nèi)存占比
-XX:MaxGCPauseMillis 預(yù)期gc停頓時(shí)間
-XX:+UseG1GC 使用G1垃圾回收器

9、(5分)簡述說明如何寫Java程序來證明STW(Stop-the-world)的這一特性?

答案:編寫java程序,開啟gclog,把jmx設(shè)置的盡量大一些(2G以上,為了讓垃圾收集的時(shí)間長一些),選擇串行垃圾回收器。
在java程序中運(yùn)行兩個(gè)線程:
線程1不斷像HashMap中增加鍵值內(nèi)容,直到老年代打滿的時(shí)候進(jìn)行map.clear()操作。
線程2設(shè)置每隔100ms像控制臺(tái)輸出時(shí)間戳日志。
當(dāng)?shù)诰€程1進(jìn)行map.clear()后觀察gclog,在gc日志中我們看到了fullgc日志,以及停頓時(shí)間。我們發(fā)現(xiàn),這時(shí)候線程2的日志打印的時(shí)間戳間隔變長了,由100毫秒的停頓變成了幾百毫秒的停頓,這個(gè)幾百毫秒的停頓與gc日志中的fullgc停頓恰好相符,就是大名鼎鼎的Stop-the-world。在STW發(fā)生時(shí),幾乎所有線程掛起,程序像hung死一樣。

10、(20分)上機(jī)題,打開vmware虛擬機(jī),使用appuser用戶登錄, 密碼: ********,執(zhí)行腳本./TestStarter.sh
(1)解決啟動(dòng)時(shí)候的報(bào)錯(cuò),請(qǐng)把調(diào)整后的JVM參數(shù)寫到答題紙上。
(2)成功啟動(dòng)程序后,結(jié)合linux系統(tǒng)工具、JVM工具分析當(dāng)前程序中的存在問題及可能的性能瓶頸。

答:一共5個(gè)問題,該題主要是考察結(jié)合工具分析jvm相關(guān)問題,綜合應(yīng)用top、vmstat、iostat、pidstat、jps、jstat、jinfo、jmap、jhat、jstack、jcmd、hprof、mat、arthas等工具發(fā)現(xiàn)相關(guān)性能問題。(該試題app后面放到github上)
問題1:??臻g不夠。 //增加-xss大小讓程序正常啟動(dòng)。
問題2:類加載器造成的死鎖。 //使用jstack 觀察線程wait
問題3:存在高cpu線程。 //arthas dashboard觀察高cup線程,jad查看高cpu方法;或者使用查看比較高的線程id轉(zhuǎn)換16進(jìn)制之后再jstack導(dǎo)出的棧文件中查找導(dǎo)致cpu過高的線程。
問題4:存在大HashMap沒有被正常垃圾回收引發(fā)fullgc的情況。//使用mat進(jìn)行分析。
問題5:存在3個(gè)線程死鎖問題。 // Jstack -l 查看死鎖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這篇文章是我之前翻閱了不少的書籍以及從網(wǎng)絡(luò)上收集的一些資料的整理,因此不免有一些不準(zhǔn)確的地方,同時(shí)不同JDK版本的...
    高廣超閱讀 16,072評(píng)論 3 83
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,689評(píng)論 1 32
  • 所有知識(shí)點(diǎn)已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數(shù)? 在 Jav...
    侯蛋蛋_閱讀 2,716評(píng)論 1 4
  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,484評(píng)論 1 34
  • 第二部分 自動(dòng)內(nèi)存管理機(jī)制 第二章 java內(nèi)存異常與內(nèi)存溢出異常 運(yùn)行數(shù)據(jù)區(qū)域 程序計(jì)數(shù)器:當(dāng)前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,300評(píng)論 0 2

友情鏈接更多精彩內(nèi)容