再也不怕JVM調優啦~

、楽. 2022-01-08 04:03:04 阅读数:486

再也不 再也 jvm

1. 常用命令

在日常工作中,我們可以使用一些命令來幫助我們快速定比特JVM中的問題。

1.1 JPS

查看java進程

The jps command lists the instrumented Java HotSpot VMs on the target system.

The command is limited to reporting information on JVMs for which it has the access permissions.

image-20220103205947388

1.2 jinfo

  1. 實時查看和調整JVM配置參數

    The jinfo command prints Java configuration information for a specified Java process or core file or a remote debug server. The configuration information includes Java system properties and Java Virtual Machine (JVM) command-line flags.

  2. 查看用法

    jinfo -flag name PID 查看某個java進程的name屬性的值

jinfo -flag MaxHeapSize PID
jinfo -flag UseG1GC PID

image-20220103210353352

  1. 修改

    參數只有被標記為manageable的flags可以被實時修改

jinfo -flag [+|-] PID
jinfo -flag <name>=<value> PID
  1. 查看曾經賦過值的一些參數
jinfo -flags PID

image-20220103211226639

1.3 jstat

  1. 查看虛擬機性能統計信息

    The jstat command displays performance statistics for an instrumented Java HotSpot VM. The target JVM is identified by its virtual machine identifier, or vmid option.

  2. 查看類裝載信息

jstat -class PID 1000 10 查看某個java進程的類裝載信息,每1000毫秒輸出一次,共輸出10次

image-20220103211404736

  1. 查看垃圾收集信息
jstat -gc PID 1000 10

image-20220103211506730

1.4 jstack

  1. 查看線程堆棧信息

    The jstack command prints Java stack traces of Java threads for a specified Java process, core file, or remote debug server.

  2. 用法

jstack PID

image-20220103211658054

  1. 排查死鎖案例
  • 代碼案例

    public class DeadLockDemo {
    
    public static void main(String[] args) {
    
    DeadLock d1 = new DeadLock(true);
    DeadLock d2 = new DeadLock(false);
    Thread t1 = new Thread(d1);
    Thread t2 = new Thread(d2);
    t1.start();
    t2.start();
    }
    }
    //定義鎖對象
    class MyLock {
    
    public static Object obj1 = new Object();
    public static Object obj2 = new Object();
    }
    //死鎖代碼
    class DeadLock implements Runnable {
    
    private boolean flag;
    DeadLock(boolean flag) {
    
    this.flag = flag;
    }
    public void run() {
    
    if (flag) {
    
    while (true) {
    
    synchronized (MyLock.obj1) {
    
    System.out.println(Thread.currentThread().getName() + "----if 獲得obj1鎖");
    synchronized (MyLock.obj2) {
    
    System.out.println(Thread.currentThread().getName() + "--- -if獲得obj2鎖");
    }
    }
    }
    } else {
    
    while (true) {
    
    synchronized (MyLock.obj2) {
    
    System.out.println(Thread.currentThread().getName() + "----否則 獲得obj2鎖");
    synchronized (MyLock.obj1) {
    
    System.out.println(Thread.currentThread().getName() + "--- -否則獲得obj1鎖");
    }
    }
    }
    }
    }
    }
    
  • 運行結果

    image-20220103212105455

  • jps查看進程ID

    image-20220103212144330

  • jstack分析

    image-20220103212210022

    我們把打印信息拉到最後可以發現找到了死鎖。

    image-20220103212238311

1.5 jmap

  1. 生成堆轉儲快照

    The jmap command prints shared object memory maps or heap memory details of a specified process, core file, or remote debug server.

  2. 打印出堆內存相關信息

jmap -heap PID

image-20220103212606695

  1. dump出堆內存相關信息
jmap -dump:format=b,file=heap.hprof PID

image-20220103212859208

  1. 要是在發生堆內存溢出的時候,能自動dump出該文件就好了

    一般在開發中,JVM參數可以加上下面兩句,這樣內存溢出時,會自動dump出該文件:

    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof

如何測試:首先設置堆內存大小: -Xms20M -Xmx20M

啟動項目,然後訪問接口,接口中寫一個死循環,不停添加對象即可。這就可以使得堆內存溢出

  1. 關於dump下來的文件

    一般dump下來的文件可以結合工具來分析,這塊後面再說。

2. 執行引擎

我們創建一個Person.java 文件 ,javac編譯器將Person.java源碼文件編譯成class文件[我們把這裏的編譯稱為前期編譯],交給JVM運行,因為JVM只能認識class字節碼文件。同時在不同的操作系統上安裝對應版本的JDK,裏面包含了各自屏蔽操作系統底層細節的JVM,這樣同一份class文件就能運行在不同的操作系統平臺之上,得益於JVM。這也 是Write Once,Run Anywhere的原因所在。

最終JVM需要把字節碼指令轉換為機器碼,可以理解為是0101這樣的機器語言,這樣才能運行在不同的機器上,那麼由字節碼轉變為機器碼是誰來做的呢?說白了就是誰來執行這些字節碼指令的呢?這就是執行引擎。

image-20220103213506722

2.1 解釋執行

Interpreter,解釋器逐條把字節碼翻譯成機器碼並執行,跨平臺的保證。

剛開始執行引擎只采用了解釋執行的,但是後來發現某些方法或者代碼塊被調用執行的特別頻繁時,就會把這些代碼認定為“熱點代碼”。

2.2 即時編譯器

Just-In-Time compilation(JIT),即時編譯器先將字節碼編譯成對應平臺的可執行文件,運行速度快。即時編譯器會把這些熱點代碼編譯成與本地平臺關聯的機器碼,並且進行各層次的優化,保存到內存中。

2.3 JVM采用哪種方式

JVM采取的是混合模式,也就是解釋+編譯的方式,對於大部分不常用的代碼,不需要浪費時間將其編譯成機器碼,只需要用到的時候再以解釋的方式運行;對於小部分的熱點代碼,可以采取編譯的方式,追求更高的運行效率。

2.4 即時編譯器類型

  1. HotSpot虛擬機裏面內置了兩個JIT:C1和C2;

    C1也稱為Client Compiler,適用於執行時間短或者對啟動性能有要求的程序;

    C2也稱為Server Compiler,適用於執行時間長或者對峰值性能有要求的程序;

  2. Java7開始,HotSpot會使用分層編譯的方式;

    也就是會結合C1的啟動性能優勢和C2的峰值性能優勢,熱點方法會先被C1編譯,然後熱點方法中的熱點會被 C2再次編譯

2.5 AOT和Graal VM

2.5.1 AOT

在Java9中,引入了AOT(Ahead-Of-Time)編譯器

即時編譯器是在程序運行過程中,將字節碼翻譯成機器碼。而AOT是在程序運行之前,將字節碼轉換為機器碼。

優勢 :這樣不需要在運行過程中消耗計算機資源來進行即時編譯

劣勢 :AOT 編譯無法得知程序運行時的信息,因此也無法進行基於類層次分析的完全虛方法內聯,或者基於程序 profile 的投機性優化(並非硬性限制,我們可以通過限制運行範圍,或者利用上一次運行的程序 profile 來繞開這兩個限制)

2.5.2 Graal VM

官網 : https://www.oracle.com/tools/graalvm-enterprise-edition.html

在Java10中,新的JIT編譯器Graal被引入。

它是一個以Java為主要編程語言,面向字節碼的編譯器。跟C++實現的C1和C2相比,模塊化更加明顯,也更加容易維護。

Graal既可以作為動態編譯器,在運行時編譯熱點方法;也可以作為靜態編譯器,實現AOT編譯。

除此之外,它還移除了編程語言之間的邊界,並且支持通過即時編譯技術,將混雜了不同的編程語言的代碼編譯到同一段二進制碼之中,從而實現不同語言之間的無縫切換。

3. 工具

了解常用命令及執行引擎後,我們來看下常用的JVM工具。

3.1 jconsole

JConsole工具是JDK自帶的可視化監控工具。查看java應用程序的運行概况、監控堆信息、永久區使用情况、類加載情况等。

我們只需要在命令行中輸入:jconsole

image-20220103214635850

3.2 jvisualvm

在命令行中輸入:jvisualvm

Visual GC插件下載地址 :https://visualvm.github.io/pluginscenters.html

image-20220103214740775

3.2.1 監控本地Java進程

可以監控本地的java進程的CPU,類,線程等;

3.2.2 監控遠端Java進程

  1. 在visualvm中選中“遠程”,右擊“添加”

  2. 主機名上寫服務器的ip地址,然後點擊“確定”

  3. 右擊該主機,添加“JMX”,也就是通過JMX技術具體監控遠端服務器哪個Java進程

  4. 要想讓服務器上的tomcat被連接,需要改一下Catalina.sh這個文件

    注意下面的8998不要和服務器上其他端口沖突

    JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote - Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.port=8998 -Dcom.sun.management.jmxremote.ssl=false - Dcom.sun.management.jmxremote.authenticate=true - Dcom.sun.management.jmxremote.access.file=../conf/jmxremote.access - Dcom.sun.management.jmxremote.password.file=../conf/jmxremote.password"
    
  5. 在…/conf文件中添加兩個文件jmxremote.access和jmxremote.password

    jmxremote.access

    guest readonly
    manager readwrite
    

    jmxremote.password

    guest guest
    manager manager
    

    授予權限:chmod 600 jmxremot

  6. 將連接服務器地址改為公網ip地址

    hostname -i 查看輸出情况
    172.26.225.240 172.17.0.1
    vim /etc/hosts
    172.26.255.240 39.100.39.63
    
  7. 設置上述端口對應的阿裏雲安全策略和防火牆策略

  8. 啟動tomcat,來到bin目錄

    ./startup.sh
    
  9. 查看tomcat啟動日志以及端口監聽

    tail -f ../logs/catalina.out
    lsof -i tcp:8080
    
  10. 查看8998監聽情况,可以發現多開了幾個端口

    lsof -i:8998 得到PID
    netstat -antup | grep PID
    
  11. 在剛才的JMX中輸入8998端口,並且輸入用戶名和密碼則登錄成功

    端口:8998
    用戶名:manager
    密碼:manager
    

3.2.3 arthas

github :https://github.com/alibaba/arthas

Arthas 是Alibaba開源的Java診斷工具,采用命令行交互模式,是排查jvm相關問題的利器。

下載安裝

curl -O https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar
or
java -jar arthas-boot.jar -h
# 然後可以選擇一個Java進程

常用命令

具體每個命令怎麼使用,大家可以自己查閱資料

version:查看arthas版本號
help:查看命名幫助信息
cls:清空屏幕
session:查看當前會話信息
quit:退出arthas客戶端
---
dashboard:當前進程的實時數據面板
thread:當前JVM的線程堆棧信息
jvm:查看當前JVM的信息
sysprop:查看JVM的系統屬性
---
sc:查看JVM已經加載的類信息
dump:dump已經加載類的byte code到特定目錄
jad:反編譯指定已加載類的源碼
---
monitor:方法執行監控
watch:方法執行數據觀測
trace:方法內部調用路徑,並輸出方法路徑上的每個節點上耗時
stack:輸出當前方法被調用的調用路徑
.........

3.3 內存分析工具

3.3.1 MAT

Java堆分析器,用於查找內存泄漏

Heap Dump,稱為堆轉儲文件,是Java進程在某個時間內的快照。

它在觸發快照的時候保存了很多信息:Java對象和類信息。

通常在寫Heap Dump文件前會觸發一次Full GC。

下載地址 :https://www.eclipse.org/mat/downloads.php

  1. 獲取dump文件

    • 手動

      jmap -dump:format=b,file=heap.hprof PID
      
    • 自動 (啟動的時候添加參數)

      -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.hprof
      
  2. Dump的信息

  3. 使用

    • Histogram:可以列出內存中的對象,對象的個數及其大小

      Class Name:類名稱,java類名

      Objects:類的對象的數量,這個對象被創建了多少個

      Shallow Heap:一個對象內存的消耗大小,不包含對其他對象的引用

      Retained Heap:是shallow Heap的總和,即該對象被GC之後所能回收到內存的總和

      右擊類名—>List Objects—>with incoming references—>列出該類的實例 ;

      右擊Java對象名—>Merge Shortest Paths to GC Roots—>exclude all …—>找到GC Root以及原因;

      JVM中GC Roots的大致分類:

      1. Class 由System Class Loader/Boot Class Loader加載的類對象,這些對象不會被回收。需要注意的是其它的Class Loader實例加載的類對象不一定是GC root,除非這個類對象恰好是其它形式的GC root;

      2. Thread 線程,激活狀態的線程;

      3. Stack Local 棧中的對象。每個線程都會分配一個棧,棧中的局部變量或者參數都是GC root,因為它們的引用隨時可能被用到;

      4. JNI Local JNI中的局部變量和參數引用的對象;可能在JNI中定義的,也可能在虛擬機中定義

      5. JNI Global JNI中的全局變量引用的對象;同上

      6. Monitor Used 用於保證同步的對象,例如wait(),notify()中使用的對象、鎖等。

      7. Held by JVM JVM持有的對象。JVM為了特殊用途保留的對象,它與JVM的具體實現有關。比如有System Class Loader, 一些Exceptions對象,和一些其它的Class Loader。對於這些類,JVM也沒有過多的信息。

    • Leak Suspects:查找並分析內存泄漏的可能原因

      Reports—>Leak Suspects—>Details

    • Top Consumers:列出大對象

3.3.2 heaphero

https://heaphero.io/

3.3.3 perfma

https://console.perfma.com/

3.4 GC日志分析工具

要想分析日志的信息,得先拿到GC日志文件才行,所以得先配置一下,根據前面參數的學習,下面的配置很容易看懂。比如打開windows中的catalina.bat,在第一行加上:

XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log

這樣使用startup.bat啟動tomcat的時候就能够在當前目錄下拿到gc.log文件,可以看到默認使用的是ParallelGC。

3.4.1 gceasy

GCeasy是一款在線的GC日志分析器,可以通過GC日志分析進行內存泄露檢測、GC暫停原因分析、JVM配置建議優化等功能,而且是可以免費使用的(有一些服務是收費的)。

http://gceasy.io

3.4.2 gcplot

https://it.gcplot.com/

3.4.3 GCViewer

上面介紹了一款在線的GC日志分析器,下面介紹一個離線版的GCViewer,其最新版本為1.36,我用的就是這個版本,需要jdk1.8才可以使用,Github地址為https://github.com/chewiebug/GCViewer,下載下來之後執行 mvn clean install -Dmaven.test.skip=true 命令進行編譯,編譯完成後在target目錄下會看到jar包,雙擊打開即可。

4. 性能優化

JVM的性能優化可以分為代碼層面和非代碼層面。

在代碼層面,大家可以結合字節碼指令進行優化,比如一個循環語句,可以將循環不相關的代碼提取到循環體之外,這樣在字節碼層面就不需要重複執行這些代碼了。

在非代碼層面,一般情况可以從內存、gc以及cpu占用率等方面進行優化。

注意,JVM調優是一個漫長和複雜的過程,而在很多情况下,JVM是不需要優化的,因為JVM本身已經做了很多的內部優化操作。

那今天我們就從內存、gc以及cpu這3個方面和大家一起探討一下JVM的優化,但是大家要注意的是不要為了調優而調優,因為我們自己調優可能真不如JVM本身的調優

4.1 內存

4.1.1 內存分配

正常情况下不需要設置,那如果是促銷或者秒殺的場景呢?

每臺機器配置2c4G,以每秒3000筆訂單為例,整個過程持續60秒。

具體流程以及解决方法大家可以觀看下圖。

image-20220103221523680

4.1.2 內存溢出(OOM)

出現內存溢出一般會有兩個原因:

  • 大並發情况下

  • 內存泄露導致內存溢出

大並發[秒殺]

  1. 出現大並發的話,我們可以從如下方面進行優化解决:
  2. 瀏覽器緩存、本地緩存、驗證碼;
  3. CDN靜態資源服務器;
  4. 集群+負載均衡;
  5. 動靜態資源分離、限流[基於令牌桶、漏桶算法];
  6. 應用級別緩存、接口防刷限流、隊列、Tomcat性能優化;
  7. 异步消息中間件;
  8. Redis熱點數據對象緩存;
  9. 分布式鎖、數據庫鎖;
  10. 5分鐘之內如果沒有支付,取消訂單、恢複庫存等;

內存泄露導致內存溢出

針對這種情况我們可以自行創建一個測試案例,使用ThreadLocal引起內存泄露,最終導致內存溢出。

public class TLController {

@RequestMapping(value = "/tl")
public String tl(HttpServletRequest request) {

ThreadLocal<Byte[]> tl = new ThreadLocal<Byte[]>(); // 1MB 
tl.set(new Byte[1024*1024]); return "ok";
}
}
  1. 將JAR包上傳到阿裏雲服務器

  2. 啟動 需要自動輸出dump文件

    java -jar -Xms1000M -Xmx1000M -XX:+HeapDumpOnOutOfMemoryError - XX:HeapDumpPath=jvm.hprof jvm-case-0.0.1-SNAPSHOT.jar
    
  3. 使用jmeter模擬10000次並發

  4. top命令查看

  5. jstack查看線程情况,發現沒有死鎖或者IO阻塞的情况

    jstack PID
    java -jar arthas.jar ---> thread
    
  6. 查看堆內存的使用,發現堆內存的使用率已經高達88.95%

    jmap -heap PID
    java -jar arthas.jar ---> dashboard
    
  7. 此時可以大體判斷出來,發生了內存泄露從而導致的內存溢出,那怎麼排查呢?

    jmap -histo:live PID | more
    獲取到jvm.hprof文件,上傳到指定的工具分析,比如heaphero.io
    

4.2 GC

這裏以G1垃圾收集器調優為例;

4.2.1 是否選用G1

官網 :https://docs.oracle.com/javase/8/docs/technotes/guides/vm/G1.html#use_cases

  • 50%以上的堆被存活對象占用

  • 對象分配和晋昇的速度變化非常大

  • 垃圾回收時間比較長

4.2.2 G1調優

  1. 使用G1GC垃圾收集器: -XX:+UseG1GC

    修改配置參數,獲取到gc日志,使用GCViewer分析吞吐量和響應時間

  2. 調整內存大小再獲取gc日志分析

    -XX:MetaspaceSize=100M
    -Xms300M
    -Xmx300M
    
  3. 調整最大停頓時間

    -XX:MaxGCPauseMillis=200 設置最大GC停頓時間指標
    
  4. 啟動並發GC時堆內存占用百分比

    -XX:InitiatingHeapOccupancyPercent=45
    

    G1用它來觸發並發GC周期,基於整個堆的使用率,而不只是某一代內存的使用比例。值為 0 則錶示“一直執行GC循環)’. 默認值為 45 (例如, 全部的 45% 或者使用了45%).

4.2.3 G1調優最佳實戰

官網 :https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations)

  1. 不要手動設置新生代和老年代的大小,只要設置整個堆的大小

    G1收集器在運行過程中,會自己調整新生代和老年代的大小;

    其實是通過adapt代的大小來調整對象晋昇的速度和年齡,從而達到為收集器設置的暫停時間目標 ;

    如果手動設置了大小就意味著放弃了G1的自動調優 ;

  2. 不斷調優暫停時間目標

    一般情况下這個值設置到100ms或者200ms都是可以的(不同情况下會不一樣),但如果設置成50ms就不太合理。暫停時間設置的太短,就會導致出現G1跟不上垃圾產生的速度。最終退化成Full GC。所以對這個參數的調優是一個持續的過程,逐步調整到最佳狀態。暫停時間只是一個目標,並不能總是得到滿足。

  3. 使用-XX:ConcGCThreads=n來增加標記線程的數量

    IHOP如果閥值設置過高,可能會遇到轉移失敗的風險,比如對象進行轉移時空間不足。如果閥值設置過低,就會使標記周期運行過於頻繁,並且有可能混合收集期回收不到空間。IHOP值如果設置合理,但是在並發周期時間過長時,可以嘗試增加並發線程數,調高ConcGCThreads。

  4. MixedGC調優

    -XX:InitiatingHeapOccupancyPercent
    -XX:G1MixedGCLiveThresholdPercent
    -XX:G1MixedGCCountTarger
    -XX:G1OldCSetRegionThresholdPercent
    
  5. 適當增加堆內存大小

  6. 不正常的Full GC

    有時候會發現系統剛剛啟動的時候,就會發生一次Full GC,但是老年代空間比較充足,一般是由 Metaspace區域引起的。可以通過MetaspaceSize適當增加其空間,比如256M。

4.3 CPU占用率高

  1. top

  2. top -Hp PID

  3. jstack PID | grep tid

5. JVM性能優化指南

image-20220103223258334

6. 常見問題

  1. 內存泄漏與內存溢出的區別

    內存泄漏是指不再使用的對象無法得到及時的回收,持續占用內存空間,從而造成內存空間的浪費。

    內存泄漏很容易導致內存溢出,但內存溢出不一定是內存泄漏導致的。

  2. young gc會有stw嗎?

    不管什麼 GC,都會發送 stop-the-world,區別是發生的時間長短。而這個時間跟垃圾收集器又有關系,Serial、PartNew、Parallel Scavenge 收集器無論是串行還是並行,都會掛起用戶線程,而 CMS和 G1 在並發標記時,是不會掛起用戶線程的,但其它時候一樣會掛起用戶線程,stop the world 的時間相對來說就小很多了。

  3. major gc和full gc的區別

    Major GC在很多參考資料中是等價於 Full GC 的,我們也可以發現很多性能監測工具中只有 Minor GC 和 Full GC。一般情况下,一次 Full GC 將會對年輕代、老年代、元空間以及堆外內存進行垃圾回收。觸發 Full GC 的原因有很多:當年輕代晋昇到老年代的對象大小,並比目前老年代剩餘的空間大小還要大時,會觸發 Full GC;當老年代的空間使用率超過某閾值時,會觸發 Full GC;當元空間不足時(JDK1.7永久代不足),也會觸發 Full GC;當調用 System.gc() 也會安排一次 Full GC。

  4. 什麼是直接內存

    Java的NIO庫允許Java程序使用直接內存。直接內存是在java堆外的、直接向系統申請的內存空間。通常訪問直接內存的速度會優於Java堆。因此出於性能的考慮,讀寫頻繁的場合可能會考慮使用直接內存。由於直接內存在java堆外,因此它的大小不會直接受限於Xmx指定的最大堆大小,但是系統內存是有限的,Java堆和直接內存的總和依然受限於操作系統能給出的最大內存。

  5. 垃圾判斷的方式

    引用計數法:指的是如果某個地方引用了這個對象就+1,如果失效了就-1,當為0就會回收但是JVM沒有用這種方式,因為無法判定相互循環引用(A引用B,B引用A)的情况。

    引用鏈法: 通過一種GC ROOT的對象(方法區中靜態變量引用的對象等-static變量)來判斷,如果有一條鏈能够到達GC ROOT就說明,不能到達GC ROOT就說明可以回收。

  6. 不可達的對象一定要被回收嗎?

    即使在可達性分析法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑階段”,要真正宣告一個對象死亡,至少要經曆兩次標記過程;可達性分析法中不可達的對象被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或finalize 方法已經被虛擬機調用過時,虛擬機將這兩種情况視為沒有必要執行。

    被判定為需要執行的對象將會被放在一個隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象建立關聯,否則就會被真的回收。

  7. 為什麼要區分新生代和老年代?

    當前虛擬機的垃圾收集都采用分代收集算法,這種算法沒有什麼新的思想,只是根據對象存活周期的不同將內存分為幾塊。一般將 java 堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。

    比如在新生代中,每次收集都會有大量對象死去,所以可以選擇複制算法,只需要付出少量對象的複制成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記-清除”或“標記-整理”算法進行垃圾收集。

  8. G1與CMS的區別是什麼?

    CMS 主要集中在老年代的回收,而 G1 集中在分代回收,包括了年輕代的 Young GC 以及老年代的 Mix GC;

    CMS采用標記清楚算法,會產生大量空間碎片;

    G1 使用了 Region 方式對堆內存進行了劃分,且基於標記整理算法實現,整體减少了垃圾碎片的產生;在初始化標記階段,搜索可達對象使用到的 Card Table,其實現方式不一樣。

  9. 方法區中的無用類回收

    方法區主要回收的是無用的類,那麼如何判斷一個類是無用的類的呢?

    判定一個常量是否是“廢弃常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。

    類需要同時滿足下面 3 個條件才能算是 “無用的類” :

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

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

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

版权声明:本文为[、楽.]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201080403036962.html