簡書 慢黑八
轉(zhuǎn)載請注明原創(chuàng)出處纱昧,謝謝刨啸!
如果讀完覺得有收獲的話,歡迎點贊加關(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()方法中啥寇,當a處于代碼塊中的時候,代碼塊結(jié)束a的槽位釋放洒扎,b的槽位將會使用index=1的槽位辑甜,這樣做的好處是節(jié)省了空間,localVar2方法塊執(zhí)行后袍冷,a的槽位釋放可以提供給b使用磷醋,a引用的對象也可以在滿足垃圾回收的條件。
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");
}
}
答:如下圖所示
- A.java編譯后生成A.class字節(jié)碼文件
- 類加載子系統(tǒng)負責從文件系統(tǒng)或網(wǎng)絡(luò)中加載Class信息,加載的類信息存放于一塊稱為方法區(qū)(JDK1.7叫永久代肴颊,JDK1.8中叫做元空間)的內(nèi)存空間氓栈。除了類的信息外,方法區(qū)中還會存放運行時的常量池信息苫昌,包括字符串常量和數(shù)字常量等颤绕。
- Java字節(jié)碼對于JVM就相當于匯編語言對于計算機幸海。JVM啟動后會查找?guī)в衜ain方法開始執(zhí)行祟身,字節(jié)碼會被JVM最后翻譯為計算機可以看懂的機器碼在CUP中運行,這個過程由“解釋執(zhí)行器”進行解釋執(zhí)行物独。還有一部分熱點方法字節(jié)碼由JIT“即時編譯器”(即時編譯器的解釋參見第7題)進行了編譯優(yōu)化袜硫,編譯優(yōu)化后的機器碼可以更加高效的執(zhí)行。
- PC寄存器(也叫程序計數(shù)器)也是每個線程的私有空間挡篓,Java虛擬機會為每一個Java線程創(chuàng)建PC寄存器婉陷。在任意時刻帚称,每一個線程總是在執(zhí)行一個方法,這個被執(zhí)行的方法叫當前方法秽澳,如果當前方法不是本地方法闯睹,PC寄存器就會指向當前正在被執(zhí)行的指令。它可以看作是當前線程所執(zhí)行的字節(jié)碼的行號指示器担神。
- Java棧是一塊私有的空間楼吃,先進后出的數(shù)據(jù)結(jié)構(gòu),只支持出棧和入棧兩種操縱妄讯。主要保存的內(nèi)容為棧幀孩锡,每一次函數(shù)調(diào)用(例如:調(diào)用main方法,fn方法)都會有一個對應(yīng)的棧幀被壓入Java棧亥贸。當前正在執(zhí)行的函數(shù)所對應(yīng)的幀就是當前的幀(棧頂)躬窜,它保存著當前函數(shù)的局部變量、棧幀數(shù)據(jù)(例如炕置,對象A()的引用)和中間運算結(jié)果數(shù)據(jù)荣挨。在方法執(zhí)行結(jié)束、異常朴摊、return之后Java棧會把方法從棧上彈出垦沉。
- Java堆在虛擬機啟動的時候建立,它是Java程序最主要的內(nèi)存工作區(qū)域仍劈。在字節(jié)碼執(zhí)行到new A()的時候厕倍,對象在堆空間被創(chuàng)建,嚴格意義上來說對象創(chuàng)建的過程要更復(fù)雜一些贩疙。簡單來說讹弯,虛擬機會先把對象嘗試在棧上分配(棧上分配的解釋參見第4題),當不滿足棧上分配的條件后这溅,對象會在在堆中分配组民。
分配的優(yōu)先級是:棧上分配 > TLAB分配 > 大對象直接老年代分配 > 新生代分配 - 垃圾回收系統(tǒng)是Java虛擬機的重要組成部分,垃圾回收器可以對方法區(qū)悲靴、Java堆和直接內(nèi)存進行回收臭胜。其中,Java堆就是垃圾收集器的工作重點癞尚。(垃圾回收器的特點參見第3題)
3耸三、(10分)在Java7或8下,簡述Serial浇揩、ParNew仪壮、parallel、cms胳徽、G1,五種垃圾回收器的特點與垃圾回收算法的區(qū)別积锅。針對串行垃圾回收器爽彤、parallel、cms垃圾回收器缚陷,新生代(含from适篙、to區(qū))、老年代的默認內(nèi)存分配比例是箫爷?
答:
垃圾回收器 | 新生代垃圾收集器(算法) | 老年代收集器(算法) |
---|---|---|
SerialGC | Copy / 復(fù)制算法 | MarkSweepCompact / 標記壓縮算法 |
ParNewGC | ParNew / 復(fù)制算法 | MarkSweepCompact / 標記壓縮算法 |
ParallelGC | PS Scavenge / 復(fù)制算法 | PS MarkSweep / 標記整理 |
ConcMarkSweepGC | ParNew / 復(fù)制算法 | ConcurrentMarkSweep+serial old / 并發(fā)標記整理+壓縮 +老年代串行垃圾回收 |
UseG1GC | G1 Eden Space(分區(qū)) | G1 Old Space(分區(qū)) |
以上五種垃圾收集器匙瘪,可以出除了G1分區(qū)回收之外,另外四種垃圾回收器都是分代回收的蝶缀。
- G1的特點是把堆分成若干個區(qū)丹喻,每次只回收若干個區(qū),用于控制停頓時間翁都。 G1中仍然包含著分代的概念碍论,例如:最大堆是1024m,G1默認1m一個區(qū)柄慰,1024m=1024個區(qū)鳍悠,堆區(qū)數(shù)=新生代區(qū)數(shù)+幸存者區(qū)數(shù)+老年代區(qū)數(shù),1024個=24(新生代)+7(幸存者)+993(老年代)坐搔,對應(yīng)的內(nèi)存容量也是這樣1024m=24m(新生代)+7m(幸存者)+993m(老年代)
=24(新生代)+7(幸存者)+993(老年代)藏研。 - SerialGC 比較適合在cup比較少的場合,性能往往最好概行。
- ParNewGC大概就是在串行的基礎(chǔ)上多線程化蠢挡。
- ParallelGC和parnew類似,但是Parallelgc更關(guān)注系統(tǒng)的吞吐凳忙,可以根據(jù)吞吐與停頓時間自動調(diào)節(jié)新生代业踏、老年代的使用比例。
- 與ParallelGC不同的是ConcMarkSweepGC主要關(guān)注系統(tǒng)停頓涧卵,是一個多線程并行垃圾回收的老年代垃圾回收器勤家,新生代只能與ParNew組合。
串行垃圾回收器柳恐、parallel伐脖、cms垃圾回收器新生代,老年代乐设、from讼庇、to的比例:
SerialGC 新生代:老年代 = eden:from:to =
ParallelGC 新生代:老年代 = eden:from:to =
比例可自動調(diào)節(jié)
ConcMarkSweepGC 新生代:老年代 = eden:from:to =
4、(5分)使用JVM參數(shù)-server -Xms10m -Xmx10m啟動如下程序伤提,是否會出現(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);
}
答:不會出現(xiàn)內(nèi)存溢出肿男,因為alloc方法內(nèi)創(chuàng)建的對象會在棧上分配介汹,棧上分配是java虛擬機的一項優(yōu)化技術(shù),它的基本思想是舶沛,對于那些線程私有的對象(這里指不可能被其他線程訪問的對象)嘹承,可以將他們打散分配在棧上,而不是分配在堆上如庭。棧上分配的好處是可以再函數(shù)調(diào)用結(jié)束后自行銷毀叹卷,而不需要垃圾回收器的介入,從而提高系統(tǒng)的性能坪它。對于大量在循環(huán)中創(chuàng)建對象的的服務(wù)器可以開啟棧上分配從而提升系統(tǒng)性能骤竹。棧上分配需要在-server模式下開啟內(nèi)存逃逸分析和標量替換,這兩個參數(shù)默認是打開的往毡。
上面代碼中蒙揣,循環(huán)alloc()方法,在alloc方法中創(chuàng)建對象开瞭,創(chuàng)建的user對象并alloc方法外其他對象引用懒震,java虛擬機會判定user對象不會逃逸,可以棧上分配嗤详,隨著alloc方法執(zhí)行結(jié)束user對象就會被垃圾回收个扰,對象回收的過程不需要垃圾回收器的介入,也不會出現(xiàn)內(nèi)存溢出的情況葱色。
5递宅、(10分)簡述如何觸發(fā)永久代溢出、棧溢出苍狰、java堆溢出恐锣、直接內(nèi)存溢出、創(chuàng)建本地線程溢出以及解決辦法舞痰?
答:
- 永久帶是存放class的區(qū)域土榴,可以通過cglib或者asm動態(tài)不停創(chuàng)建class讓永久帶溢出。
- 棧上存放棧幀信息响牛,可以通過方法的無限遞歸調(diào)用玷禽,讓棧幀上方法無限增加導(dǎo)致棧溢出。
- Java堆溢出:簡單來說循環(huán)中不停創(chuàng)建不被回收的內(nèi)存對象即可導(dǎo)致堆的溢出呀打。
- 直接內(nèi)存溢出:可以循環(huán)分配直接內(nèi)存
造成直接內(nèi)存溢出矢赁。
6、(5分)簡述類加載器雙親委派模式贬丛?
答:在類加載的時候撩银,系統(tǒng)會判斷當前類是否已經(jīng)加載,如果已經(jīng)被加載豺憔,就會直接返回可用的類额获,否則就會嘗試加載够庙,在嘗試加載時,會先請求雙親處理抄邀,如果雙親請求失敗耘眨,則會自己加載。自定義類加載器的雙親是應(yīng)用類加載器境肾,應(yīng)用類加載器的雙親是擴展類加載器剔难,擴展類加載器的雙親是啟動類加載器。
7奥喻、(10分)簡述解釋執(zhí)行器偶宫、即時編譯器(JIT)在java程序執(zhí)行時的工作流程,為什么有時候Java方法執(zhí)行的比C快 环鲤?
答:如上圖所示纯趋,class字節(jié)碼會被JVM最后翻譯為計算機可以看懂的機器碼在CUP中運行,這個過程由“解釋執(zhí)行器”進行解釋執(zhí)行楔绞。HotSpot通過循環(huán)回邊計數(shù)器統(tǒng)計熱點方法结闸,即時編譯器會根據(jù)JVM進程的運行時信息(profiler、hprof)進行“編譯優(yōu)化”酒朵,接著把“編譯優(yōu)化”后的熱點方法放在codeCache中桦锄,Java8中默認開啟了分層編譯,方法調(diào)用1500次以下解釋執(zhí)行蔫耽,達到1500次調(diào)用時候調(diào)C1編譯器结耀,達到10000次時調(diào)C2編譯器。C1匙铡、C2編譯后的機器碼執(zhí)行效率更高图甜。通常C2代碼的執(zhí)行效率要比C1高出30%以上。
解釋執(zhí)行相當于同聲傳譯鳖眼,你說一句我翻一句給觀眾(CPU)聽黑毅。JIT是線下翻譯,可以花時間精簡掉你的口語話表達(做編譯優(yōu)化)钦讳。相對比C程序矿瘦, 執(zhí)行java程序時,cpu拿到的是C1愿卒、C2編譯優(yōu)化后的機器碼直接執(zhí)行缚去,所以執(zhí)行效率更快一些。
8琼开、(10分)簡述如下JVM參數(shù)含義:
-XX:+PrintHeapAtGC 打印垃圾回收時的堆信息
-XX:+PrintGCTimeStamps gclog中輸出gc發(fā)生時間
-XX:+PrintGCApplicationStoppedTime 打印GC停頓時間
-Xloggc 指定gclog的文件位置
-XX:HeapDumpPath 指定導(dǎo)出堆文件的文件路徑
-verbose:class 跟蹤類的加載和卸載
-XX:+PrintCommandLineFlags 打印虛擬機顯式和隱式參數(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停頓時間
-XX:+UseG1GC 使用G1垃圾回收器
9易结、(5分)簡述說明如何寫Java程序來證明STW(Stop-the-world)的這一特性?
答案:編寫java程序,開啟gclog搞动,把jmx設(shè)置的盡量大一些(2G以上躏精,為了讓垃圾收集的時間長一些),選擇串行垃圾回收器滋尉。
在java程序中運行兩個線程:
線程1不斷像HashMap中增加鍵值內(nèi)容玉控,直到老年代打滿的時候進行map.clear()操作飞主。
線程2設(shè)置每隔100ms像控制臺輸出時間戳日志狮惜。
當?shù)诰€程1進行map.clear()后觀察gclog,在gc日志中我們看到了fullgc日志碌识,以及停頓時間碾篡。我們發(fā)現(xiàn),這時候線程2的日志打印的時間戳間隔變長了筏餐,由100毫秒的停頓變成了幾百毫秒的停頓开泽,這個幾百毫秒的停頓與gc日志中的fullgc停頓恰好相符,就是大名鼎鼎的Stop-the-world魁瞪。在STW發(fā)生時穆律,幾乎所有線程掛起,程序像hung死一樣导俘。
10峦耘、(20分)上機題,打開vmware虛擬機旅薄,使用appuser用戶登錄, 密碼: ********辅髓,執(zhí)行腳本./TestStarter.sh
(1)解決啟動時候的報錯,請把調(diào)整后的JVM參數(shù)寫到答題紙上少梁。
(2)成功啟動程序后洛口,結(jié)合linux系統(tǒng)工具、JVM工具分析當前程序中的存在問題及可能的性能瓶頸凯沪。
答:一共5個問題第焰,該題主要是考察結(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:棾永剩空間不夠。 //增加-xss大小讓程序正常啟動笔喉。
問題2:類加載器造成的死鎖取视。 //使用jstack 觀察線程wait
問題3:存在高cpu線程。 //arthas dashboard觀察高cup線程常挚,jad查看高cpu方法作谭;或者使用查看比較高的線程id轉(zhuǎn)換16進制之后再jstack導(dǎo)出的棧文件中查找導(dǎo)致cpu過高的線程。
問題4:存在大HashMap沒有被正常垃圾回收引發(fā)fullgc的情況奄毡。//使用mat進行分析折欠。
問題5:存在3個線程死鎖問題。 // Jstack -l 查看死鎖