吊打面試官----精華彙總之JVM

1.   描述一下 JVM 加載 Class 文件的原理機制?
2.   什麼是類加載器?
3.   類加載器有哪些?
4.   什麼是tomcat類加載機制?

前面3個類加載和默認的一致,CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader則是Tomcat自己定義的類加載器,它們分別加載 /common/*、/server/*、/shared/*(在tomcat 6之後已經合併到根目錄下的lib目錄下)和 /WebApp/WEB-INF/* 中的Java類庫。其中WebApp類加載器和Jsp類加載器通常會存在多個實例,每一個Web應用程序對應一個WebApp類加載器,每一個JSP文件對應一個Jsp類加載器。

tomcat 違背了java 推薦的雙親委派模型了嗎?違背了。
雙親委派模型要求除了頂層的啓動類加載器之外,其餘的類加載器都應當由自己的父類加載器加載。
顯然,tomcat 不是這樣實現,tomcat 爲了實現隔離性,沒有遵守這個約定,每個webappClassLoader加載自己的目錄下的class文件,不會傳遞給父類加載器。
如果tomcat 的 Common ClassLoader 想加載 WebApp ClassLoader 中的類,該怎麼辦?可以使用線程上下文類加載器實現,使用線程上下文加載器,可以讓父類加載器請求子類加載器去完成類加載的動作。

 

5.   類加載器雙親委派模型機制

一個 java 類的完整的生命週期會經歷加載、連接、初始化、使用、和卸載五大階段
加載:將.class字節碼加載到jvm中,並生成一個class對象。
連接:進行驗證(是否能被jvm執行,主要包括文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證)、
準備:爲靜態變量分配內存;
解析:把類中的符號引用轉換爲直接引用;
初始化:爲類的靜態變量賦予用戶定義的默認值和執行靜態代碼塊;
在例子中,在加載階段將ClassLoaderTest加載到jvm中;在連接階段對靜態變量分配內存空間並初始化默認值,此時classLoaderTest = null;a =0;b=0;
main方法中通過
        ClassLoaderTest instance = getInstance();


待完善;

 

6.   Java 內存分配?

 

首先要說一下JVM內存空間分爲五部分,分別是:方法區、堆、Java虛擬機棧、本地方法棧、程序計數器

方法區主要用來存放類信息、類的靜態變量、常量、運行時常量池等,方法區的大小是可以動態擴展的,

主要存放的是數組、類的實例對象、字符串常量池等。

Java虛擬機棧是描述JAVA方法運行過程的內存模型,Java虛擬機棧會爲每一個即將執行的方法創建一個叫做“棧幀”的區域,該區域用來存儲該方法運行時需要的一些信息,包括:局部變量表、操作數棧、動態鏈接、方法返回地址等。比如我們方法執行過程中需要創建變量時,就會將局部變量插入到局部變量表中,局部變量的運算、傳遞等在操作數棧中進行,當方法執行結束後,這個方法對應的棧幀將出棧,並釋放內存空間。棧中會發生的兩種異常,StackOverFlowError和OutOfMemoryError,StackOverFlowError表示當前線程申請的棧超過了事先定好的棧的最大深度,但內存空間可能還有很多。 而OutOfMemoryError是指當線程申請棧時發現棧已經滿了,而且內存也全都用光了。

本地方法棧結構上和Java虛擬機棧一樣,只不過Java虛擬機棧是運行Java方法的區域,而本地方法棧是運行本地方法的內存模型。運行本地方法時也會創建棧幀,同樣棧幀裏也有局部變量表、操作數棧、動態鏈接和方法返回地址等,在本地方法執行結束後棧幀也會出棧並釋放內存資源,也會發生OutOfMemoryError。

最後是程序計數器,程序計數器是一個比較小的內存空間,用來記錄當前線程正在執行的那一條字節碼指令的地址。如果當前線程正在執行的是本地方法,那麼此時程序計數器爲空。程序計數器有兩個作用,1、字節碼解釋器通過改變程序計數器來一次讀取指令,從而實現代碼的流程控制,比如我們常見的順序、循環、選擇、異常處理等。2、在多線程的情況下,程序計數器用來記錄當前線程執行的位置,當線程切換回來的時候仍然可以知道該線程上次執行到了哪裏。而且程序計數器是唯一一個不會出現OutOfMeroryError的內存區域。

 

方法區和堆都是線程共享的,在JVM啓動時創建,在JVM停止時銷燬,而Java虛擬機棧、本地方法棧、程序計數器是線程私有的,隨線程的創建而創建,隨線程的結束而死亡。

1、 String s = new String(“abc”) ; 創建了多上個對象?
答:創建了2個對象,會在編譯期創建一個"abc"保存到對象池(常量池)
運行期的出現了new關鍵字的時候會將對象池中的"abc"複製到堆內存中保存。
2、String s1 = new String(“abc”) ;String s2 = new String(“abc”) ; 創建了多少個對象?
答:創建了三個對象,編譯器會在對象池中創建"abc",在運行期會將對象池中的"abc"對象複製兩個到堆區。
3、  String  str=“abc”;String  str1=new String(“abc”),一共創建了幾個對象呢?
答:創建了兩個對象,編譯器創建在對象池中創建一個"abc",運行時候會將對象池中的"abc"複製一份到堆中。
String  str1 = new String(“abc”) 和String  str2=“abc”;

7.   Java 堆的結構是什麼樣子的?
8.   簡述各個版本內存區域的變化?
9.   說說各個區域的作用?
10.                Java 中會存在內存泄漏嗎,簡述一下?
11.                Java 類加載過程?
12.                什麼是GC? 爲什麼要有 GC?
13.                簡述一下Java 垃圾回收機制?
14.                如何判斷一個對象是否存活?
15.                垃圾回收的優點和原理,並考慮 2 種回收機制?基本原理是什麼?
16.                深拷貝和淺拷貝?
17.                什麼是分佈式垃圾回收(DGC)?它是如何工作的?
18.                在 Java 中,對象什麼時候可以被垃圾回收?
19.                簡述Minor GC 和 Major GC?
20.                Java 中垃圾收集的方法有哪些?
21.                講講你理解的性能評價及測試指標?
22.                常用的性能優化方式有哪些?
23.                說說分佈式緩存和一致性哈希?
24.                什麼是GC調優?

對象優先在Eden分配
大多數情況下,對象在新生代Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。但也有一種情況,在內存擔保機制下,無法安置的對象會直接進到老年代。

大對象直接進入老年代
大對象是指需要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組。
虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配。目的就是避免在Eden區及兩個Survivor區之間發生大量的內存複製。

長期存活的對象將進入老年代
虛擬機給每個對象定義了一個對象年齡(Age)計數器。如果對象在Eden出生並經過第一次Minor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且對象年齡設爲1 。對象在Survivor區中每經過一次Minor GC,年齡就加1歲,當年齡達到15歲(默認值),就會被晉升到老年代中。

滿足如下條件之一,對象能晉升老年代:
1.對象的年齡達到了MaxTenuringThreshold(默認15)能晉升老年代。
2.如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

面試題:什麼條件下會觸發Minor GC  和Full  GC ?
答:當創建對象的時候Eden的空間不足則觸發MinorGC,Minor GC 非常頻繁,一般回收速度也比較快,Survivor滿不會引發MinorGC。當老年代空間不足的時候會觸發FullGC,FullGC 會同時將老年代和新生代(年輕代)的垃圾進行回收。Full GC叫做MajorGC。

Minor GC觸發條件:當Eden區滿時,觸發Minor GC。
Full GC觸發條件:
(1)調用System.gc時,系統建議執行Full GC,但是不必然執行
(2)老年代空間不足
(3)方法區(永生代  jdk1.8已經去掉這個概念)空間不足
(4)通過Minor GC後進入老年代的平均大小大於老年代的可用內存
(5)由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小

Major GC 是清理OldGen。 Full GC 是清理整個堆空間—包括年輕代和永久代以及老年代OldGen。


Full GC太過頻繁如何處理?

Full GC本身是好的,可以清除老年代的垃圾,但是如果Full GC發生的頻率高了,就會影響性能,同時意味着系統內存分配機制出現問題。
因爲Full GC本身執行時間較長(甚至超過1秒),而且除非採用G1 GC,否則其它的GC方式都會或多或少掛起所有線程執行(Stop-the-world),如果Full GC頻繁發生,系統被掛起的次數就會增加,響應時間就會變慢。
同時,Full GC頻繁發生,意味着你的內存分配機制存在問題,也許是內存泄露,有大量內存垃圾不斷在老年代產生;也許是你的大對象(緩存)過多;也有可能是你的參數設置不好,minor GC清理不掉內存,導致每次minor GC都會觸發Full GC;還有可能是你的老年代大小參數設置錯誤,老年代過小等等原因。也許是服務器問題,需要運維或者客服支撐;也許是數據庫查詢出現大數據量導致;

老年代與新生代不同,老年代對象存活的時間比較長、比較穩定,因此採用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,然後再進行回收未被標記的對象,回收後對用空出的空間要麼進行合併、要麼標記出來便於下次進行分配,總之目的就是要減少內存碎片帶來的效率損耗。
在執行機制上JVM提供了串行GC(Serial MSC)、並行GC(Parallel MSC)和併發GC(CMS)

 

標記清除:

標記整理:

複製算法:

JVM回收算法小結:

  • 標記清除速度快,但是會產生內存碎片;
  • 標記整理解決了標記清除內存碎片的問題,但是每次都得移動對象,因此成本很高;
  • 複製算法沒有內存碎片也不需要移動對象,但是導致空間的浪費;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章