面試官問JVM

2020-08-11 17:55:00

請你談談你對JVM的理解?
JVM類載入器是怎麼樣的?有幾種?
什麼是OOM,什麼是StackOverFlowError? 怎麼分析?
JVM常用調優參數有哪寫?
GC有幾種演算法?分別是怎麼執行的?
你知道JProfiler嗎,怎麼分析Dump檔案?
第一次看到這些真真實實的面試題的時候,我~

image-20200802133524318
這都什麼玩意???????

經過一段時間的研究!!接下來,我將以大白話從頭到尾給大家講講Java虛擬機器!!

不對的地方還請大家指正~

目錄
1、什麼是JVM?在哪?
2、JVM、JRE、JDK 的關係
3、JVM體系結構
4、三種JVM(瞭解)
5、類載入器
1、回顧new物件的過程
2、類載入器的類別
6、雙親委派機制 機製
1、什麼是雙親委派機制 機製
2、作用
7、沙箱安全機制 機製
什麼是沙箱?
java中的安全模型演進
組成沙箱的基本元件

  1. 位元組碼校驗器(bytecode verifier)
  2. 類裝載器(class loader)
    8、Native本地方法介面
    9、PC暫存器
    10、方法區
  3. 方法區中有啥?
  4. 建立物件記憶體分析
    11、棧
    1、棧中存放啥?
    2、棧執行原理
    3、堆疊溢位StackOverflowError
    12、堆
    1、堆中有啥?
    2、堆記憶體詳解
    1、Young 年輕代
    2、Tenured 老年代
    3、Perm 元空間
    3、什麼是OOM?
    4、GC垃圾回收
    14、堆記憶體調優
    1、檢視並設定JVM堆記憶體
    2、怎麼排除OOM錯誤?
  5. 嘗試擴大堆記憶體看結果
  6. 利用記憶體快照工具JProfiler
  7. 什麼是Dump檔案?如何分析?
    附:安裝Jprofiler教學
    15、GC垃圾回收
    1、回顧
    2、GC演算法
    1、參照計數演算法(很少使用)
    2、複製演算法
    3、標記–清除演算法
    4、標記–整理演算法
    總結
    思考:有沒有最優的演算法?

1、什麼是JVM?在哪?
JVM是Java Virtual Machine(Java虛擬機器)的縮寫,JVM是一種用於計算裝置的規範,它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。

百度的解釋雲裡霧裏,對於我們Java程式設計師,說白了就是:

JVM本質上是一個程式,它能識別.class 位元組碼檔案(裏面存放的是我們對.java編譯後產生的二進制程式碼),並且能夠解析它的指令,最終呼叫操作系統上的函數,完成我們想要的操作!
關於Java語言的跨平臺性,就是因爲JVM,我們可以將其想象爲一個抽象層,只要這個抽象層JVM正確執行了.class檔案,就能執行在各種操作系統之上了!這就是一次編譯,多次執行
對於JVM的位置:

JVM是執行在操作系統之上的,它與硬體沒有直接的互動
image-20200802133302669
2、JVM、JRE、JDK 的關係
JDK(Java Development Kit):Java開發工具包

JRE(Java Runtime Environment):Java執行環境

JDK = JRE + javac/java/jar 等指令工具

JRE = JVM + Java基本類庫

image-20200802140843921
3、JVM體系結構
Java虛擬機器主要分爲五大模組:

類裝載器子系統
執行時數據區
執行引擎
本地方法介面
垃圾收集模組
image-20200802154319450

方法區是一種特殊的堆
棧裏面不會有垃圾,用完就彈出了,否則阻塞了main方法
垃圾幾乎都在堆裡,所以JVM效能調優%99都針對於堆

4、三種JVM(瞭解)
Sun公司 HotSpot(我們都用的這個)

BEA公司 JRockit
IBM公司 J9 VM

5、類載入器
作用:載入.Class位元組碼檔案

1、回顧new物件的過程
public class Student {
//私有屬性
private String name;

//構造方法
public Student(String name) {
    this.name = name;
}

}
1
2
3
4
5
6
7
8
9
類是模板、模板是抽象的;物件是具體的,是對抽象的範例化
//執行時,JVM將Test的資訊放入方法區
public class Test{
//main方法本身放入方法區
public static void main(String[] args){
//s1、s2、s3爲不同對象
Student s1 = new Student(「zsr」); //參照放在棧裡,具體的範例放在堆裡
Student s2 = new Student(「gcc」);
Student s3 = new Student(「BareTH」);
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
//class1、class2、class3爲同一個物件
Class<? extends Student> class1 = s1.getClass();
Class<? extends Student> class2 = s2.getClass();
Class<? extends Student> class3 = s3.getClass();
System.out.println(class1.hashCode());
System.out.println(class2.hashCode());
System.out.println(class3.hashCode());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
根據結果,我們發現:

s1、s2、s3的hashcode是不同的,因爲是三個不同的物件,物件是具體的
class1、class2、class3的hashcode是相同的,因爲這是類別範本,模板是抽象的
我們畫圖分析以下new一個物件的流程:

首先Class Loader讀取位元組碼.class檔案,載入初始化生成Student模板類
通過Student模板類new出三個物件
image-20200801191801450

那麼Class Loader具體是怎麼執行我們的.class位元組碼檔案呢,這就引出了我們類載入器~

2、類載入器的類別
我們編寫這樣一個程式
image-20200801201223277
根據返回結果,我們來講解以下三種載入器:

級別從高到底

啓動類(根)載入器:BootstrapClassLoader

c++編寫,載入java核心庫 java.*,構造拓展類載入器和應用程式載入器。

根載入器載入拓展類載入器,並且將拓展類載入器的父載入器設定爲根載入器,

然後再載入應用程式載入器,應將應用程式載入器的父載入器設定爲拓展類載入器

由於引導類載入器涉及到虛擬機器本地實現細節,我們無法直接獲取到啓動類載入器的參照;這就是上面那個程式我們第三個結果爲null的原因。

載入檔案存在位置

image-20200801201827272
拓展類載入器:PlatformClassLoader

java編寫,載入擴充套件庫,開發者可以直接使用標準擴充套件類載入器。
java9之前爲ExtClassloader,Java9以後改名爲PlatformClassLoader
載入檔案存在位置
image-20200801201942721
應用程式載入器:AppClassLoader

java編寫,載入程式所在的目錄
是Java預設的類載入器
使用者自定義類載入器:CustomClassLoader

java編寫,使用者自定義的類載入器,可載入指定路徑的class檔案

6、雙親委派機制 機製
1、什麼是雙親委派機制 機製
類載入器收到類載入的請求
將這個請求向上委託給父類別載入器去完成,一直向上委託,直到根載入器BootstrapClassLoader
根載入器檢查是否能夠載入當前類,能載入就結束,使用當前的載入器;否則就拋出異常,通知子載入器進行載入;自載入器重複該步驟。
2、作用
舉個例子:我們重寫以下java.lang包下的String類
image-20200802150012759
發現報錯了,這就是雙親委派機制 機製起的作用,當類載入器委託到根載入器的時候,String類已經被根載入器載入過一遍了,所以不會再載入,從一定程度上防止了危險程式碼的植入!!

作用總結:

防止重複載入同一個.class。通過不斷委託父載入器直到根載入器,如果父載入器載入過了,就不用再載入一遍。保證數據安全。
保證系統核心.class,如上述的String類不能被篡改。通過委託方式,不會去篡改核心.class,即使篡改也不會去載入,即使載入也不會是同一個.class物件了。不同的載入器載入同一個.class也不是同一個class物件。這樣保證了class執行安全。

7、沙箱安全機制 機製
這裏參照了這篇博文參照鏈接,瞭解即可

什麼是沙箱?
Java安全模型的核心就是Java沙箱(sandbox)

沙箱是一個限製程式執行的環境。沙箱機制 機製就是將 Java 程式碼限定在虛擬機器(JVM)特定的執行範圍中,並且嚴格限制程式碼對本地系統資源存取,通過這樣的措施來保證對程式碼的有效隔離,防止對本地系統造成破壞。
沙箱主要限制系統資源存取,系統資源包括CPU、記憶體、檔案系統、網路。不同級別的沙箱對這些資源存取的限制也可以不一樣。
所有的Java程式執行都可以指定沙箱,可以定製安全策略。

java中的安全模型演進
​ 在Java中將執行程式分成原生代碼和遠端程式碼兩種

原生代碼可信任,可以存取一切本地資源。
遠端程式碼不可信信在早期的Java實現中,安全依賴於沙箱 (Sandbox) 機制 機製。
如下圖所示

img

如此嚴格的安全機制 機製也給程式的功能擴充套件帶來障礙,比如當使用者希望遠端程式碼存取本地系統的檔案時候,就無法實現。

因此在後續的 Java1.1 版本中,針對安全機制 機製做了改進,增加了安全策略,允許使用者指定程式碼對本地資源的存取許可權。

如下圖所示

在Java1.2版本中,再次改進了安全機制 機製,增加了程式碼簽名。

不論原生代碼或是遠端程式碼,都會按照使用者的安全策略設定,由類載入器載入到虛擬機器中許可權不同的執行空間,來實現差異化的程式碼執行許可權控制。
如下圖所示

JDK1.2安全模型

當前最新的安全機制 機製實現,則引入了域 (Domain) 的概念。

虛擬機器會把所有程式碼載入到不同的系統域和應用域
系統域部分專門負責與關鍵資源進行互動
應用域部分則通過系統域的部分代理來對各種需要的資源進行存取。
虛擬機器中不同的受保護域 (Protected Domain),對應不一樣的許可權 (Permission)。存在於不同域中的類檔案就具有了當前域的全部許可權,如下圖所示
最新的安全模型

組成沙箱的基本元件

  1. 位元組碼校驗器(bytecode verifier)
    ​ 確保Java類檔案遵循Java語言規範。這樣可以幫助Java程式實現記憶體保護。但並不是所有的類檔案都會經過位元組碼校驗,比如核心類(如上述java.lang.String)。

  2. 類裝載器(class loader)
    其中類裝載器在3個方面對Java沙箱起作用

它防止惡意代碼去幹涉善意的程式碼;
它守護了被信任的類庫邊界;
它將程式碼歸入保護域,確定了程式碼可以進行哪些操作。
虛擬機器爲不同的類載入器載入的類提供不同的名稱空間,名稱空間由一系列唯一的名稱組成,每一個被裝載的類將有一個名字,這個名稱空間是由Java虛擬機器爲每一個類裝載器維護的,它們互相之間甚至不可見。

類裝載器採用的機制 機製是雙親委派模式。

從最內層JVM自帶類載入器開始載入,外層惡意同名類得不到載入從而無法使用;
由於嚴格通過包來區分了存取域,外層惡意的類通過內建程式碼也無法獲得許可權存取到內層類,破壞程式碼就自然無法生效。
存取控制器(access controller):存取控制器可以控制核心API對操作系統的存取許可權,而這個控制的策略設定,可以由使用者指定。
安全管理器(security manager):是核心API和操作系統之間的主要介面。實現許可權控制,比存取控制器優先順序高。
安全軟體包(security package):java.security下的類和擴充套件包下的類,允許使用者爲自己的應用增加新的安全特性,包括:
安全提供者
訊息摘要
數位簽名
加密
鑑別

8、Native本地方法介面
JNI:Java Native Interface

本地介面的作用是融合不同的程式語言爲Java所用,它的初衷是融合C/C++程式
image-20200801214515097

native:凡是帶native關鍵字的,說明java的作用範圍達不到了,會去呼叫底層c語言的庫!進入本地方法棧,呼叫本地方法介面JNI,拓展Java的使用,融合不同的語言爲Java所用

Java誕生的時候C、C++橫行,爲了立足,必須要能呼叫C、C++的程式
於是在記憶體區域中專門開闢了一塊標記區域:Native Method Stack,登記Native方法
最終在執行引擎執行的的時候通過JNI(本地方法介面)載入本地方法庫的方法
目前該方法使用的越來越少了,除非是與硬體有關的應用,比如通過Java程式驅動印表機或者Java系統管理生產裝置,在企業級應用中已經比較少見。因爲現在的異構領域間通訊很發達,比如可以使用 Socket通訊,也可以使用 Web service等等,瞭解即可!

9、PC暫存器
程式計數器: Program Counter Register

每個執行緒都有一個程式計數器,是執行緒私有的,就是一個指針,指向方法區中的方法位元組碼(用來儲存指向像一條指令的地址,也即將要執行的指令程式碼),在執行引擎讀取下一條指令,是一個非常小的記憶體空間,幾乎可以忽略不計

10、方法區
方法區:Method Area

方法區是被所有執行緒共用,所有欄位和方法位元組碼,以及一些特殊方法,如建構函式,介面程式碼也在此定義,簡單說,所有定義的方法的資訊都儲存在該區域,此區域屬於共用區間;
方法區與Java堆一樣,是各個執行緒共用的記憶體區域,用於儲存已被虛擬機器載入的類資訊、常數、靜態變數、即時編譯器編譯後的程式碼等數據。雖然Java 虛擬機器規範把方法區描述爲堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java 堆區分開來。

  1. 方法區中有啥?
    靜態變數(static)
    常數(final)
    類資訊(構造方法、介面定義)
    執行時的常數池
  2. 建立物件記憶體分析
    image-20200802091737843 image-20200802092913288
    建立一個物件時,方法區中會生成對應類的抽象模板;還有對應的常數池、靜態變數、類資訊、常數
    我們通過類別範本去new物件的時候
    堆中存放範例物件
    棧中存放物件的參照,每個物件對應一個地址指向堆中相同地址的範例物件
    例如這個例子中,生成了對應的Person模板類,name常數「zsr」放在常數池中,三個物件的參照放在棧中,該參照指向放在堆中的三個範例物件。

這就是堆、棧、方法區的互動關係

11、棧
又稱棧記憶體,主管程式的執行,生命週期和執行緒同步,執行緒結束,棧記憶體就釋放了,不存在垃圾回收

棧:先進後出
佇列:先進先出(FIFO)
1、棧中存放啥?
8大基本型別
物件參照
範例的方法
2、棧執行原理
棧表示Java方法執行的記憶體模型
每呼叫一個方法就會爲每個方法生成一個棧幀(Stack Frame),每個方法被呼叫和完成的過程,都對應一個棧幀從虛擬機器棧上入棧和出棧的過程。
程式正在執行的方法一定在棧的頂部
image-20200802105127681
3、堆疊溢位StackOverflowError
舉個例子:

public class Test {
public static void main(String[] args) {
new Test().a();
}

public void a() {
    b();
}

public void b() {
    a();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
最開始,main()方法壓入棧中,然後執行a(),a()壓入棧中;再呼叫b(),b()壓入棧中;以此往復,a與b方法不斷被壓入棧中,最終導致棧溢位
image-20200802175751568
image-20200802100238868

12、堆
Heap,一個JVM只有一個堆記憶體(棧是執行緒級的),堆記憶體的大小是可以調節的

image-20200803130536438
1、堆中有啥?
範例化的物件

2、堆記憶體詳解
image-20200802183315909

1、Young 年輕代
物件誕生、成長甚至死亡的區

Eden Space(伊甸園區):所有的物件都是在此new出來的
Survivor Space(倖存區)
倖存0區(From Space)(動態的,From和To會互相交換)
倖存1區(To Space)
Eden區佔大容量,Survivor兩個區佔小容量,預設比例是8:1:1。

2、Tenured 老年代
3、Perm 元空間
儲存的是Java執行時的一些環境或類資訊,這個區域不存在垃圾回收!關閉虛擬機器就會釋放這個區域記憶體!

這個區域常駐記憶體,用來存放JDK自身攜帶的Class物件、Interface元數據。
名稱演變

jdk1.6之前:永久代
jdk1.7:永久代慢慢退化,去永久代
jdk1.8之後:永久代改名爲元空間
注意:元空間在邏輯上存在,在物理上不存在

新生代 + 老年代的記憶體空間 = JVM分配的總記憶體

如圖所示:

image-20200802211056044

3、什麼是OOM?
記憶體溢位java.lang.OutOfMemoryError

產生原因:

分配的太少
用的太多
用完沒釋放
4、GC垃圾回收
GC垃圾回收,主要在年輕代和老年代

首先,物件出生再伊甸園區

假設伊甸園區只能存一定數量的物件,則每當存滿時就會觸發一次輕GC(Minor GC)
輕GC清理後,有的物件可能還存在參照,就活下來了,活下來的物件就進入倖存區;有的物件沒用了,就被GC清理掉了;每次輕GC都會使得伊甸園區爲空
如果倖存區和伊甸園都滿了,則會進入老年代,如果老年代滿了,就會觸發一次重GC(FullGC),年輕代+老年代的物件都會清理一次,活下的物件就進入老年代
如果新生代和老年代都滿了,則OOM
Minor GC:伊甸園區滿時觸發;從年輕代回收記憶體

Full GC:老年代滿時觸發;清理整個堆空間,包含年輕代和老年代

Major GC:清理老年代

什麼情況永久區會崩?

一個啓動類載入了大量的第三方Jar包,Tomcat部署了過多應用,或者大量動態生成的反射類

這些東西不斷的被載入,直到記憶體滿,就會出現OOM

14、堆記憶體調優
1、檢視並設定JVM堆記憶體
檢視我們jvm的堆記憶體

public class Test {
public static void main(String[] args) {
//返回jvm試圖使用的最大記憶體
long max = Runtime.getRuntime().maxMemory();
//返回jvm的初始化記憶體
long total = Runtime.getRuntime().totalMemory();
//預設情況下:分配的總記憶體爲電腦記憶體的1/4,初始化記憶體爲電腦記憶體的1/64
System.out.println(「max=」 + max / (double) 1024 / 1024 / 1024 + 「G」);
System.out.println(「total=」 + total / (double) 1024 / 1024 / 1024 + 「G」);
}
}
1
2
3
4
5
6
7
8
9
10
11
image-20200802203740212
image-20200802203754264
預設情況下:

JVM最大分配記憶體爲電腦記憶體的1/4
JVM初始化記憶體爲電腦記憶體的1/64
我們可以手動調堆記憶體大小

image-20200802203828544
在VM options中可以指定jvm試圖使用的最大記憶體和jvm初始化記憶體大小

-Xms1024m -Xmx1024m -Xlog:gc*
1
-Xmx用來設定jvm試圖使用的最大記憶體,預設爲1/4
-Xms用來設定jvm初始化記憶體,預設爲1/64
-Xlog:gc*用來列印GC垃圾回收資訊
image-20200802205748906
image-20200802205834231
2、怎麼排除OOM錯誤?

  1. 嘗試擴大堆記憶體看結果
    利用上述方法指定jvm試圖使用的最大記憶體和jvm初始化記憶體大小

  2. 利用記憶體快照工具JProfiler
    記憶體快照工具:

MAT(Eclipse)
JProfiler
作用:

分析Dump記憶體檔案,快速定位記憶體漏失
獲得堆中的檔案
獲得大的物件

3. 什麼是Dump檔案?如何分析?
Dump檔案是進程的記憶體映象,可以把程式的執行狀態通過偵錯程式儲存到dump檔案中

舉個例子

import java.util.ArrayList;

public class Test {
byte[] array = new byte[1024 * 1024];//1M

public static void main(String[] args) {
    ArrayList<Test> list = new ArrayList<>();
    int count = 0;
    try {
        while (true) {
            list.add(new Test());
            count++;
        }
    } catch (Exception e) {
        System.out.println("count=" + count);
        e.printStackTrace();
    }
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
執行該程式,報錯OOM

image-20200803101511962
接下來我們設定以下堆記憶體,並附加生成對應的dump檔案的指令

-Xms1m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
1
-XX:+HeapDumpOnOutOfMemoryError表示當JVM發生OOM時,自動生成DUMP檔案。
再次點選執行,下載了對應的Dump檔案

image-20200803101950485
我們右鍵該類,點選Show in Explorer
image-20200803102029506

一直點選上級目錄,直到找到.hprof檔案,與src同級目錄下

image-20200803102326809
我們雙擊開啓,可以看到每塊所佔的大小,便於分析問題
image-20200803103451739

點選Thread Dump,裏面是所有的執行緒,點選對應的執行緒可以看到相應的錯誤,反饋到具體的行,便於排錯

image-20200803103549701
每次開啓Dump檔案檢視完後,建議刪除,可以在idea中看到,開啓檔案後生成了很多內容,佔記憶體,建議刪除
image-20200803105321131

附:安裝Jprofiler教學
idea中安裝外掛
image-20200802220019318

下載用戶端 https://www.ej-technologies.com/download/jprofiler/files
image-20200802220133541

安裝用戶端

選擇自定義安裝,注意:路徑不能有中文和空格
image-20200802221801637
image-20200802222314832
這裏name和Company任意,License Key大家可以尋找對應版本的註冊機獲得
image-20200802224806160
image-20200802224847517

後續預設,安裝成功即可!!!

安裝完成後,重新啓動IDEA,可以看到我們的記憶體快照工具

image-20200803100145161
​ 開啓IDEA的設定,找到Tools裏面的JProfiler,沒有設定位置則設定位置

image-20200803100302668 此時則全部安裝完成!

15、GC垃圾回收
1、回顧
Garbage Collection:垃圾回收
image-20200803111122670

在12.4中,我們已經對GC的流程進行了大概的講解,這裏做一些總結:

JVM在進行GC時,並不是對年輕代、老年代統一回收;大部分時候,回收都是在年輕代

GC分爲兩種:

輕GC(清理年輕代)
重GC(清理年輕代+老年代)
2、GC演算法
1、參照計數演算法(很少使用)
每個物件在建立的時候,就給這個物件系結一個計數器。

每當有一個參照指向該物件時,計數器加一;每當有一個指向它的參照被刪除時,計數器減一。

這樣,當沒有參照指向該物件時,該物件死亡,計數器爲0,這時就應該對這個物件進行垃圾回收操作。

image-20200803131912296
2、複製演算法
複製演算法主要發生在年輕代( 倖存0區 和 倖存1區)

當Eden區滿的時候,會觸發輕GC,每觸發一次,活的物件就被轉移到倖存區,死的就被GC清理掉了,所以每觸發輕GC時,Eden區就會清空;
物件被轉移到了倖存區,倖存區又分爲From Space和To Space,這兩塊區域是動態交換的,誰是空的誰就是To Space,然後From Space就會把全部物件轉移到To Space去;
那如果兩塊區域都不爲空呢?這就用到了複製演算法,其中一個區域會將存活的物件轉移到令一個區域去,然後將自己區域的記憶體空間清空,這樣該區域爲空,又成爲了To Space;
所以每次觸發輕GC後,Eden區清空,同時To區也清空了,所有的物件都在From區
這也就是倖存0區和倖存1區總有一塊爲空的原因

image-20200803120637032
好處:沒有記憶體的碎片(記憶體集中在一塊)

壞處:

浪費了記憶體空間(浪費了倖存區一半空間)
物件存活率較高的場景下(比如老年代那樣的環境),需要複製的東西太多,效率會下降。
最佳使用環境:物件存活度較低的時候,也就是年輕代

3、標記–清除演算法
爲每個物件儲存一個標記位,記錄物件的生存狀態

標記階段:這個階段內,爲每個物件更新標記位,檢查物件是否死亡;
清除階段:該階段對死亡的物件進行清除,執行 GC 操作。
image-20200803131533109
缺點:兩次掃描嚴重浪費時間,會產生記憶體碎片

優點:不需要額外的空間

4、標記–整理演算法
標記-整理法 是 標記-清除法 的一個改進版。

又叫做 標記-清楚-壓縮法

標記階段,該演算法也將所有物件標記爲存活和死亡兩種狀態;https://www.szcbjs.com/

不同的是,在第二個階段,該演算法並沒有直接對死亡的物件進行清理,而是將所有存活的物件整理一下,放到另一處空間,然後把剩下的所有物件全部清除。
image-20200803131545066

可以進一步優化,在記憶體碎片不太多的情況下,就繼續標記清除,到達一定量的時候再壓縮

總結
記憶體(時間複雜度)效率:複製演算法 > 標記清除演算法 > 標記壓縮演算法

記憶體整齊度:複製演算法 = 標記壓縮法 > 標記清除法

記憶體利用率:標記壓縮法 = 標記清除法 > 複製演算法

思考:有沒有最優的演算法?
沒有最優的演算法,只有最合適的演算法

GC 也稱爲 分代收集演算法

對於年輕代:

物件存活率低
用複製演算法
對於老年代:

區域大,物件存活率高
用標記清除+標記壓縮混合實現