Java底層知識JVM、GC

1、JVM如何加載.class文件?

答:Java虛擬機,最值的學習的兩點,JVM內存結構模型以及GC。JVM是一個內存中的虛擬機,JVM的存儲就是內存,例如類、常量、變量、方法都是在內存中。Java虛擬機是一種抽象化的虛擬機,在實際的計算機上仿真模擬各種計算機功能來實現,JVM有自己完善的硬件架構,如處理器,堆棧,寄存器等等,還具有相應的指令系統。JVM屏蔽了與具體操作系統平臺相關的信息,使得Java程序只需生成在Java虛擬機上運行的目標代碼即字節碼,就可以在多種平臺上不加修改的運行,一次編譯到處運行。

  1)、JVM主要由Runtime Data Area 、Class Loader、Execution Engine、Native Interface四部分組成。主要通過Class Loader將符合格式的class文件進行加載到內存裏面,並通過Execution Engine去解析class文件裏面的字節碼並提交給操作系統去執行等等。
  2)、Class Loader,依據特定格式,加載class文件到內存,加載javac編譯好的class文件到內存中,不是自己隨意創建的class文件,加載的文件是有格式要求的,符合格式就可以加載。
  3)、Execution Engine,加載的文件可以不可以運行,是由Execution Engine進行負責,Execution Engine叫做解釋器,對命令進行解析,解析完成後提交給真正的操作系統進行執行。
  4)、Native Interface,融合不同開發語言的原生庫爲Java所用,Native Interface稱爲本地接口。
  5)、Runtime Data Area,JVM內存空間結構模型,所寫的程序都會加載到這裏,之後開始運行。

2、談談Java反射。

答:Java反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法。對於任意一個對象,都能夠調用它的任意方法和屬性。這種動態獲取信息以及動態調用對象方法的功能稱爲Java語言的反射機制。

 1 package com.reflect;
 2 
 3 import java.lang.reflect.Field;
 4 import java.lang.reflect.Method;
 5 
 6 /**
 7  *
 8  */
 9 public class Student {
10 
11     private String name;// 姓名
12 
13     /**
14      * 公有的方法
15      *
16      * @param hello
17      */
18     public void sayHi(String hello) {
19         System.out.println(hello + " " + name);
20     }
21 
22     /**
23      * 私有的方法
24      *
25      * @param tag
26      * @return
27      */
28     private String sayHello(String tag) {
29         return "hello " + tag;
30     }
31 
32     // 反射就是類中的各種成分映射成一個個Java對象
33     public static void main(String[] args) throws Exception {
34         // 獲取到類的對象,必須是全路徑加類名,纔可以獲取成功
35         Class clazz = Class.forName("com.reflect.Student");
36         // 創建類實例,返回的是泛型,需要強轉
37         Student student = (Student) clazz.newInstance();
38         // 打印類的名稱
39         System.out.println("Class name is " + clazz.getName());
40         // 首先獲取到類,再對類的實例進行調用,調用類裏面的方法
41         // 如果直接根據創建的實例調用公有的方法,就不是反射的用法了
42         // student.sayHi("張三");
43 
44         // 反射可以直接調用公有的,私有的方法的。獲取聲明的方法。
45         // getDeclaredMethod可以獲取到包括公共的,保護的,私有,包的方法,但是不能獲取到繼承的方法和實現接口的方法。
46         Method sayHello = clazz.getDeclaredMethod("sayHello", String.class);
47         // 將獲取到的私有的方法,設置爲true,默認是false,不設置報錯。
48         sayHello.setAccessible(true);
49         // 此時,開始調用這個私有的方法,參數一是實例名稱,參數二是參數名稱。
50         Object invoke = sayHello.invoke(student, "張三三");
51         // 打印私有的方法
52         System.out.println("sayHello result is : " + invoke);
53 
54         // 通過反射獲取到公共的方法
55         // getMethod方法可以獲取公共的方法,獲取到繼承的方法和實現接口的方法。
56         Method sayHi = clazz.getMethod("sayHi", String.class);
57         // 通過invoke反射獲取到公共的方法
58         sayHi.invoke(student, "hello");
59 
60         // 獲取私有類型的字段
61         Field name = clazz.getDeclaredField("name");
62         // 設置爲ture,因爲屬性name是私有的
63         name.setAccessible(true);
64         // 通過set方法進行設置,參數一是實例,參數二名稱
65         name.set(student, "李四四");
66         sayHi.invoke(student, "hello");
67 
68     }
69 
70 }

3、類從編譯到執行的過程。

答:之所以可以獲取到類的屬性或者方法,都是先獲取到Class對象,而獲取到該類的Class對象,必須先要獲取到該類的字節碼文件的對象。

1)、編譯器將類源文件編譯爲class字節碼文件。
2)、ClassLoader類加載器將字節碼轉換爲JVM中的Class 類對象。
3)、JVM利用Class類對象實例化爲類對象。

 

4、談談ClassLoader。

  答:ClassLoader在Java中有着非常重要的作用,它主要工作在Class裝載的加載階段,其主要作用是從系統外部獲得Class二進制數據流。它是Java的核心組件,所有的Class都是由ClassLoader進行加載的,ClassLoader負責通過將Class文件裏的二進制數據流裝載進系統,然後交給Java虛擬機進行連接,初始化等操作。

5、ClassLoader的種類。

  1)、BootStrapClassLoader,C++編寫,加載核心庫java.*,加載Java自帶的核心類。
  2)、ExtClassLoader,Java編寫,加載擴展庫javax.*,是用戶可見的ClassLoader,用於加載位於jre/lib/exe下面的jar包,可以將自定義的包放到該目錄下,通過ClassLoader進行加載。
  3)、AppClassLoader,Java編寫,加載程序所在的目錄,是用戶可見的ClassLoader,通過代碼可以看得到的,加載ClassPath下面的內容的。
  4)、自定義ClassLoader,Java編寫,定製化加載。
 

6、類的加載方式。

  1)、第一種,隱式加載,通過new關鍵字,程序在運行過程中,遇到通過new方式來生成對象的時候,飲食調用類加載器,加載對應的類到JVM中,new創建對象最常用。支持向構造器傳入參數創建實例對象。
  2)、第二種,顯示加載,loadClass,forName等等,對於顯示加載來講,當我們取到class對象之後,需要調用class對象的newInstance方法來生成對象的實例。


7、loadClass,forName的區別。

  1)、首先都可以在運行時對任意一個類,都能夠知道該類的所有屬性和方法,對於任意一個對象都能夠調用它的任意方法和屬性。
  2)、Class.forName得到的class是已經初始化完成的。已經完成了第三步了初始化了。
  3)、Classloder.loadClass得到的class是還沒有鏈接的。只完成了第一步加載,第二步鏈接和第三步初始化都沒有執行。


8、類的裝載(加載是裝載的一個過程)過程,裝載是Class類生成的過程。

  1)、第一步,加載,通過ClassLoader加載class文件字節碼,生成Class對象。。
  2)、第二步,鏈接,校驗是檢查加載的class的正確性和安全性,準備是爲類變量分配存儲空間並設置類變量初始值,解析是Jvm將常量池內的符號引用轉換爲直接引用,解析是可選的。
  3)、第三步,初始化,執行類變量賦值和靜態代碼塊。

 

9、Java的內存模型,JVM的內存模型-JDK1.8。

答:可以從線程的角度(線程角度:那些區是線程私有的,那些區是線程共享的)和存儲的角度進行觀察。

1)、線程私有:程序計數器,虛擬機棧,本地方法棧。
2)、線程共享:MetaSpace,Java堆。

 

10、程序計數器(Program Counter Register)。

答:程序計數器是一塊較小的內存空間,作用是當前線程所執行的字節碼行號指示器。

  1)、當前線程所指向的字節碼行號指示器(邏輯),程序計數器是邏輯計數器,而非物理計數器。
  2)、在虛擬機的概念模型裏面,字節碼解釋器工作時,就是改變計數器的值來選取下一條需要執行的字節碼指令,包括分支,循環,異常,跳轉,異常處理,線程恢復等計數功能。
  3)、和線程是一對一的關心即"線程私有"。由於JVM的多線程是通過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器只會執行一條線程中的指令,因此爲了線程切換後能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,稱這類內存區域爲線程私有的內存。
  4)、對Java方法計數,如果是Native方法則計數器值爲Underfined。如果線程正在執行一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址,如果正在執行是Native方法,這個計數器值爲Underfined。
  5)、不會發生內存泄漏。由於只是記錄行號,程序計數器不用擔心內存泄露的問題。

 

11、Java虛擬機棧(Stack)。

答:1)、Java方法執行的內存模型。Java虛擬機棧也是線程私有的,可以說是Java方法執行的內存模型。

  2)、包含多個棧幀。每個方法在執行的時候,都會創建一個棧幀,即方法運行期間的基礎數據結構,棧幀用於存儲局部變量表、操作棧、動態連接、返回地址,每個方法執行中對應虛擬機棧幀從入棧到出棧的過程,Java虛擬機棧用來存儲棧幀,而棧幀持有局部變量和部分結果,以及參與方法的調用與返回,當方法調用結束時,幀纔會被銷燬。這裏的關鍵信息是虛擬機棧包含了單個線程每個方法執行的棧幀,而棧幀則存儲了局部變量表,操作棧、動態連接、返回地址等信息。


12、局部變量表和操作數棧的區別。

答:1)、局部變量表,包含方法執行過程中的所有變量。

  2)、操作數棧,入棧、出棧、複製、交換、產生消費變量。

 

13、本地方法棧。

答:與虛擬機棧相似,主要作用於標註了native的方法。

 

14、JVM三大性能調優參數,-Xms、-Xmx、-Xss的含義。

答:1)、-Xms,堆的初始值,表示的是初始的Java堆的大小,即該進程剛創建出來的時候它的專屬Java堆的大小,一旦對象容量超過了Java堆的初始容量,Java堆將會自動擴容,擴容到-Xmx大小。
  2)、-Xmx,堆能達到的最大值。通常情況下,將-Xmx和-Xms設置成一樣的,因爲當heap不夠用而發生擴容的時候,會發生內存抖動,影響程序運行時的穩定性。
  3)、-Xss,規定了每個線程虛擬機棧(堆棧)的大小。一般情況下256k就足夠了,此配置將影響此進程中併發線程數的大小。


15、Java內存模型中堆和棧的區別,內存分配策略。

答:如果向弄清楚堆和棧的區別,需要先弄清楚內存分配策略。內存分配策略分爲靜態的、堆式的、棧式的。

  1)、靜態存儲,編譯時確定每個數據目標在運行時的存儲空間需求。因而在編譯時就可以給它們分配固定的內存空間,這種分配策略要求程序代碼不允許有可變數據結構的存在,也不允許有嵌套或者遞歸的j結構出現,因爲它們會導致編譯程序無法計算準備的存儲空間。
  2)、棧式存儲,數據區需求在編譯時未知,運行時模塊入口前確定。可以稱爲動態的存儲分配,是由一個類似於堆棧的運行棧來實現的,和靜態存儲的分配相反,棧式存儲方案中,程序堆數據區的要求在編譯時是完全未知的,只有到了運行時才能知道,但是規定在運行中進入一個程序模塊的時候,必須知道該程序模塊所需要的數據區的大小,才能分配其內存,棧式存儲按照先進後出的分配原則。
  3)、堆式存儲,編譯的時候或者運行的時候模塊入口都無法確定,動態分配。比如可變長度串,對象實例,堆由大片的可利用塊和空閒塊組成,堆中的內存可以按照任意順序分配和釋放。

16、Java內存模型中的堆和棧的聯繫。

答:引用對象,數組的時候,棧裏面定義變量保存堆中目標的首地址。

  創建好的數組、對象實例,都會被保存到堆中,想要引用堆中的某個對象或者數組,咱們可以在棧中定義一個特殊的變量,這種棧中的這個變量的取值等於數組或者對象,在堆內存中的首地址。棧中的這個變量就成了數組或者對象的引用變量,以後就可以在程序中使用棧中的引用變量來訪問堆中的數組或者對象。引用變量就相當於是爲數組或者對象起的一個名稱,引用變量是普通的變量,定義的時候在棧中分配,引用變量在程序運行到其作用域之外後呢,就會被釋放掉了,而數組和對象本身在堆中分配,即使程序運行到使用new產生數組或者對象的語句所在的代碼塊之外,數組和對象本身佔據的內存不會被釋放,在沒有引用變量指向的時候,纔會變爲垃圾,需要等待隨後的一個b不確定的時間被垃圾回收器釋放掉。
 

17、Java內存模型中堆和棧的區別。

答:1)、管理方式,棧自動釋放,堆需要GC。JVM可以針對n內存棧進行管理操作,而且該內存空間的釋放是編譯器就可以操作的內容,而堆空間在java中,JVM執行引擎不會對其進行釋放,而是讓垃圾回收器進行自動回收。
  2)、空間大小,棧比堆小。這是棧空間裏面存儲的數據以及本身需要的數據特性來決定的,而堆空間在JVM堆實例進行分配的時候,一般大小都比較大,因爲堆空間在一個Java程序中需要存儲比較多的java對象數據。
  3)、碎片相關,棧產生的碎片遠小於堆。針對堆空間而言,即使垃圾回收器能夠進行自動堆內存回收,但是堆空間的活動量相對棧空間而言比較大,很有可能存在長期的堆空間分配和釋放操作,而且垃圾回收器b不是實時的,它有可能使得堆空間的內存碎片逐漸累積起來。針對棧空間而言,因爲它本身就是棧的數據結構,它的操作都是一一對應的,而且每一個最小單位的結構棧幀和堆空間內複雜的n內存結構不一樣,所以它一般在使用過程中很少出現內存碎片。
  4)、分配方式,棧支持靜態和動態分配,而堆僅支持動態分配。堆在有垃圾回收器的前提下,還是需要考慮其釋放的問題的。
  5)、效率,棧的效率比堆高。因爲內存塊的排列本身就是一個堆棧結構,所以棧空間的效率比堆空間的效率高很多。計算機底層內存結構本身就是使用了堆棧結構,使得棧空間和底層結構更加符合。棧的操作簡單,只設計到了入棧和出棧。棧空間相對堆空間是靈活程度不夠,特別是在動態管理的時候,而堆空間最大的優點是動態分配,因爲它在計算機底層可能是一個雙向鏈表的結構。

 


18、Java垃圾回收機制。

答:對象被判定爲垃圾的標準,沒有被其它對象引用的時候,就被判定爲垃圾了。對於系統而言,它就是垃圾,佔據的內存就要被釋放。



19、判定對象是否爲垃圾的算法。

答:1)、引用計數算法。
  2)、可達性分析算法。   

 


20、引用計數算法。

答:判斷對象的引用數量。
  1)、通過判斷對象的引用數量來決定對象是否可以被回收。
  2)、堆中的每個對象實例都有一個引用計數器,被引用則加一,完成引用則減一。當一個對象被創建的時候,若該對象實例分配一個引用變量,該對象實例的引用計數就設置爲1,若該對象又被另外一個對象所引用,則該對象的引用計數器繼續加一,而當該對象實例的某個引用超過了生命週期,或者被設置爲一個新值的時候,該對象實例的引用計算便會減一。
  3)、任何引用計數爲0的對象實例可以被當作垃圾收集。
  4)、優點是執行效率高,程序執行受影響較小。
  5)、缺點,實現過於簡單,無法檢測出循環引用的情況,導致內存泄漏。如父對象引用子對象,子對象引用父對象。



21、可達性分析算法。

答:通過判斷對象的引用鏈是否可達來決定對象是否可以被回收。可達性算法是從離散數學的圖論引入的,程序把所有的引入關係看作一張圖,通過一系列名爲GC root作爲起始點,從這些節點開始向下搜索,搜索所走過的路徑就被稱爲引用鏈,當一個對象從GC root沒有任何引用鏈相連,從圖論上來說就是從GC root到這個對象是不可達的,這個時候就證明了這個對象是不可用的,它就被標記爲垃圾了。



22、可達性分析算法,什麼可以作爲GC Root的對象。

  1)、虛擬機棧中引用的對象(棧幀中的本地變量表中引用的對象),比如,定義的局部變量。
  2)、方法區中的常量引用的對象,比如,類裏面定義的常量,而該常量保存的某個對象的地址,那麼被保存的對象也稱爲GC的根對象,當別的對象引用到它的時候就會形成關係鏈。
  3)、方法區中的類靜態屬性引用的對象。
  4)、本地方法棧中JNI(Native方法)的引用對象。
  5)、活躍線程的引用對象。

 

23、談談你瞭解的垃圾回收算法。

  1)、標記-清除算法(Mark and Sweep)。
    a)、標記就是從根集合進行掃描,對存活的對象進行標記,使用的是可達性算法來找到垃圾對象。
    b)、清除,標記完成後,對堆內存從頭到尾進行線性遍歷,如果發現對象m沒有被標識爲可達對象,就將此對象佔用的內存回收了,並且將之前標識爲可達的標識清除掉以便進行下一次垃圾回收,回收不可達對象內存。
    c)、標記-清除算法缺點,就是產生大量碎片化,由於標記清除不需要對象的移動,並且僅對b不存活的對象進行處理,因此標記清除之後呢,會產生大量不連續的內存碎片,空間碎片太多,可能會導致以後在程序運行過程中,需要分配較大的對象時,無法找到足夠的連續內存,而不得不提前觸發另一次垃圾收集動作。
  2)、複製算法(Copying),這種算法適用於對象存活率低的場景,採用此算法回收年輕代。
    a)、分爲對象面和空閒面。複製算法將可用的內存按照容量和一定比例劃分爲兩塊或者多個塊,並選擇其中一塊或者兩塊作爲對象面,其他的就作爲空閒面。
    b)、對象則是在對象面上創建的。當被定義爲對象面的塊的內存使用完之後,就將還存活着的對象複製到其中一塊空閒面上。
    c)、存活的對象被從對象面複製到空閒面。就是還不是垃圾的對象複製到空閒面上。
    d)、將對象面所有對象內存清除,然後再將已使用過的內存空間一次清理掉。
    e)、複製算法解決碎片化的問題。順序分配內存,簡單高效。適用於對象存活率低的場景。
  3)、標記-整理算法(Compacting),適用於老年代的對象回收。標記-整理算法是在標記-清除算法的基礎上又進行了對象的移動,因此成本更高,但是卻解決了內存碎片的問題。
    a)、標記,從根集合進行掃描,對存活的對象進行標記。
    b)、清除,移動所有存活的對象,且按照內存地址次序依次排列,然後將末端內存地址以後的內存全部回收。
    c)、標記-整理算法優點,避免內存的不連續性。不用設置兩塊內存互換。適用於對象存活率極高的場景。
  4)、分代收集算法(Generational Collector),主流的垃圾回收算法。
    a)、垃圾回收算法的組合拳。
    b)、按照對象聲明週期的不同劃分區域以採用不同的垃圾回收算法。將堆內存進行進一步劃分,不同的對象的聲明週期以及存活情況是不一樣的,將不同生命週期的對象分配到堆中不同的區域,並對堆內存不同區域採用不同的策略進行回收。
    c)、分代收集算法目的,提高JVM的回收效率。

 

24、分代收集算法(Generational Collector)。

  1)、JDK6、JDK7的堆內存,分爲年輕代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation)。
  2)、JDK8以及其以後的版本,的堆內存,分爲年輕代(Young Generation)、老年代(Old Generation)。年輕代的對象存活率低就會採用複製算法,老年代對象存活率高,就會採用標記-清除算法或者標記整理算法。
 

25、分代收集算法(Generational Collector),GC的分類。

  1)、Minor GC,發生在年輕代中的l垃圾收集動作,所採用的複製算法,年輕代是幾乎所有Java對象出生的地方,即Java對象申請的內存以及存放都是在年輕代的,Java中的大部分對象不需要長久的存活,具有朝生熄滅的性質。當一個對象被判斷爲死亡的時候,GC就有責任回收掉這部分對象的內存空間,新生代是GC收集垃圾的頻繁區域。
  2)、Full GC,這種GC和老年代相關,由於對老年代的回收一般會伴隨着年輕代的垃圾收集,因此,此種方式稱爲Full GC。


26、分代收集算法(Generational Collector),年輕代的介紹。

答:儘可能快速地收集掉那些生命週期短的對象。

  1)、Eden區,伊甸園,人類的起源,是對象剛被創建出來的時候,其內存空間首先是被分配在Eden區的,如若Eden區放不下新創建的對象的時候,對象也有可能直接放到Survivor區,甚至是老年代中。
  2)、兩個Survivor區(倖存者空間),分別被定義爲from區和to區,那個是fron區,那個是to區也不是固定的,會隨着垃圾回收的進行而相互轉換,年輕代的目標就是儘可能快速收集掉那些生命週期較短的對象,一般情況下,所有新生成的對象首先都是放在年輕代的。
  3)、年輕代的內存都會按照8-1-1的比例進行劃分,絕大部分對象是在Eden區生成,新生代中98%的對象都是朝生夕死的,所以不需要按照1-1-1的比例來劃分內存空間,而是8Eden-1from-1to區,每次使用Eden和其中的一塊Survivor區,當進行垃圾回收的時候,將Eden和Survivor區存活的對象一次性複製到另一塊Survivor區,最後清理掉Eden區和用過的Survivor區,當Survivor區空間不夠用的時候,則需要依賴老年代,進行分配的擔保了。

 

27、分代收集算法(Generational Collector),對象如何晉升到老年代。

  1)、經歷一定Minor次數依然存活的對象。長期存活的對象會進入老年代,默認是每經過一次長一歲,達到Minor15歲的時候就晉升到老年代。
  2)、Survivor區中存放不下的對象。如果是Edea區或者Survivor區放不下的對象會直接進入老年代,對象優先是Edea區進行分配,當Edea區沒有足夠的空間分配的時候,會觸發一次Minor GC,每次Minor GC結束Edea區就會被清空,因爲它會把Edea區還依然存活的對象放到Survivor區中,當Survivor區中放不下的時候,則有分派擔保進入到老年代中,因爲不能放不下了就將它幹掉。
  3)、新生成的大對象直接進入到老年代當中,(-XX:+PretenuerSizeThreshold),可以通過這個參數控制大對象的大小,如果超過這個參數的對象,一經生成直接放入到老年代中。

 

28、分代收集算法(Generational Collector),常用性能調優參數。

答:1)、-XX:SurvivorRatio : Eden和Survivor的比值,默認是8比1。
  2)、-XX:NewRatio : 老年代和年輕代內存大小的比例。新生代和老年代的內存大小由-Xms、-Xmx參數決定的。
  3)、-XX:MaxTenuringThreshold : 對象從年輕代晉升到老生代經過GC次數的最大閾值。

 

29、分代收集算法(Generational Collector),老年代。

答;老年代是存放生命週期較長的對象,在年輕代中經過了n次垃圾回收依然存活的對象就會被放到老年代中。
  1)、老年代的內存比新生代的內存大,大概比例是2比1。新生代使用的是複製算法,複製成本較低。
  2)、老年代對象存活率較高,沒有額外空間分配擔保,使用的算法是標記-清理算法、標記-整理算法進行回收。
  3)、當觸發老年代的垃圾回收的時候,通常也伴隨着對新生代堆內存的回收,即對整個堆進行垃圾回收,這便是所謂的Full GC。Major GC通常是和Full GC等價的,即收集整個GC堆。Full GC比Major GC慢,一般慢十倍以上,但執行頻率低。
  4)、當Edea區空間不足的時候,會觸發Minor GC回收年輕代的內存空間。什麼情況下會觸發Full GC內。

 

30、分代收集算法(Generational Collector),觸發Full GC的條件。

  1)、老年代空間不足。如果創建大對象直接放入到老年代,如果老年代空間不足,就會觸發Full GC。
  2)、永久代空間不足。針對JDK7以及之前的版本,當系統中需要加載的類,調用的方法很多,同時持久代中沒有足夠的空間去存放類,信息,方法的信息,就會觸發一次Full GC。
  3)、CMS GC的時候出現promotion failed,concurrent mode failure。對於採用CMS進行老年代GC的程序而言,尤其要注意GC日誌中是否有promotion failed,concurrent mode failure,當這兩種情況發生的時候,可能會觸發Full GC,promotion failed是在進行Minor GC的時候,Survivor空間是放不下了,對象只能放入到老年代,而此時老年代也放不下了,此時就會造成promotion failed。concurrent mode failure是在執行CMS GC的時候,同時有對象要放入老年代中,而此時老年代空間不足就會造成concurrent mode failure。
  4)、Minor GC晉升到老年代的平均大小大於老年代的剩餘空間。
  5)、調用System.gc()。在程序中調用該方法,顯示觸發Full GC,對老年代和新生代進行回收,但是此方法提醒虛擬機需要在這裏進行回收,但是回收不回收還是看虛擬機。
  6)、使用RMI來進行RPC或者管理的JDK應用,每小時執行1次Full GC。

 

31、分代收集算法(Generational Collector)關鍵詞。

  1)、Stop-the-World,JVM由於要執行GC而停止了應用程序的執行。在任何一種GC算法中都會發生,當Stop-the-World發生時,除了GC所需的線程,所有線程都處於等待狀態,直到GC任務完成。多數GC優化通過減少Stop-the-world發生的時間來提高程序性能,從而使系統具有高吞吐,低停頓的效果。
  2)、Safepoint,垃圾收集器裏面的安全點。分析過程中對象引用關係不會發生變化的點,在可達性分析中,要分析那個對象沒有引用的時候,必須在一個快照的狀態點進行,在這個點所有的線程都被凍結了,不可以出現分析過程中對象引用關係還在不停變化的情況,因此分析結果需要在某個節點具備確定性,該節點便叫做安全點,程序不是那個點就停頓下來的,而是到達安全點纔會停頓下來。產生Safepoint的地方是方法調用,循環跳轉,異常跳轉等等,一旦GC發生,所有的線程都跑到最新的安全點纔會停頓下來,如果發現線程不在安全點,就恢復線程,等其跑到安全點再說。安全點數量得適中,安全點的數量不能太少,太少就會讓GC等待太長時間,也不能太多,因爲太多會增加程序運行的負荷。
 

32、常見的垃圾收集器,JVM的運行模式。

答:第一種是Server,第二種是Client。
  1)、Client啓動速度較快,採用的是輕量級的虛擬機。
  2)、Server啓動速度較慢,啓動進入穩定器,長期運行之後,Server模式程序運行比Client快,這是因爲Server模式採用的是重量級的虛擬機,對程序採用了更多的優化。
  3)、java -version可以查看是使用的那種運行模式。


33、常見的垃圾收集器,垃圾收集器之間的聯繫。

答:垃圾收集器和JVM實現緊密相關的,虛擬機所處的區域,說明它是屬於新生代的收集器還是老年代的收集器,如果兩個收集器之間有連線,就說明它們可以搭配使用。

 

34、年輕代常見的垃圾收集器,Serial收集器(-XX:+UseSerialGC,複製算法)。

答:1)、在程序啓動的時候,通過設置UseSerialGC參數使得年輕代使用該垃圾收集器回收。Serial收集器是java最基本,歷史最悠久的收集器,jdk1.3版本之前,年輕代收集器的唯一選擇。
  2)、單線程收集,進行垃圾收集時,必須暫停所有工作線程。單線程的意義不僅僅是說明只會使用一個CPU或者一條收集線程去完成垃圾收集工作,更重要的是,在它進行垃圾收集的時候,必須暫停其它所有工作線程,直到它收集結束。
  3)、簡單高效,Client模式下默認的年輕代收集器。用戶桌面應用場景中,分配給虛擬機管理的內存一般來說不會很大,收集幾十兆甚至一兩百兆,年輕代停頓時間會在幾十毫秒到最多一百毫秒之間。

 

35、年輕代常見的垃圾收集器,ParNew收集器(-XX:+UseParNewGC,複製算法)。

答:1)、在程序啓動的時候,通過設置UseParNewGC參數使得年輕代使用該垃圾收集器回收。
  2)、多線程收集,其餘的行爲,特點和Serial收集器一樣。是Server模式下虛擬機年輕代首選的收集器。
  3)、單核執行效率不如Serial,因爲存在線程交互開銷,在多核下執行纔有優勢。默認開啓的收集線程數和CPU數量相同,在CPU數量非常多的情況下,可以使用參數限制垃圾收集的線程數。
  4)、在Server模式下ParNew收集器是一個非常重要的收集器,因爲除Serial收集器外,目前只有它能與CMS收集器配合工作。

 

36、年輕代常見的垃圾收集器,Parallel Scavenge收集器(-XX:+UseParallelGC,複製算法)。

答:1)、在程序啓動的時候,通過設置UseParallelGC參數使得年輕代使用該垃圾收集器回收。
  2)、系統的吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)。系統的吞吐量等於運行用戶代碼時間除以CPU總消耗時間的比值。
  3)、比起關注用戶線程停頓時間,更關注系統的吞吐量。Parallel Scavenge收集器類似,ParNew收集器,使用多線程進行垃圾回收。停頓時間斷適合用於用戶交互的程序,良好的相應速度,可以提升用戶的體驗,高吞吐量則可以高效率利用CPU時間,儘可能快的完成運算任務,主要適合在後臺運算,而不需要太多交互任務的這種情況。
  4)、在多核下執行纔有優勢,Server模式下默認的年輕代收集器。如果對垃圾收集器運作原理不熟悉,在優化過程中遇到困難了,可以使用-XX:+UseAdaptiveSizePolicy參數,配合自適應調節策略,即在啓動參數中加入這個參數會把內存管理調優任務交給虛擬機去完成。

 

 

37、老年代常見的垃圾收集器,Serial Old(MSC)收集器(-XX:+UseSerialOldGC,標記-整理算法)。

答:1)、在程序啓動的時候,通過設置UseSerialOldGC參數使得年輕代使用該垃圾收集器回收。
  2)、單線程收集,進行垃圾收集的適合,必須暫停所有工作線程。
  3)、簡單高效,Client模式下默認的老年代收集器。

 

38、老年代常見的垃圾收集器,Parallel Old收集器(-XX:+UseParallelOldGC,標記-整理算法)。

答:1)、在程序啓動的時候,通過設置UseParallelOldGC參數使得年輕代使用該垃圾收集器回收。
  2)、多線程,吞吐量優先,在jdk1.6之後纔開始提供的。
  3)、在注重吞吐量和CPU資源敏感的場合,都可以優先考慮Parallel Old收集器 加Parallel Scavenge收集器。

 

39、老年代常見的垃圾收集器,CMS收集器(-XX:+UseConcMarkSweepGC,標記-清除算法)。

答:1)、在程序啓動的時候,通過設置UseConcMarkSweepGC參數使得年輕代使用該垃圾收集器回收。
  2)、CMS收集器佔據了老年代垃圾收集器的半壁江山,劃時代的意義就是幾乎可以做到垃圾回收線程集合可以和用戶線程做到同時工作,幾乎是因爲還不能做到完全不需要停止用戶線程的,只是儘可能的縮短了停頓時間,如果應用程序對停頓比較敏感,並且在應用程序運行的時候,可以提供更大的內存和更多的CPU,也就是更厲害的硬件,使用CMS來收集會帶來好處,如果在JVM中有相對較多存活時間較長的對象,會更適合使用CMS。
  3)、CMS整個垃圾收集器的整個過程可以分爲六步。

    a)、初始化標記,stop-the-world。在這個階段需要虛擬機停頓正在執行的任務,這個過程從垃圾收集器的根對象開始,只掃描到能和根對象關聯的對象並做標記,所以這個過程雖然暫停了整個JVM,但是很快就完成了
    b)、併發標記,併發追溯標記,程序不會停頓。緊隨初始標記階段,在初始標記的基礎上繼續向下追溯標記,併發標記階段,併發標記的線程和用戶執行的線程併發執行,所以程序不會停頓。
    c)、併發預清理,查找執行並併發標記階段從年輕代晉升到老年代的對象。通過重新掃描,減少下個階段重新標記的工作,因爲下個階段會stop-the-world。
    d)、重新標記,暫停虛擬機,掃描CMS堆中的剩餘對象。這個過程從垃圾收集器的根對象開始向下追溯,並處理對象關聯。
    e)、併發清理,清除垃圾對象,程序不會停頓。
    f)、併發重置,重置CMS收集器的數據結構。等待下一次垃圾回收。

  併發標記,也就是和用戶線程同時工作,就是一邊丟垃圾,一邊打掃,這樣就會帶來如果垃圾的產生是在標記後發生的,那麼這次垃圾就只能等待下次再回收了,當然等待垃圾標記了過後呢,垃圾自然不會和用戶線程產生衝突,而清理過程就能和用戶線程同時處理了,對於此類垃圾回收器,有一個比較顯著不可避免的一個問題,就是它所採用的是標記-清除算法,也就是說它不會壓縮存活的對象,這樣就會帶來內存空間碎片化的問題,如果出現需要分配一個連續的較大的n內存空間,則只能觸發一次GC。

 

40、老年代常見的垃圾收集器,G1收集器(-XX:+UseG1GC,複製 + 標記-整理算法)。

答:1)、在程序啓動的時候,通過設置UseG1GC參數使得年輕代使用該垃圾收集器回收。
  2)、即用於年輕代,又用於老年代的收集器。G1收集器的使命是未來替換掉JDK1.5發佈的CMS收集器。
  3)、Garbage First收集器的特點。
    a)、並行和併發,使用多個CPU來縮短stop-the-world的停頓時間,與用戶線程b併發執行。
    b)、分代收集,獨立管理整個堆,能夠採用不同的方式,去處理新創建的對象,和以及存在一段時間熬過多次GC舊對象,以獲得更好的收集效果。
    c)、空間整合,基於標記-整理算法,這樣就解決了內存碎片的問題。
    d)、可預測的停頓,可以建立可以預測的停頓時間模型,能讓使用者明確指定在一個長度爲m毫秒時間片段內,消耗在垃圾收集器上的時間不得超過m毫秒。
  4)、Garbage First收集器之前收集器收集的範圍都是整個年輕代的,或者老年代的,Garbage First收集器Java堆的內存佈局與其他收集器有很大的差別,將整個Java堆內存劃分成多個大小相等的獨立區域Region。
  5)、年輕代和老年代不再是物理隔離了,雖然保留了年輕代和老年代的概念。它們是一部分不再連續的Region的集合,這就意味着在分配空間的時候不需要連續的內存空間,即不需要再JVM啓動的時候決定那些Region是屬於老年代,那些屬於年輕代。隨着時間推移,年輕代Region被回收以後,就會變爲可用狀態,這個時候可以把它分配成老年代,Garbage First年輕代收集器是並行stop-the-world收集器,和其它的GC一樣,當一次年輕代GC發生的時候,整個年輕代會被回收。G1的老年代收集器有所不同,它在老年代不需要整個老年代進行回收,只有一部分Region被調用,Garbage First GC的年輕代Edea Region、Survivor Region組成,JVM分配Edea Region失敗之後就會觸發一個年輕代回收,這意味着Edea 區間滿了,GC開始釋放空間,第一個年輕代收集器會移動所有的存儲對象,從Edea Region到Survivor Region,這就是Copy-on-Survivor的過程。

 

41、Java中的強引用,軟引用,弱引用,虛引用有什麼用?

答:1)、強引用(Strong Reference),最普遍的引用,例如Object obj = new Object();這裏new一個對象實例來,這裏面的obj就是一個強引用。

    a)、如果一個對象具有強引用,當內存空間不足的時候,Java虛擬機寧可拋出OutOfMemoryError終止應用程序,也不會回收具有強引用的對象。
    b)、通過將對象設置爲null來弱化引用,使其被回收。如果我們不使用這個對象了,需要通過將對象的引用設置爲null方法來弱化引用,使其被回收,即將剛纔的obj設置爲null,或者等待它超過對象的生命週期範圍,這個時候GC就認爲該對象不存在引用了,就可以回收這個對象了具體什麼時候收集,取決於系統了。
  2)、軟引用(Soft Reference),表示一個對象處在有用但非必須的狀態。

    a)、只有當內存空間不足的時候,GC會回收該引用的對象的內存。當內存空間充足的時候,GC就不會回收該對象。
    b)、軟引用可以用來實現告訴緩存,可以實現內存敏感的高速緩存。不用太擔心OutOfMemoryError的問題,因爲軟引用的對象內存會在內存不足的時候進行回收,同時由於一般情況下內存空間是充足的,相關對象就一直存在便於複用。軟引用也可以和引用隊列配合使用。

1 例如,String str = new String("abc");// 強引用,創建的對象實例賦值給強引用str
2 SoftReference<String> softReference = new SoftReference<String>(str);// 軟引用,使用SoftReference類型,泛型類型是String的,然後將強引用str包裝起來,此時softReference就是軟引用了。

  3)、弱引用(Weak Reference),用來描述非必須的對象,類似軟引用,強度比軟引用更弱一些。

    a)、弱引用具有更短的生命,GC時會被回收,GC在掃描的過程中,一旦發現有被弱引用關聯的對象,就會將它回收了。換言之,無論此時內存是否緊缺,GC都將回收被弱引用關聯的對象。
    b)、被回收的概率也不大,因爲GC線程優先級比較低。由於垃圾回收是一個優先級很低的線程,因爲不一定回很快發現那些子句有弱引用的對象。
    c)、適用於引用偶爾被使用且不影響垃圾收集的對象。用法和軟引用一樣,弱引用也可以和引用隊列配合使用。

1 例如,String str = new String("abc");// 強引用,創建的對象實例賦值給強引用str
2 WeakReference<String> weakReference = new WeakReference<String>(str);// 軟引用,使用WeakReference類型,泛型類型是String的,然後將強引用str包裝起來,此時weakReference就是弱引用了。

  4)、虛引用(Phantom Reference),顧名思義就是形同虛設,與其他幾種引用不同,虛引用不會決定對象的生命週期。

    a)、任何時候都可能被垃圾收集器回收。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣。
    b)、跟蹤對象被垃圾收集器回收的活動,起哨兵作用。
    c)、虛引用和軟引用和弱引用的一個區別,就是必須和引用隊列ReferenceQueue聯合使用。GC在回收一個對象的時候,如果發現該對象具有虛引用,那麼在回收之前會首先將該對象的虛引用加入到與之關聯的引用隊列當中,程序可以通過判斷引用隊列是否以及加入虛引用來了解被引用的對象是否被GC回收,因此起到一個哨兵的作用。

1 例如,String str = new String("abc");// 強引用,創建的對象實例賦值給強引用str
2 ReferenceQueue queue = new ReferenceQueue();// ReferenceQueue對象
3 PhantomReference weakReference = new PhantomReference(str,queue);// 虛引用

 

42、Java中的強引用,軟引用,弱引用,虛引用的等級。

答:強引用 > 軟引用 > 弱引用 > 虛引用。

引用類型 被垃圾回收時間 用途 生存時間
強引用 從來不會 對象的一般狀態 JVM停止運行時終止
軟引用 在內存不足時 對象緩存 內存不足時終止
弱引用 在垃圾回收時 對象緩存 GC運行後終止
虛引用 Unknown 標記、哨兵 Unknown

 

43、引用隊列(ReferenceQueue)。

  1)、ReferenceQueue名義上是一個隊列,但是無實際存儲結構,存儲邏輯依賴於內部節點之間的關係來表達,Queue類似於一個鏈表的結構,這裏的節點其實就是Reference本身,鏈表的容器,其自己只存儲當前的head節點,而後面的節點由每個Reference節點自己通過next來保存即可。
  2)、存儲關聯的且被GC的軟引用,弱引用以及虛引用,這三個引用都可以保存到引用隊列裏面,如果在創建一個引用對象的時候,指定了ReferenceQueue,那麼當引用對象指向的對象達到合適的狀態的時候,GC會把引用對象本身添加到這個隊列裏面,方便我們處理它。

 

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