2-JVM內存結構

內存結構

方法區

JDK1.7 之前包含1.7 將方法區稱爲 Perm Space 永久代

JDK1.8之後包含1.8 將方法區稱爲 MetaSpace 元空間。

堆(分配內存會大一些)

分配對象、new 實例。

堆內存當中劃分爲兩個區域:老年代和新生代。

如何去劃分老年代和新生代,根據對象的年齡。這個年齡是一個對象經過一次GC,如果還存在的話,年齡就加一。當年齡超過默認值(15)時,就會從新生代劃分到老年代當中。

1.新生代(Young)

  1. new Object() ,實例化10個單位爲1的對象 ===>>> 新生代分配
  2. 新生代內存不夠用時,觸發GC
  3. GC之後,釋放空間,會存在空間碎片
  4. 這時又new一個對象,這時這個對象的單位是3;GC之後釋放的空間不連續,導致新生代不夠分配,又會再一次觸GC
  5. GC的弊端就是會消耗線程資源,stop the world。

以上的設計顯然是不合理的,重新設計之後

新生代new出來的對象是朝生夕死,將新生代劃分爲兩個區域:Eden區,Survivor區。Survivor區又劃分爲S0、S1兩個區域;

新生代內存分配情況是:Eden區80%,Survivor區20%(S0:10%,S1:10%)。

如果剛new出來的對象太大,超過了新生代的Eden區內存,會直接存入在老年代。

舉例說明:

老年代:2G內存

新生代:1G內存

Eden區800MB

S0,S1各100MB

這是new一個900MB的對象,會直接分配在老年代(Old)裏。

新生代(Young)GC:Minor GC

老年代(Old)GC:Major GC

  • Eden區

所有剛剛new出來的對象,就會分配在Eden區。

  • Survivor區
  1. S0、S1永遠有一塊內存是浪費的,一塊被使用;

  2. S0、S1兩個區域互相轉換身份,以空間的浪費換取內存空間的連續性;

  3. Eden=80%;S0=10%;S1=10%;Eden:S0:S1=8:1:1;

  4. 比如說直接new一個900MB的新對象,會直接在老年代(Old)區進行分配;

  5. 如果新生代(Young)區的Young GC之後對象的年齡不斷的+1+1+1 > 年齡15之後,會將該對象存放到老年代(Old)區;

    假如這時新生代(Young)區有120MB存活對象,S區不夠放了,會跟老年代借20MB的空間存放,會觸發擔保機制,這20MB依舊還是屬於老年代(Old)管理的。

  6. 極端情況,如果有個對象超過老年代內存直接OOM。

2.老年代(Old)

如果老年代的內存不夠用了,會觸發 Old GC 也可 稱爲 Major GC。Old GC會比較耗時。當然一旦觸發了Old GC(Major GC)通常都會伴隨着Young GC(Minor GC)

Old GC(Major GC)+ Young GC(Minor GC)+ MetaSpace GC(可以忽略它)= Full GC

調優的原則:

避免觸發Full GC,換句話說避免觸發Old GC(Major GC);如果要觸發GC,儘量只觸發Young GC(Minor GC)。

  1. 儘量減少GC次數
  2. 儘量只觸發Young GC(Minor GC)

實操:

在IDEA中VM options設置JVM堆內存:-Xms30M -Xmx30M(設置堆內存30MB,最大30MB)

@RestController
@RequestMapping("/test/jvm")
public class TestJvmController {
    List<AuthAccount> list = new ArrayList<>();
    @GetMapping("/jvmTest")
    public void jvmTest() {
        while (true) {
            list.add(new AuthAccount());
        }
    }
}

運行SpringBoot程序後、在JAVA安裝目錄中,找到bin文件夾下的jvisualvm工具(這個工具是JDK自帶的),首先還要安裝Visual GC的插件才能查看到JVM GC運行時狀況

之後等待程序運行後,打開這個jvisualvm工具就可以查看到JVM內存運行時的狀況

如果堆內存中,沒有可分配的內存空間了,就會報OOM。

同理方法區Metaspace也會報OOM,設置JVM中方法區大小:-XX:MetaspaceSize=40M -XX:MaxMetaspaceSize=40M。

棧也會報OOM,首先我們先測試棧的深度:

	// 通過遞歸操作
	public static long count = 0;

    public static void test(long i) {
        System.out.println(count++);
        test(i);
    }

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

通過測試我們發現,棧的默認深度是7000左右。之後就會報OOM錯誤。

可以根據需求去調整棧的深度大小;

一個棧的深度大小,太大或太小都會有弊端,太小的話影響方法鏈調用的深度、太大的話在整個JAVA進程當中它能夠創建這樣一個的線程的數量是有限的,如果太大會影響到其他線程創建棧的深度。

通過前人的經驗來看,最佳值設置到5000左右就可以了。可以通過JVM參數去設置。
趙小胖個人博客

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