JVM、垃圾收集器

一、Java虛擬機原理java

      所謂虛擬機,就是一臺虛擬的機器。他是一款軟件,用來執行一系列虛擬計算指令,大致上虛擬機能夠分爲系統虛擬機和程序虛擬機, 大名鼎鼎的Vmare就屬於系統虛擬機,他徹底是對物理計算的仿真,提供了一個能夠運行完整操做系統的軟件平臺。程序虛擬機典型代碼就是Java虛擬機,它專門爲執行單個計算程序而計算,在Java虛擬機中執行的指令咱們稱爲Java字節碼指令。不管是系統虛擬機仍是程序虛擬機,在上面運行的軟件都被限制於虛擬機提供的資源中。到如今引用最普遍的是HotSpot虛擬c++

二、Java內存結構程序員

  1. 類加載子系統:負責從文件系統或者網絡加載Class信息,加載的信息存放在一塊稱之方法區的內存空間。
  2. 方法區:就是存放類的信息、常量信息、常量池信息、包括字符串字面量和數字常量等
  3. Java堆:在Java虛擬機啓動的時候創建Java堆,它是Java程序最主要的內存工做區域,幾乎全部的對象實例都存放到Java堆中,堆空間是全部線程共享。
  4. 直接內存:JavaNio庫容許Java程序直接內存,從而提升性能,一般直接內存速度會優於Java堆。讀寫頻繁的場合可能會考慮使用。
  5. 每一個虛擬機線程都有一個私有棧,一個線程的Java棧在線程建立的時候被建立,Java棧保存着局部變量、方法參數、Java的方法調用、返回值等
  6. 本地方法棧:最大不一樣爲本地方法棧用於本地方法調用。Java虛擬機容許Java直接調用本地方法(經過使用C語言寫)
  7. 垃圾收集系統是Java的核心,也是不可少的,Java有一套本身進行垃圾清理的機制,開發人員無需手工清理
  8. PC(Program Couneter)寄存器:也是每一個線程私有的空間, Java虛擬機會爲每一個線程建立PC寄存器
  9. 虛擬機核心的組件就是執行引擎,它負責執行虛擬機的字節碼,通常先進行編譯成機器碼後執行。

三、Java堆算法

       堆內存用於存放由new建立的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,之後就能夠在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量至關於爲數組或者對象起的一個別名。數組

       根據垃圾回收機制的不一樣,Java堆有可能擁有不一樣的結構,最爲常見的就是將整個Java堆分爲新生代和老年代。其中新生代存放新生的對象或者年齡不大的對象,老年代則存放老年對象。新生代分爲den區、s0區、s1區,s0和s1也被稱爲from和to區域,他們是兩塊大小相等而且能夠互相角色的空間。絕大多數狀況下,對象首先分配在eden區,在新生代回收後,若是對象還存活,則進入s0或s1區,以後每通過一次新生代回收,若是對象存活則它的年齡就加1,對象達到必定的年齡後,則進入老年代。緩存

四、Java棧    服務器

      java虛擬機也是線程私有的,它的生命週期和線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。局部變量表存放了編譯期可知的各類基本數據類型(8個基本數據類型)、對象引用(地址指針)、returnAddress類型。局部變量表所需的內存空間在編譯期間完成分配。在運行期間不會改變局部變量表的大小。網絡

這個區域規定了兩種異常狀態:數據結構

  1. 若是線程請求的棧深度大於虛擬機所容許的深度,則拋出StackOverflowError異常。
  2. 若是虛擬機棧能夠動態擴展,在擴展是沒法申請到足夠的內存,就會拋出OutOfMemoryError異常。

Java棧是一塊線程私有的空間,一個棧通常由三部分組成:局部變量表、操做數據棧和幀數據區多線程

  1. 局部變量表:用於保存函數的參數及局部變量
  2. 操做數棧:主要保存計算過程的中間結果,同時做爲計算過程當中的變量臨時的存儲空間。
  3. 幀數據區:幀數據區保存着訪問常量池的指針

五、本地方法棧(Native Method Stack)

       本地方法棧與虛擬機棧所發揮做用很是類似,它們之間的區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的native方法服務。

六、Java方法區

       Java方法區和堆同樣,方法區是一塊全部線程共享的內存區域,他保存系統的類信息。好比類的字段、方法、常量池、靜態變量、即時編譯器編譯後的代碼等。方法區的大小決定系統能夠保存多少個類。若是系統定義太多的類,致使方法區溢出。虛擬機一樣會拋出內存溢出的錯誤。方法區能夠理解爲永久區,它有個別命叫Non-Heap(非堆)。

七、運行時常量池(Runtime Constant Pool)

     運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯期生成的各類字面量和符號引用,這部份內容將在加載後進入方法區的運行時常量池中存放。

八、直接內存(Direct Memory)

       直接內存不是虛擬機運行時數據區的一部分,也不是java虛擬機規範中定義的內存區域。但這部分區域也被頻繁使用,並且也可能致使OutOfMemoryError異常。在JDK1.4中新加入的NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。

九、程序計數器(Program Counter Register)

       程序計數器是一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器

十、執行引擎

      虛擬機核心的組件就是執行引擎,它負責執行虛擬機的字節碼,通常先進行編譯成機器碼後執行。

十一、什麼是虛擬機參數配置

       在虛擬機運行的過程當中,能夠跟蹤系統的運行狀態,對於問題的故障排查會有必定的幫助,在虛擬機提供了一些跟蹤系統狀態的參數,使用給定的參數執行Java虛擬機,就能夠在系統運行時打印相關日誌,用於分析實際問題。咱們進行虛擬機參數配置,其實就是圍繞着堆、棧、方法區、進行配置。

堆的參數配置:

-XX:+PrintGC             每次觸發GC的時候打印相關日誌

-XX:+UseSerialGC      串行回收

-XX:+UseG1GC         應用G1收集器,最新的收集器

-XX:+PrintGCDetails  更詳細的GC日誌

-Xms                           堆初始值

-Xmx                           堆最大可用值,咱們能夠直接將初始的堆大小與最大堆大小相等,這樣的好處是能夠減小程序運行時垃圾回收次數,從而提升效率

-Xmn                           新生代堆最大可用值,通常設爲整個堆的1/3到1/4左右

-XX:NewRatio             設置新生代和老年代的比例,老年代/新生代

-XX:SurvivorRatio       用來設置新生代中eden空間和from/to空間的比例,SurvivorRatio=eden/from=den/to

-XX:PermSize              JVM初始分配的非堆內存 。JDK8後換成Metaspace(元空間)

-XX:MaxPermSize       JVM最大容許分配的非堆內存。MaxMetaspaceSize(最大元空間)

-Xss                            設置棧內存大小(設置最大調用深度),棧溢出產生於遞歸調用,解決辦法:設置線程最大調用深度

十二、JVM參數調優總結

       在JVM啓動參數中,能夠設置跟內存、垃圾回收相關的一些參數設置,默認狀況不作任何設置JVM會工做的很好,但對一些配置很好的Server和具體的應用必須仔細調優才能得到最佳性能。經過設置咱們但願達到一些目標:

  1. GC的時間足夠的小
  2. GC的次數足夠的少
  3. 發生Full GC(新生代和老年代)的週期足夠的長

前兩個目前是相悖的,要想GC時間小必需要一個更小的堆,要保證GC次數足夠少,必須保證一個更大的堆,咱們只能取其平衡。

  1. 針對JVM堆的設置,通常能夠經過-Xms -Xmx限定其最小、最大值,爲了防止垃圾收集器在最小、最大之間收縮堆而產生額外的時間,咱們一般把最大、最小設置爲相同的值
  2. 年輕代和年老代將根據默認的比例(1:2)分配堆內存,能夠經過調整兩者之間的比率NewRadio來調整兩者之間的大小,也能夠針對年輕代,經過 -XX:newSize -XX:MaxNewSize來設置其絕對大小。一樣爲了防止年輕代的堆收縮,咱們一般會把-XX:newSize -XX:MaxNewSize設置爲一樣大小
  3. 年輕代和年老代設置多大才算合理?這個我問題毫無疑問是沒有答案的,不然也就不會有調優。咱們觀察一下兩者大小變化有哪些影響。更大的年輕代必然致使更小的年老代,大的年輕代會延長普通GC的週期,但會增長每次GC的時間;小的年老代會致使更頻繁的Full GC。更小的年輕代必然致使更大年老代,小的年輕代會致使普通GC很頻繁,但每次的GC時間會更短;大的年老代會減小Full GC的頻率。
  4. 如何選擇應該依賴應用程序對象生命週期的分佈狀況:若是應用存在大量的臨時對象,應該選擇更大的年輕代;若是存在相對較多的持久對象,年老代應該適當增大。但不少應用都沒有這樣明顯的特性,在抉擇時應該根據如下兩點:本着Full GC儘可能少的原則,讓年老代儘可能緩存經常使用對象,JVM的默認比例1:2也是這個道理 。經過觀察應用一段時間,在峯值時年老代會佔多少內存,在不影響Full GC的前提下,根據實際狀況加大年輕代,好比能夠把比例控制在1:1,但應該給年老代至少預留1/3的增加空間

 1三、垃圾回收機制概述

     Java語言中一個顯著的特色就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候再也不須要考慮內存管理。因爲有個垃圾回收機制,Java中的對象再也不有「做用域」的概念,只有對象的引用纔有「做用域」。垃圾回收能夠有效的防止內存泄露,有效的使用空閒的內存。

       內存泄露是指該內存空間使用完畢以後未回收,在不涉及複雜數據結構的通常狀況下,Java 的內存泄露表現爲一個內存對象的生命週期超出了程序須要它的時間長度,咱們有時也將其稱爲「對象遊離」。

1四、垃圾回收簡要過程

      這裏必須點出一個很重要的誤區:不可達的對象並不會立刻就會被直接回收,而是至少要通過兩次標記的過程。

     第一次被標記過的對象,會檢查該對象是否重寫了finalize()方法。若是重寫了該方法,則將其放入一個F-Query隊列中,不然直接將對象加入「即將回收」集合。在第二次標記以前,F-Query隊列中的全部對象會逐個執行finalize()方法,可是不保證該隊列中全部對象的finalize()方法都能被執行,這是由於JVM建立一個低優先級的線程去運行此隊列中的方法,極可能在沒有遍歷完以前,就已經被剝奪了運行的權利。那麼運行finalize()方法的意義何在呢?這是對象避免本身被清理的最後手段,若是在執行finalize()方法的過程當中,使得此對象從新與GC Roots引用鏈相連,則會在第二次標記過程當中將此對象從F-Query隊列中清除,避免在此次回收中被清除,恢復成了一個「正常」的對象。但顯然這種好事不能無限的發生,對於曾經執行過一次finalize()的對象來講,以後若是再被標記,則不會再執行finalize()方法,只能等待被清除的命運。以後GC將對F-Queue中的對象進行第二次小規模的標記,將隊列中從新與GC Roots引用鏈恢復鏈接的對象清除出「即將回收」集合。因此此集合中的內容將被回收。

1五、手動GC回收

package com.zhang.jvm;

public class JVMDemo05 {
    public static void main(String[] args) {
        JVMDemo05 jvmDemo05 = new JVMDemo05();
        jvmDemo05 = null;
        System.gc();
    }
    protected void finalize() throws Throwable {
       System.out.println("gc在回收對象...");
    }
}

1六、finalize做用

        Java技術使用finalize()方法在垃圾收集器將對象從內存中清除出去前,作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時對這個對象調用的。它是在Object類中定義的,所以全部的類都繼承了它。子類覆蓋finalize()方法以整理系統資源或者執行其餘清理工做。finalize()方法是在垃圾收集器刪除對象以前對這個對象調用的。

1七、內存泄露

       對象已經沒有被應用程序使用,可是垃圾回收器沒辦法移除它們,由於還在被引用着。  下面圖中能夠看出,裏面有被引用對象和未被引用對象。未被引用對象會被垃圾回收器回收,而被引用的對象卻不會。未被引用的對象固然是再也不被使用的對象,由於沒有對象再引用它。然而無用對象卻不全是未被引用對象。其中還有被引用的。就是這種情況致使了內存泄漏。

1八、如何防止內存泄露

  1. 特別注意一些像HashMap、ArrayList的集合對象,它們常常會引起內存泄漏。當它們被聲明爲static時,它們的生命週期就會和應用程序同樣長。
  2. 特別注意事件監聽和回調函數。當一個監聽器在使用的時候被註冊,但再也不使用以後卻未被反註冊。

一、靜態集合類引發內存泄露: 
像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,他們所引用的全部的對象Object也不能被釋放,由於他們也將一直被Vector等引用着。

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}//

//...對v的操做
v= null;
 //...與v無關的其餘操做

在這個例子中,循環申請Object 對象,並將所申請的對象放入一個Vector 中,若是僅僅釋放引用自己(o=null),那麼Vector 仍然引用該對象,因此這個對象對GC 來講是不可回收的。所以,若是對象加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector對象設置爲null。

二、當集合裏面的對象屬性被修改後,再調用remove()方法時不起做用。

public static void main(String[] args)
{
Set<Person> set = new HashSet<Person>();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孫悟空","pwd2",26);
Person p3 = new Person("豬八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:3 個元素!
p3.setAge(2); //修改p3的年齡,此時p3元素對應的hashcode值發生改變

set.remove(p3); //此時remove不掉,形成內存泄漏

set.add(p3); //從新添加,竟然添加成功
System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:4 個元素!
for (Person person : set)
{
System.out.println(person);
}
}

三、單例模式

單例對象在被初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),若是單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,致使內存泄露

class A{
public A(){
B.getInstance().setA(this);
}
....
}
//B類採用單例模式
class B{
private A a;//B中持有A對象
private static B instance=new B();
private B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}
//getter...
}

顯然B採用singleton模式,它持有一個A對象的引用,而這個A類的對象將不能被回收

1九、新生代與老年代

       Java 中的堆是 JVM 所管理的最大的一塊內存空間,主要用於存放各類類的實例對象。堆被劃分紅兩個不一樣的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分爲三個區域:Eden、From Survivor、To Survivor。這樣劃分的目的是爲了使 JVM 可以更好的管理堆內存中的對象,包括內存的分配以及回收。默認的新生代 ( Young ) 與老年代 ( Old ) 的比例的值爲 1:2 ( 該值能夠經過參數 –XX:NewRatio 來指定 )其中新生代 ( Young )被細分爲Eden和兩個Survivor區域,這兩個Survivor 區域分別被命名爲 from 和 to。默認的Edem : from : to = 8 : 1 : 1 ( 能夠經過參數 –XX:SurvivorRatio 來設定 )

垃圾回收算法

 

20、根搜索法——判斷對象是否可達(是否能夠被回收)

 

         根搜索算法的基本思路就是經過一系列名爲」GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。在Java語言中,能夠做爲GCRoots的對象包括下面幾種:

 

  1. 虛擬機棧中引用的對象。
  2. 方法區中的類靜態屬性引用的對象。
  3. 方法區中常量引用的對象。
  4. 本地方法棧中JNI(Native方法)引用的對象。

 

 

 

2一、引用計數法

       給對象中添加一個引用計數器,每當有一個地方引用它時計數器值就加1,當引用失效時計數器值就減1;任什麼時候刻計數器都爲0的對象就是再也不被使用的,垃圾收集器將回收該對象使用的內存。

優勢:引用計數收集器能夠很快的執行,交織在程序運行中。對程序須要不被長時間打斷的實時環境比較有利。

缺點:

  1. 沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣他們的引用計數永遠不可能爲0.並且每次加減很是浪費內存。
  2. 效率問題,標記和清除兩個過程的效率都不高。
  3. 空間問題,標記清除後會產生大量的不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中須要分配較大對象時,沒法找到足夠的連續空間而不得不提早觸發另外一次垃圾收集活動。
執行過程以下圖:

 

2二、複製算法

       S0和s1將可用內存按容量分紅大小相等的兩塊,每次只使用其中一塊,當這塊內存使用完了,就將還存活的對象複製到另外一塊內存上去,而後把使用過的內存空間一次清理掉。這樣使得每次都是對其中一塊內存進行回收,內存分配時不用考慮內存碎片等複雜狀況,只須要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。複製算法的缺點顯而易見,可以使用的內存降爲原來一半。複製算法用於在新生代垃圾回收

2三、標記-清除算法

        標記-清除(Mark-Sweep)算法顧名思義,主要就是兩個動做,一個是標記,另外一個就是清除。標記就是根據特定的算法(如:引用計數算法,可達性分析算法等)標出內存中哪些對象能夠回收,哪些對象還要繼續用。標記指示回收,那就直接收掉;標記指示對象還能用,那就原地不動留下。缺點是 標記與清除沒有連續性效率低,清除以後內存會產生大量碎片;

2四、標記-壓縮算法

        標記壓縮法在標記清除基礎之上作了優化,把存活的對象壓縮到內存一端,然後進行垃圾清理。老年代使用的就是標記壓縮法

2五、Minor GC和Full GC區別

      新生代GC(Minor GC):指發生在新生代的垃圾收集動做,由於 Java 對象大多都具備朝生夕滅的特性,因此Minor GC 很是頻繁,通常回收速度也比較快。當年輕代滿時就會觸發Minor GC,這裏的年輕代滿指的是Eden代滿,Survivor滿不會引起GCFull GC觸發機制。

       老年代GC(Major GC  / Full GC):指發生在老年代的GC,出現了Major GC常常會伴隨至少一次的 Minor GC。MajorGC 的速度通常會比 Minor GC 慢 10倍以上。當年老代滿時會引起Full GC,Full GC將會同時回收年輕代、老年代,當永久代滿時也會引起Full GC,會致使Class、Method元信息的卸載。

       虛擬機給每一個對象定義了一個對象年齡(Age)計數器。若是對象在 Eden 出生並通過第一次 Minor GC 後仍然存活,而且能被 Survivor 容納的話,將被移動到 Survivor 空間中,並將對象年齡設爲 1。對象在 Survivor 區中每熬過一次 Minor GC,年齡就增長 1 歲,當它的年齡增長到必定程度(默認爲 15 歲)時,就會被晉升到老年代中。對象晉升老年代的年齡閾值,能夠經過參數 -XX:MaxTenuringThreshold (閾值)來設置

2六、JVM的永久代中會發生垃圾回收麼

     垃圾回收會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。

2七、元空間

       元空間的本質和永久代相似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。所以默認狀況下,元空間的大小僅受本地內存限制,但能夠經過如下參數來指定元空間的大小。

  1. -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整,若是釋放了大量的空間,就適當下降該值;若是釋放了不多的空間,那麼在不超過MaxMetaspaceSize時,適當提升該值。 
  2. -XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。

除了上面兩個指定大小的選項之外,還有兩個與 GC 相關的屬性: 

  1. -XX:MinMetaspaceFreeRatio,在GC以後,最小的Metaspace剩餘空間容量的百分比,減小爲分配空間所致使的垃圾收集 
  2. -XX:MaxMetaspaceFreeRatio,在GC以後,最大的Metaspace剩餘空間容量的百分比,減小爲釋放空間所致使的垃圾收集 

2八、分代算法 

       這種算法根據對象的存活週期的不一樣將內存劃分紅幾塊,新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。新建立的對象被分配在新生代,若是對象通過幾回回收後仍然存活,那麼就把這個對象劃分到老年代。在新生代,每次垃圾收集器都發現有大批對象死去,只有少許存活,採用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須「標記-清除-壓縮」算法進行回收。

2九、爲何老年代使用標記壓縮、新生代使用複製算法

       當前商業虛擬機的垃圾收集都採用「分代收集」(Generational Collection)算法,這種算法只是根據對象存活週期的不一樣將內存劃分爲幾塊。通常是把java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適合的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。而老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記--清理」或者「標記--整理」算法來進行回收。

30、垃圾回收時的停頓現象

       垃圾回收的任務是識別和回收垃圾對象進行內存清理,爲了讓垃圾回收器能夠更高效的執行,大部分狀況下,會要求系統進行一個停頓的狀態。停頓的目的是爲了終止全部的應用線程,只有這樣的系統纔不會有新垃圾的產生。同時停頓保證了系統狀態在某一個瞬間的一致性,也有利於更好的標記垃圾對象。所以在垃圾回收時,都會產生應用程序的停頓

3一、什麼是Java垃圾回收器

        若是說收集算法是內存回收的方法論,那麼垃圾收集器則是內存回收的具體實現,Java垃圾回收器是Java虛擬機(JVM)的三個重要模塊(另外兩個是解釋器多線程機制)之一,爲應用程序提供內存的自動分配(Memory Allocation)、自動回收(Garbage Collect)功能,這兩個操做都發生在Java堆上(一段內存快)。某一個時刻一個對象若是有一個以上的引用(Rreference)指向它,那麼該對象就爲活着的(Live)不然死亡(Dead)視爲垃圾,可被垃圾回收器回收再利用。垃圾回收操做須要消耗CPU、線程、時間等資源,因此容易理解的是垃圾回收操做不是實時的發生(對象死亡立刻釋放),當內存消耗完或者是達到某一個指標(Threshold,使用內存佔總內存的比列,好比0.75)時,觸發垃圾回收操做。有一個對象死亡的例外,java.lang.Thread類型的對象即便沒有引用,只要線程還在運行,就不會被回收。

3二、Java堆溢出

錯誤緣由: java.lang.OutOfMemoryError: Java heap space 堆內存溢出

解決辦法:設置堆內存大小 // -Xms1m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

package com.zhang.test;

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

public class Test {
    public static void main(String[] args) {
        List<Object> listObject = new ArrayList<Object>();
        for (int i = 0; i < 10; i++) {
            System.out.println("i:" + i);
            Byte[] bytes = new Byte[100 * 1024 * 1024];//每次分配100M
            listObject.add(bytes);
        }
        System.out.println("添加成功...");

    }
}

3三、虛擬機棧溢出

錯誤緣由: java.lang.StackOverflowError 棧內存溢出

棧溢出產生於遞歸調用,循環遍歷是不會的

解決辦法:設置線程最大調用深度-Xss5m 設置最大調用深度

package com.zhang.test;

public class JvmDemo04 {
    private static int count;

    public static void count() {
        try {
            count++;
            count();
        } catch (Throwable e) {
            System.out.println("最大深度:" + count);
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        count();
    }
}

3四、串行回收器(Serial Collector)

       單線程執行回收操做,回收期間暫停全部應用線程的執行,client模式下的默認回收器,經過-XX:+UseSerialGC命令行可選項強制指定。

新生代的回收算法(Minor Collection):複製算法

老年代的回收算法(Full Collection):標記-壓縮算法

3五、並行回收器(ParNew回收器)

       並行回收器在串行回收器基礎上作了改進,他可使用多個線程同時進行垃圾回收,對於計算能力強的計算機而言,能夠有效的縮短垃圾回收的時間。ParNew回收器是一個工做在新生代的垃圾收集器,回收策略和算法和串行回收器同樣。ParNew回收器工做時的線程數量可使用XX:ParaleiGCThreads參數指定,通常最好和計算機的CPU至關,避免過多的線程影響性能。新生代並行,老年代串行;新生代複製算法、老年代標記-壓縮

 

a:並行(Parallel):指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態。

b:併發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不必定是並行的,可能會交替執行),用戶程序在繼續運行,而垃圾收集程序運行在另外一個CPU上。

併發是一我的同時吃三個饅頭,而並行是三我的同時吃三個饅頭。 

3六、並行回收集器(ParallelGC)

 

 

       老年代ParallelOldGC回收器也是一種多線程的回收器和新生代的ParallelGC回收器同樣,也是一種關注吞吐量的回收器,他使用了標記-壓縮算法進行實現,-XX:+UseParallelOldGC 進行設置 XX:+ParallelCThread也能夠設置垃圾收集時的線程教量。

-XX:+UseParallelGC:選擇垃圾收集器爲並行收集器。此配置僅對年輕代有效。能夠同時並行多個垃圾收集線程,但此時用戶線程必須中止。

-XX:+UseParNewGC:設置年輕代爲多線程收集。可與CMS收集同時使用。在serial基礎上實現的多線程收集器。

3七、CMS收集器(Concurrent Mark Sweep)

      是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的java應用集中在互聯網站或者B/S系統的服務端上,這類應用尤爲重視服務的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就很是符合這類應用的需求。

      CMS收集器是基於「標記--清除」算法實現的, 他的運做過程相對於前幾種收集器來講更復雜一些,整個過程分爲4個步驟:

  1. 初始標記(CMS inital mark):須要「stop the world」,但只標記一下GC Roots能直接關聯的對象,速度很快。
  2. 併發標記(CMS concurrent mark):是GC Roots Tracing的過程,花費時間長
  3. 從新標記(CMS remark):是爲了修正併發標記期間因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,這個階段時間通常會比初始標記階段稍長一些,但遠比並發標記的時間短。
  4. 併發清除(CMS concurrent sweep):是併發清除無用對象。

3八、G1回收器

        G1回收器(Garbage-First)是在]dk1.7中提出的垃圾回收器,從長期目標來看是爲了取代CMS回收器,G1回收器擁有獨特的垃圾回收策略,G1屬於分代垃圾回收器,區分新生代和老年代,依然有eden和from/to區,它並不要求整個eden區或者新生代、老年代的空間都連續,它使用了分區算法。

        G1收集器(Garbage-First):是當今收集器技術發展的最前沿的成果之一,G1是一款面向服務器端應用的垃圾收集器。與其餘GC收集器相比,G1具有以下特色:

  1. 並行與併發:G1能充分利用多CPU、多核環境下的硬件優點,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓的時間,部分其餘收集器本來須要停頓java線程執行的GC動做,G1收集器仍然能夠經過併發的方式讓java程序繼續執行。
  2. 分代收集:與其餘收集器同樣,分代概念在G1中依然得以保留。雖然G1能夠不須要其餘收集器配合就可以獨立管理整個GC堆,但它可以採用不一樣的方式去處理新建立的對象和已經存活了一段時間、熬過屢次GC的舊對象以獲取更好的收集效果。
  3. 空間整合:與CMS的「標記--清理」算法不一樣,G1從總體來看是基於「標記--整理」算法實現的收集器,從局部(兩個Region之間)上來看是基於「複製」算法實現的,但不管如何,這兩種算法都意味着G1運行期間不會產生內存空間碎片,收集後能提供規整的可用內存。這個特性有利於程序長時間運行,分配大對象時不會由於沒法找到連續內存空間而提早出發下一次GC。
  4. 可預測的停頓:這是G1相對於CMS的另外一大優點,下降停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能創建可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已是實時java(RTSJ)的垃圾收集器的特性了。

       使用G1收集器時,java堆的內存佈局就與其餘收集器有很大差異,它將整個java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留新生代與老年代的概念,但新生代與老年代再也不試物理隔離的了,他們都是一部分Region(不須要連續)的集合。若是不計算維護Remembered Set的操做,G1收集器的運做大體可劃分爲一下幾個步驟:

  1. 初始標記(Initial Marking)
  2. 併發標記(Concurrent Marking)
  3. 最終標記(Final Marking)
  4. 篩選回收(Live Data Counting and Evacuation)

3九、GC日誌

   經過如下兩段典型的GC日誌進行分析:

33.125:[GC [DefNew:3324K->152K(3712K),0.0025925secs] 3324K->152K(11904K),0.0031680 secs] 
100.667:[Full GC [Tenured:0K->210K(10240K),0.0149142secs] 4603K->210K(19456K),[Perm:2999K->2999K(21248K)],0.0150007 secs] <br>[Times:user=0.01 sys=0.00,real=0.02 secs]
  1. 最前面的數字「33.125:」和「100.667:」表明了GC發生的時間,這個數字的含義是從Java虛擬機啓動以來通過的秒數。

  2. GC日誌開頭的「[GC」和「[Full GC」說明了此次垃圾收集的停頓類型,而不是用來區分新生代GC仍是老年代GC的。若是有「Full」,說明此次GC是發生了Stop-The-World的,例以下面這段新生代收集器ParNew的日誌也會出現「[Full GC」(這通常是由於出現了分配擔保失敗之類的問題,因此才致使STW)。若是是調用System.gc()方法所觸發的收集,那麼在這裏將顯示「[Full GC(System)」。

    [Full GC 283.736:[ParNew:261599K->261599K(261952K),0.0000288 secs] 
  3. 接下來的「[DefNew」、「[Tenured」、「[Perm」表示GC發生的區域,這裏顯示的區域名稱與使用的GC收集是密切相關的,例如上面樣例所使用的Serial收集器中的新生代名爲「Default New Generation」,因此顯示的是「[DefNew」。若是是ParNew收集器,新生代名稱就會變爲「[ParNew」,意爲「Parallel New Generation」。若是採用Parallel Scavenge收集器,那它配套的新生代稱爲「PSYoungGen」,老年代和永久代同理,名稱也是由收集器決定的。GC發生區域日誌與GC收集器對照列表以下:

GC日誌區域名

對應GC收集器名

[DefNew (Default New Generation)

Serial收集器

[ParNew (Parallel New Generation)

ParNew收集器

[PSYoungGen

Parallel Scavenge收集器

[ParOldGen

Parallel Old收集器

        後面方括號內部的「3324K->152K(3712K)」含義是「GC前該內存區域已使用容量->GC後該內存區域已使用容量(該內存區域總容量)」。 而在方括號以外的「3324K->152K(11904K)」表示「GC前Java堆已使用容量->GC後Java堆已使用容量(Java堆總容量)"。再日後,「0.0025925 secs」表示該內存區域GC所佔用的時間,單位是秒。有的收集器會給出更具體的時間數據,如「[Times:user=0.01 sys=0.00,real=0.02 secs]」,這裏面的user、sys和real與Linux的time命令所輸出的時間含義一致,分別表明用戶態消耗的CPU時間、內核態消耗的CPU事件和操做從開始到結束所通過的牆鍾時間(Wall Clock Time)。

       CPU時間與牆鍾時間的區別是,牆鍾時間包括各類非運算的等待耗時,例如等待磁盤I/O、等待線程阻塞,而CPU時間不包括這些耗時,但當系統有多CPU或者多核的話,多線程操做會疊加這些CPU時間,因此讀者看到user或sys時間超過real時間是徹底正常的。

40、GC收集器參數

一、 Serial串行收集器相關的參數

-XX:+UseSerialGC 虛擬機運行在Client模式下的默認值,打開此開關後,使用Serial + Serial Old的收集器組合進行內存回收
-XX:SurvivorRatio 新生代中設置eden區大小和survivior區大小的比例。默認爲8,表明Eden:Survivor =8:1
-XX:PretenureSizeThreshold 設置大對象直接進入老年代的閾值。當對象的大小超過這個值時,將直接在老年代分配。
-XX:MaxTenuringThreshold 晉升到老年代的對象年齡。每一個對象在堅持過一次Minor GC以後,年齡增長1,當超過這個參數值時就進入老年代

注意:當GC發生在新生代時,稱爲Minor GC次收集;當GC發生在年老代時,稱爲Major GC主收集。 通常的Minor GC的發生頻率要比Major GC高不少。

二、 ParNew並行收集器相關的參數

-XX:+UseParNewGC 打開此開關後,使用ParNew + Serial Old的收集器組合進行內存回收

-XX:+UseParallelGC

虛擬機運行在Server模式下的默認值,打開此開關後,使用Parallel Scavenge + Serial Old(PS Mark Sweep)的收集器組合進行內存回收
-XX:+UseParallelOldGC 打開此開關後,使用Parallel Scavenge + Parallel Old的收集器組合進行內存回收
-XX:ParallelGCThreads 設置用於垃圾回收的線程數。一般狀況下能夠和CPU數量相等,但在CPU數量比較多的狀況下,設置相·對較小的數值也是合理的。
-XX:MaxGCPauseMills 設置最大垃圾收集停頓時間。它的值是一個大於0的正數。收集器在工做時,會調整java堆大小或者其餘一些參數,儘量地把停頓時間控制在MaxGCPauseMills之內。 僅在Parallel Scavenge收集器時生效
-XX:GCTimeRatio 設置吞吐量大小,即GC時間佔總時間的比率。它的值是一個0到100之間的整數。假設GCTimeRatio的值爲n,那麼系統將花費不超過1/(1+n)的時間用於垃圾收集。默認值爲99,即容許1%的GC時間,僅在Parallel Scavenge收集器時生效
-XX:+UseAdaptiveSizePolicy 打開自適應GC策略。在這種模式下,新生代的大小、eden和survivior的比例、晉升老年代的對象年齡等參數會被自動調整,以達到在堆大小、吞吐量和停頓時間之間的平衡點。
-XX:HandlePromotionFailure 是否容許分配擔保失敗,即老年代的剩餘空間不足以應付新生代的整個Eden和Survivor區的全部對象都存活的極端狀況

三、 CMS 收集器相關的參數

-XX:+UseConcMarkSweepGC 打開此開關後,使用ParNew + CMS —— Serial Old的收集器組合進行內存回收。Serial Old收集器是CMS收集器出現Concurrent Mode Failure失敗後的備用收集器
-XX:ParallelCMSThreads 設定CMS的線程數量。
-XX:CMSInitiatingOccupancyFraction 設置CMS收集器在老年代空間被使用多少後觸發,默認爲68%。僅在CMS收集器時生效
-XX:+UseCMSCompactAtFullCollection 設置CMS收集器在完成垃圾收集後是否要進行一次內存碎片整理。 僅在CMS收集器時生效
-XX:CMSFullGCsBeforeCompaction 設定進行多少次CMS垃圾回收後,進行一次內存壓縮(碎片整理)。 僅在CMS收集器時生效
-XX:+CMSClassUnloadingEnabled 容許對類元數據區進行回收。
-XX:CMSInitiatingPermOccupancyFraction 當永久區佔用率達到這一百分比時,啓動CMS回收(前提是-XX:+CMSClassUnloadingEnabled激活了)。
-XX:CMSInitiatingPermOccupancyOnly 表示只在到達閾值的時候才進行CMS回收。
-XX:+CMSIncrementalMode 使用增量模式,比較適合單CPU。增量模式在JDK8中標記爲廢棄,而且將在JDK9中完全移除。

四、 G1收集器相關的參數

-XX:+UseG1GC

使用G1回收器。

-XX:MaxGCPauseMillis

設置最大垃圾收集停頓時間。

-XX:GCPauseIntervalMillis

設置停頓間隔時間。

https://blog.csdn.net/yjl33/article/details/78890363

相關文章
相關標籤/搜索