java架構之路-(JVM優化與原理)JVM的對象和堆

  上次博客,咱們說了jvm運行時的內存模型,堆,棧,程序計數器,元空間和本地方法棧。咱們主要說了堆和棧,棧的流程大體也說了一遍,同時咱們知道堆是用來存對象的,分別年輕代和老年代。可是具體的堆是怎麼來存放對象的呢?何時能夠將對象放置在老年代呢。下面我來看一下。html

  

 

 

 若是都爲默認設置,大體就是這樣的。假設咱們設置內存堆的大小爲600M,那麼老年代就大概是400M,咱們的年輕代就是200M,而後年輕代的eden區域佔160M也就是200M的8/10,通常新建的對象都在這,我是說通常啊。後面會用這個600M來詳細說明,from和 to區域各佔20M,也就是Survivor區域佔用40M,每次作完minor GC,對象就放在這個區域。java

  我剛纔說到,有時候對象不在年輕代,那麼我來具體分析一下,什麼狀況放置在年輕代,而何時又放置在老年代。算法

1,Minor GC以後,存活的對象Survivor區域放不下。緩存

public class Main { public static void main(String[] args) { byte[] bt1; bt1 = new byte[60000 * 1024]; } }

加入堆內存日誌,咱們獲得打印結果爲:mybatis

   咱們獲得bt1新建之後,咱們的堆內存幾乎佔滿了,如今已經99%了,那麼咱們再來看一下。jvm

public class Main { public static void main(String[] args) { byte[] bt1,bt2; bt1 = new byte[60000 * 1024]; bt2 = new byte[10000 * 1024]; } }

從代碼裏咱們能夠得知,咱們新建了bt1以後又新建了bt2,這時咱們的eden區域應該不夠用了,那麼咱們的內存會怎麼來處理呢。咱們來看一下結果優化

  咱們能夠看到已經作了一次GC了,可是仍是放不下,那麼咱們直接將較大的對象直接放置在了堆內存上。spa

2,長期存活的對象移到老年代。也就是通過屢次minorGC之後,對象仍是存活的,咱們將該對象移置老年代,通常是15次,也就是對象頭內的分代年齡達到15歲時,咱們將該對象移置老年代。線程

3,對象動態年齡判斷。日誌

  這個很重要的一個理論知識,大概來講一下,當咱們作完minorGC之後,對象放在to區域,也就是咱們Survivor的to區域,可能對象是放不下的,這時會來計算分類年齡,大體是這樣來算的將全部分代年齡爲1的相加,再加上分代年齡爲2的,再加分代年齡爲3的,依次相加,一直加到最大的分代年齡,但在相加過程當中,你會發現加到分代年齡爲m的對象,總大小已經放滿了to區域,這時就將m到n分代年齡的對象都移置到老年代,包含m。也就是大於Survivor區域的50%時,則後面的對象,包含該年齡的對象都放置在老年代。

4,大對象直接放在老年代。再來看段代碼。

public class Main { public static void main(String[] args) { byte[] bt1; bt1 = new byte[90000 * 1024]; } }

  上面我知道咱們建立一個大概600M的對象放置在eden時,佔了99%,那麼咱們建立大於600M的對象,eden必定放不下了。那麼直接放置在老年代。這裏參數也是能夠設置的。我來設置一個參數再看看,設置參數爲

-XX:PretenureSizeThreshold=10000000 -XX:+UseSerialGC -XX:+PrintGCDetails   

public class Main { public static void main(String[] args) { byte[] bt1; bt1 = new byte[20000 * 1024]; } }

   咱們設置了參數,聲明10M的對象就爲大對象,咱們建立了一個大概20M的對象,就直接放置在了老年代上。就是對象經歷那麼屢次的minorGC了,jvm虛擬機會認爲你可能會一直存活,趁着此次放不下了,你就趁早過來吧,來咱們老年代混吧。

5,老年代空間分配擔保機制。

  其實咱們每次進行minorGC前,會有一系列操做的,可能會進行full GC的,那麼咱們來看一下流程吧。

 

 我來解釋一下上面那個五彩繽紛的圖。等咱們的eden區滿時,須要進行minorGC,這時會優先看一下老年代的剩餘空間大小,若是老年代剩餘的空間很少了,咱們就可能進行full GC,也就是咱們老年代的剩餘空間小於咱們的eden區內將要進行minorGC對象的總和。

若是真的小了,那麼咱們往下走,咱們會判斷時候配置了-XX:-HandlePromotionFailure (jdk8以上默認設置)這個參數,若是沒配置,直接進行fullGC,若是配置了就去判斷老年代的剩餘空間是否小於咱們每次minorGC後每次要放在老年代對象大小的平均值,若是老年代小於minorGC了,那麼進行fullGC。不然不須要進行full GC。

eden和Survivor(from和to)默認比例是8:1:1,可是jvm可能會將咱們的參數優化,也就是-XX:+UseAdaptiveSizePolicy這個默認參數,我將其改成-XX:-UseAdaptiveSizePolicy不進行優化,保持8:1:1的比例了。

  咱們再來看一下什麼樣的對象是能夠被回收的。

1,引用計數法(基本不用,循環引用對象永遠沒法銷燬,可能內存溢出)

  給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加1;當引用 失效,計數器就減1;任什麼時候候計數器爲0的對象就是不可能再被使用的。 

  GC Roots根節點通常爲線程棧的本地變量、靜態變量、本地方法棧的變量等等。

2,可達性分析算法。

  這個算法的基本思想就是經過一系列的稱爲 「GC Roots」 的對象做爲起點, 從這些節點開始向下搜索,找到的對象都標記爲非垃圾對象,其他未標記的對象都是垃圾對象

3,常見的引用類型。

  java的引用類型通常分爲四種:強引用、軟引用、弱引用、虛引用 

import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; public class Main { public static void main(String[] args) { User user = new User();//強引用
        WeakReference<User> user2 = new WeakReference<User>(new User());//弱引用
        SoftReference<User> user3 = new SoftReference<User>(new User());//軟引用
 } }

  通常將對象用SoftReference軟引用類型的對象包裹,正常狀況不會被回收,可是GC作完後發現釋放不出空間存放新的對象,則會把這些軟引用的對象回收掉。軟引用可用來實現內存敏感的高速緩存。 

4,finalize最終判斷對象存活。

  finalize是在對象立刻要被收回以前運行的最後一個方法,能夠寫邏輯,可是徹底不建議去這樣去寫,極可能出現對象永遠不會被回收,形成內存溢出,也就是說在finalize方法內還可能「救活」咱們的對象。

  即便在可達性分析算法中不可達的對象,也並不是是「非死不可」的,這時候它們暫時處於「緩刑」階段,要真正宣告一個對象死亡,至少要經歷再次標記過程。 

  如何判斷一個類是無用的類

1.該類全部的實例都已經被回收,也就是 Java 堆中不存在該類的任何 實例。

2.加載該類的 ClassLoader 已經被回收。

3.該類對應的 java.lang.Class對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。 

  最後咱們來看一下逃逸分析。

    JVM的運行模式用三種,分別是解釋模式,編譯模式和混合模式,這裏簡單說一下這個問題,否則後面會蒙圈的。

解釋模式就是執行一行JVM字節碼就編譯一行爲機器碼,這樣的好處就是很節省內存空間,不用把全部的字節碼都塞到內存裏面去,運行效率低,可是啓動快。

編譯模式和解釋模式偏偏相反,是先將全部JVM字節碼一次編譯爲機器碼,而後一次性執行全部機器碼。這樣會提升咱們的運行效率,可是消耗空間資源。

混合模式是上面的總和,依然使用解釋模式執行代碼,可是對於一些 "熱點" 代碼採用編譯模式執行,JVM通常採用混合模式執行代碼。

咱們來看一段代碼。

public class Main { public User getUserBeanTest() { User user = new User();//放置在堆上
        return user; } public void userBeanTest() { User user = new User();//優先和方法一塊兒發放置在棧上.
 } }

也就是說明,對象也是有很小的可能放置在棧上的。中秋放假了,明天補一下mybatis的底層是實現原理。過幾天繼續來講咱們的jvm優化

 

最進弄了一個公衆號,小菜技術,歡迎你們的加入

原文出處:https://www.cnblogs.com/cxiaocai/p/11520731.html

相關文章
相關標籤/搜索