JVM筆記——技術點匯總


目錄

· 初步認識

    · Java里程碑(關鍵部分)

    · 理解虛擬機

    · Java虛擬機種類

    · Java語言規范

    · Java虛擬機規范

· 基本結構

    · Java堆(Heap)

    · Java棧(Stacks)

    · 方法區(Method Area)

    · 直接內存(Direct Memory)

    · 本地方法棧(Native Method Stacks)

· 常用參數

    · 設置參數

    · 查看參數

    · 跟蹤垃圾回收

    · 跟蹤類加載/卸載

    · 設置初始堆和最大堆

    · 設置堆分布

    · 處理堆溢出

    · 配置方法區

    · 配置棧

    · 配置直接內存

    · 配置工作模式

· 垃圾回收算法

    · 垃圾回收

    · 引用計數法(Reference Counting)

    · 標記清除法(Mark-Sweep)

    · 復制算法(Coping)

    · 標記壓縮法(Mark-Compact)

    · 分代算法(Generational Collecting)

    · 分區算法(Region)

    · 判斷可觸及性

    · 停頓現象

· 垃圾回收器

    · 串行回收器

    · 新生代ParNew回收器

    · 新生代ParallelGC回收器

    · 老年代ParallelOldGC回收器

    · CMS回收器

    · G1回收器

· 垃圾回收其他細節

    · 禁用System.gc()

    · 開啟System.gc()並發回收

    · 對象何時進入老年代

    · TLAB

· 性能監控工具

    · top命令

    · vmstat命令

    · iostat命令

    · pidstat命令

    · jps命令

    · jstat命令

    · jinfo命令

    · jmap命令

    · jhat命令

    · jstack命令

    · jcmd命令

    · Visual VM工具

    · Mission Control工具

· 解決OOM問題

    · 堆溢出

    · 直接內存溢出

    · 過多線程導致溢出

    · 永久區/元數據區溢出

    · GC效率低導致OOM

· String實現細節

    · String對象特點

    · String常量池

· 字節碼優化

    · 靜態編譯優化

    · JIT運行優化


 

初步認識

Java里程碑(關鍵部分)

1. 2004年,JDK 1.5發布。同時更名為J2SE 5.0。Java語言大量改進,比如支持泛型、注解、自動裝箱、枚舉類型、可變長參數、增強的foreach循環等。

2. 2011年,JDK 1.7發布。正式啟用新垃圾回收器G1,支持64位系統的壓縮指針,NIO 2.0,新增invokedynamic指令。

3. 2014年,JDK 1.8發布。全新的Lambda表達式徹底改變Java編程風格和習慣。

4. 2016年,JDK 1.9發布。最令人期待的功能應該是Java的模塊化。

理解虛擬機

1. 虛擬機。

    a) 虛擬的計算機。

    b) 軟件,執行一系列虛擬計算機指令。

2. 分類。

    a) 系統虛擬機:對物理計算機的仿真,提供一個可運行完整操作系統的軟件平台。

    b) 程序虛擬機:為執行單個計算機程序而設計,如Java字節碼指令。

Java虛擬機種類

被大規模部署和應用的是Hotspot虛擬機。

Java語言規范

1. 語言規范:定義Java語言特性,如Java語法、詞法、數據類型、變量類型、數據類型轉換約定、數組、異常等。

2. 詞法:規定每個單詞如何書寫,如關鍵字、標識符等。

3. 語法:規定語句如何書寫,如if語句等。

4. 官方文檔:http://docs.oracle.com/javase/specs/。

Java虛擬機規范

1. 虛擬機規范大概組成:

    a) 定義虛擬機的內部結構;

    b) 定義虛擬機執行的字節碼類型和功能;

    c) 定義Class文件的結構;

    d) 定義類的裝載、連接和初始化。

2. Java虛擬機執行Java字節碼,而運行的Java字節碼未必由Java語言編寫,如Groovy、Scala等都可以生成Java字節碼。

3. 官方文檔:http://docs.oracle.com/javase/specs/。

基本結構

Java堆(Heap)

1. 存儲:幾乎所有的對象。

2. 使用者:所有線程共享。

3. 結構:根據垃圾回收機制,一般划分為

    a) 新生代(New/Young Generation):新生對象或年齡不大的對象。又可能分為eden區、s0區(也稱from區)、s1區(也稱to區,from/to合稱Survivor區)。from和to是兩塊大小相等、可以互換角色的內存空間。

    b) 老年代(Tenured Generation):老年對象。

4. 過程:絕大多數情況下,對象首先分配在eden,一次新生代回收后,如果對象存活,則進入s0或s1。之后,每經一次新生代回收,如果對象存活,則年齡加1。當對象年齡達到一定條件后,則進入老年代。

5. 舉例。

 1 public class SimpleHeap {
 2     
 3     private int id;
 4     
 5     public SimpleHeap(int id) {
 6         this.id = id;
 7     }
 8     
 9     public void show() {
10         System.out.println("My ID is " + id);
11     }
12 
13     public static void main(String[] args) {
14         SimpleHeap s1 = new SimpleHeap(1);
15         SimpleHeap s2 = new SimpleHeap(2);
16         s1.show();
17         s2.show();
18     }
19 
20 }

Java棧(Stacks)

1. 存儲:線程執行的基本行為是函數調用,每次函數調用的數據都通過Java棧傳遞。

2. 使用者:線程私有。

3. 結構:棧數據結構(先進后出),棧元素為棧幀。一個棧幀至少包括局部變量表、操作數棧和幀數據區。

4. 過程:每一次函數調用,都有一個對應的棧幀入棧,每一次函數調用結束(return指令或拋出異常),都有一個棧幀出棧。

5. 局部變量表。

    a) 作用:保存函數的參數和局部變量。

    b) 其中的變量只在當前函數調用有效,函數調用結束后銷毀。

6. 操作數棧。

    a) 作用:保存計算過程中的中間結果,同時作為計算過程中變量臨時的存儲空間。

    b) 舉例:iadd指令會在操作數棧中彈出兩個整數並相加,計算結果再入棧。

7. 幀數據區。

    a) 作用:方便訪問常量池;函數返回或異常后,恢復調用者函數的棧幀。

    b) 存儲:常量池指針;異常處理表。

8. 棧上分配。

    a) 一項虛擬機優化技術。

    b) 思想:對於線程私有(不可能被其他線程訪問)的對象,將它們打散分配在棧上,而不是在堆上。函數調用結束后自行銷毀,無需垃圾回收器介入。

    c) 基礎:逃逸分析,判斷對象作用域是否可能逃逸出函數體。

    d) 舉例。

 1 public class EscapeAnalysis {
 2 
 3     private static User user;
 4     
 5     public static void test1() {
 6         // 逃逸對象
 7         user = new User();
 8         user.id = 1;
 9         user.name = "test1";
10     }
11     
12     public static void test2() {
13         // 非逃逸對象
14         User user = new User();
15         user.id = 2;
16         user.name = "test2";
17     }
18 
19 }
20 
21 class User {
22     
23     int id;
24     
25     String name;
26     
27 }

方法區(Method Area)

1. 存儲:類信息,如類字段、方法、常量池(字符串字面量、數字常量)等。

2. 使用者:線程共享。

3. 結構:JDK 1.6、JDK 1.7中,即永久區(Perm);JDK 1.8中,即元數據區(Metaspace,永久區已被移除)。注意:方法區是Java虛擬機規范的概念,永久區、元數據區是Hotspot對方法區的實現。

直接內存(Direct Memory)

1. 使用者:NIO庫,如ByteBuffer。

2. 特點:

    a) 直接向操作系統申請的內存空間,性能優於Java堆。

    b) 大小受限於操作系統分配的最大內存。

本地方法棧(Native Method Stacks)

與Java棧非常類似,不同在於用於本地方法調用。

常用參數

設置參數

java [-options] class [args...]

1. -options表示Java虛擬機啟動參數。

2. class為帶有main()函數的Java類。

3. args表示傳遞給主函數main()的參數。

查看參數

1. “-XX:+PrintVMOptions”:打印虛擬機接受到的參數(顯式參數)。

2. “-XX:+PrintCommandLineFlags”:打印虛擬機顯式和隱式參數。

跟蹤垃圾回收

1. “-XX:+PrintGC”。

    a) GC時打印簡單日志。

    b) 舉例:2次新生代GC,1次Full GC。

[GC (Allocation Failure)  42000K->36496K(56320K), 0.0009016 secs]
[GC (Allocation Failure) -- 41616K->41616K(56320K), 0.0020593 secs]
[Full GC (Ergonomics)  41616K->5655K(56320K), 0.0070610 secs]

2. “-XX:+PrintGCDetails”。

    a) GC時打印詳細日志。

    b) 舉例:2次新生代GC,1次Full GC。第1 次新生代從6570K降至648K,堆從42410K降至36496K,user表示用戶態CPU耗時,sys表示系統CPU耗時,real表示GC實際耗時。第3次Full GC對新生代、老年代和元數據區回收。

[GC (Allocation Failure) [PSYoungGen: 6570K->648K(15360K)] 42410K->36496K(56320K), 0.0008117 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) --[PSYoungGen: 5768K->5768K(15360K)] 41616K->41616K(56320K), 0.0010395 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 5768K->0K(15360K)] [ParOldGen: 35848K->5659K(40960K)] 41616K->5659K(56320K), [Metaspace: 2517K->2517K(1056768K)], 0.0065072 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

    c) 虛擬機在退出時,打印堆的詳細信息。3個16進制數字分別表示下界、當前上界和上界。以eden為例,(上界0x00000000ff600000-下界0x00000000fec00000)/1024=10240K。

Heap
 PSYoungGen      total 15360K, used 5188K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
  eden space 10240K, 50% used [0x00000000fec00000,0x00000000ff111298,0x00000000ff600000)
  from space 5120K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffb00000)
  to   space 5120K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x0000000100000000)
 ParOldGen       total 40960K, used 5659K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
  object space 40960K, 13% used [0x00000000fc400000,0x00000000fc986ee0,0x00000000fec00000)
 Metaspace       used 2523K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 273K, capacity 386K, committed 512K, reserved 1048576K

3. “-XX:+PrintHeapAtGC”。

    a) 堆GC時打印前后日志。

    b) 舉例:1次堆GC。

{Heap before GC invocations=40 (full 0):
 PSYoungGen      total 583680K, used 583110K [0x0000000780a00000, 0x00000007bfb00000, 0x00000007c0000000)
  eden space 582144K, 99% used [0x0000000780a00000,0x00000007a42635c8,0x00000007a4280000)
  from space 1536K, 70% used [0x00000007bf880000,0x00000007bf98e5f0,0x00000007bfa00000)
  to   space 1024K, 0% used [0x00000007bfa00000,0x00000007bfa00000,0x00000007bfb00000)
 ParOldGen       total 131072K, used 2060K [0x0000000701e00000, 0x0000000709e00000, 0x0000000780a00000)
  object space 131072K, 1% used [0x0000000701e00000,0x0000000702003050,0x0000000709e00000)
 Metaspace       used 2539K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 275K, capacity 386K, committed 512K, reserved 1048576K
Heap after GC invocations=40 (full 0):
 PSYoungGen      total 556544K, used 578K [0x0000000780a00000, 0x00000007bfb00000, 0x00000007c0000000)
  eden space 555520K, 0% used [0x0000000780a00000,0x0000000780a00000,0x00000007a2880000)
  from space 1024K, 56% used [0x00000007bfa00000,0x00000007bfa90928,0x00000007bfb00000)
  to   space 1536K, 0% used [0x00000007bf800000,0x00000007bf800000,0x00000007bf980000)
 ParOldGen       total 131072K, used 2574K [0x0000000701e00000, 0x0000000709e00000, 0x0000000780a00000)
  object space 131072K, 1% used [0x0000000701e00000,0x0000000702083978,0x0000000709e00000)
 Metaspace       used 2539K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 275K, capacity 386K, committed 512K, reserved 1048576K
}

4. “-XX:+PrintGCTimeStamps”:打印虛擬機啟動后GC發生的時間偏移量。

5. “-XX:+PrintGCApplicationStoppedTime”。

    a) GC時打印停頓時間。

    b) 舉例:3次GC停頓時間。

Total time for which application threads were stopped: 0.0006926 seconds, Stopping threads took: 0.0000476 seconds
Total time for which application threads were stopped: 0.0012608 seconds, Stopping threads took: 0.0000349 seconds
Total time for which application threads were stopped: 0.0012115 seconds, Stopping threads took: 0.0000374 seconds

6. “-XX:+PrintReferenceGC”:打印軟運用、弱引用、虛引用和Finallize隊列。

7. “-Xloggc”:GC日志輸出到文件。例如:

-Xloggc:E:/gc.log

跟蹤類加載/卸載

1. “-verbose:class”:打印類加載和卸載。

2. “-XX:+TraceClassLoading”:打印類加載。

3. “-XX:+TraceClassUnloading”:打印類卸載。

4. 類存在形式:

    a) 一般,類以jar打包或class文件形式存放在文件系統。

    b) ASM等在運行時動態生成類。

設置初始堆和最大堆

1. “-Xms”:堆初始大小。

2. “-Xmx”:堆最大大小。

3. 技巧:實際工作中,設置初始堆和最大堆相等,可以減少垃圾回收次數,提供性能。

設置堆分布

1. “-Xmn”:新生代大小,同時影響老年代大小。

2. “-XX:NewRatio”:新生代和老年代比例,即老年代/新生代,與“-Xmn”作用相同(舊參數)。

3. “-XX:SurvivorRatio”:edit區和from/to區的比例,即eden/from和eden/to。

4. 技巧。

    a) 新生代大小對系統性能及GC有很大影響。

    b) 實際工作中,應根據系統特點合理設置堆分布,基本策略是:盡可能將對象預留在新生代,減少老年代GC的次數。

    c) 一般,新生代設置成整個堆的1/4~1/3左右。

5. 舉例。

    a) 代碼。

 1 public class NewSizeDemo {
 2     
 3     public static void main(String[] args) {
 4         byte[] b = null;
 5         for (int index = 0; index < 10; index++) {
 6             b = new byte[5 * 1024 * 1024];
 7         }
 8     }
 9 
10 }

    b) 參數:兩組參數等效。

-Xms60m -Xmx60m -Xmn20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails
-Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=2 -XX:+PrintGCDetails

    c) 結構。

空間

大小

60MB

新生代

20MB

eden

10MB

from

5MB

to

5MB

老年代

40MB

    d) 結果:eden無法容納數組,發生GC。注意:雖然通過地址計算新生代大小為(0x0000000100000000-0x00000000fec00000)/1024=20480KB,但由於垃圾回收需要和對齊等原因,實現大小(15360K)會有損失。

[GC (Allocation Failure) [PSYoungGen: 6365K->616K(15360K)] 42205K->36456K(56320K), 0.0024206 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) --[PSYoungGen: 5736K->5736K(15360K)] 41576K->41584K(56320K), 0.0031165 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 5736K->0K(15360K)] [ParOldGen: 35848K->5655K(40960K)] 41584K->5655K(56320K), [Metaspace: 2483K->2483K(1056768K)], 0.0070298 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 15360K, used 5222K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
  eden space 10240K, 51% used [0x00000000fec00000,0x00000000ff119b20,0x00000000ff600000)
  from space 5120K, 0% used [0x00000000ff600000,0x00000000ff600000,0x00000000ffb00000)
  to   space 5120K, 0% used [0x00000000ffb00000,0x00000000ffb00000,0x0000000100000000)
 ParOldGen       total 40960K, used 5655K [0x00000000fc400000, 0x00000000fec00000, 0x00000000fec00000)
  object space 40960K, 13% used [0x00000000fc400000,0x00000000fc985f98,0x00000000fec00000)
 Metaspace       used 2489K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 273K, capacity 386K, committed 512K, reserved 1048576K

處理堆溢出

1. “-XX:+HeapDumpOnOutOfMemoryError”:堆溢出(OOM)時導出信息。

2. “-XX:HeapDumpPath”:堆溢出時導出的路徑。使用MAT等工具可分析文件。

3. “-XX:OnOutOfMemoryError”:堆溢出時執行腳本,可用於奔潰程序自救、報警、通知等。

4. 舉例:使用下面的參數運行NewSizeDemo。

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/test.dump -XX:OnOutOfMemoryError=D:/shell.bat

配置方法區

1. “-XX:PermSize”:永久區初始大小(JDK 1.6/1.7)。

2. “-XX:MaxPermSize”:永久區最大大小(JDK 1.6/1.7)。

3. “-XX:MaxMetaspaceSize”:元數據區最大大小(JDK 1.8)。

4. 技巧。

    a) 動態生成大量類(如動態代理、AOP)時,可能導致方法區溢出(OOM)。

    b) JDK 1.6/1.7,默認永久區最大大小為64MB;JDK 1.8,默認元數據區耗盡所有可用系統內存。

配置棧

1. “-Xss”:單個線程的棧最大大小。

2. “-XX:+DoEscapeAnalysis”:開啟逃逸分析(僅限Server模式下使用)。

3. “-XX:+EliminateAllocations”:開啟標量替換(默認已開啟,允許對象打散分配在棧上)。

4. 技巧:

    a) “-Xss”影響函數調用深度、局部變量大小等。

    b) 棧上分配速度快,同時避免垃圾回收,但棧相比堆較小,不適合大對象。

5. 舉例:測試函數調用深度。

    a) 代碼。

 1 public class TestStackDeep {
 2     
 3     private static int count = 0;
 4     
 5     public static void recursion() {
 6         count++;
 7         recursion();
 8     }
 9 
10     public static void main(String[] args) {
11         try {
12             recursion();
13         } catch (StackOverflowError e) {
14             System.out.println("deep of calling = " + count);
15             e.printStackTrace();
16         }
17     }
18 
19 }

    b) 參數。

-Xss128k

    c) 結果。

deep of calling = 1087
java.lang.StackOverflowError
    at gz.jvm.TestStackDeep.recursion(TestStackDeep.java:8)

配置直接內存

1. “-XX:MaxDirectMemorySize”:直接內存最大大小。如不設置,默認為“-Xmx”。

2. 技巧:直接內存適合申請次數較少、訪問較頻繁的場景。因為申請堆空間的速度遠遠高於直接內存。

配置工作模式

1. “-client”:Client模式。

2. “-server”:Server模式。

3. “-version”:查看模式。

4. 技巧。

    a) Client模式:啟動速度較快。適合用戶界面,運行時間不長。

    b) Server模式:啟動速度較慢(啟動時嘗試收集更多系統性能信息,使用更復雜的優化算法優化程序),完全啟動並穩定后,執行速度遠遠快於Client模式。適合后台長期運行的系統。

    c) 兩種模式下的各種參數默認值可能不同,可使用“-XX:+PrintFlagsFinal”參數查看。

垃圾回收算法

垃圾回收

1. 垃圾:存在於內存中的、不會再被使用的對象。

2. 回收:將內存空間空閑的區域騰出來。

3. 如果大量不會被使用的對象一直占用空間不放,需要內存空間時,無法使用這些被垃圾對象占用的內存,從而有可能導致內存溢出。

引用計數法(Reference Counting)

1. 原理:對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器加1;當引用失效時,引用計數器減1。當對象A的引用計數器值為0,則對象A不可能再被使用。

2. 特點:

    a) 簡單;

    b) 最經典、最古老的垃圾回收算法。

3. 問題:

    a) 無法處理循環引用的情況。

        i. 可達對象:通過根對象進行引用搜索,最終可以達到的對象。

        ii. 不可達對象:通過根對象進行引用搜索,最終沒有被引用到的對象。

    b) 每次引用產生和失效時,引用計數器的加、減法操作對性能有一定影響。

4. 應用:Java虛擬機未采用。

標記清除法(Mark-Sweep)

1. 原理:將垃圾回收分為標記階段和清除階段。

    a) 標記階段:從跟節點開始,標記所有可達對象。

    b) 清除階段:清除未被標記的垃圾對象。

2. 特點:現代垃圾回收算法的思想基礎。

3. 問題:回收后的空間不連續(碎片)。空間分配時,尤其大對象內存分配時,不連續的內存空間工作效率低於連續空間。

復制算法(Coping)

1. 原理:將內存空間分為兩塊,每次只使用其中一塊。垃圾回收時,將正在使用的一塊內存中的存活對象復制到未使用的一塊中,再清除正在使用的內存中的所有對象,最后交換兩塊內存的角色。

2. 特點:

    a) 如果垃圾對象多,則復制的存活對象相對較少,復制算法效率就高(如新生代)。

    b) 回收后的內存空間沒有碎片。

3. 缺點:內存折半。

4. 應用:新生代串行垃圾回收器。eden中的存活對象被復制到未使用的survivor中(假設是to),正在使用的survivor(假設是from)中的年輕對象也被復制到to中(大對象或老年對象直接進入老年代,如果to已滿,則對象也直接進入老年代)。清除eden和from中的剩余垃圾對象。

標記壓縮法(Mark-Compact)

1. 原理:標記清除算法的優化版。三步:

    a) 從根節點開始,標記所有可達對象;

    b) 將所有存活對象壓縮到內存的一端;

    c) 清理邊界外所有空間。

2. 特點:

    a) 回收后的內存空間沒有碎片。

    b) 無內存折半。

3. 應用:老年代回收。

分代算法(Generational Collecting)

1. 原理:根據對象的特點將內存空間分為幾塊,每塊內存采用不同的回收算法。

2. 應用:Java虛擬機。

    a) 新生代特點是對象朝生夕滅,約90%對象很快被回收,適合復制算法。

    b) 老年代的回收性價比低於新生代,適合標記壓縮或標記清除算法。

    c) 通常,新生代回收頻率高,回收耗時短;老年代回收頻率低,回收耗時較長。

3. 卡表(Card Table):一個比特位集合,每一個比特位表示老年代的某一區域中的對象是否持有新生代的引用。新生代GC時,根據卡表掃描老年代對象,而避免掃描所有老年代對象。下圖,每一位表示老年代4KB的空間。

分區算法(Region)

1. 原理:將堆划分成連續的不同小區間,每個小區間都獨立使用、獨立回收。

2. 特點:

    a) 可控制每次回收的小區間個數;

    b) 避免回收整個堆,減少GC停頓時間。

判斷可觸及性

1. 可觸及性的3中狀態。

    a) 可觸及的:從根節點開始,可達對象。

    b) 可復活的:對象的所有引用都被釋放,但在finalize()函數復活(注意finalize()只會執行一次)。

    c) 不可觸及的:對象的所有引用都被釋放,且執行finalize()函數后未復活。

2. 回收依據:不可觸及的對象。

3. 對象復活舉例。

 1 public class CanReliveObj {
 2     
 3     public static CanReliveObj obj;
 4     
 5     @Override
 6     protected void finalize() throws Throwable {
 7         super.finalize();
 8         System.out.println("CanReliveObj.finalize()");
 9         obj = this;
10     }
11 
12     public static void main(String[] args) throws Exception {
13         obj = new CanReliveObj();
14         
15         obj = null;
16         System.gc();
17         Thread.sleep(1000);
18         // 由於在finalize()后復活,所以打印結果不為null
19         System.out.println("obj = " + obj);
20         
21         obj = null;
22         System.gc();
23         Thread.sleep(1000);
24         // 由於finalize()只執行一次,所以不可能再復活,打印為null
25         System.out.println("obj = " + obj);
26     }
27 
28 }

4. 不建議使用finalize()釋放資源,原因:

    a) 無意中復活對象;

    b) finalize()被系統調用,調用時間不確定,推薦使用“try-catch-finally”釋放資源。

5. 4種引用類型。

    a) 對比。

類型

被回收時間

是否引起OOM

應用

強引用

寧願OOM也不回收

 

軟引用

內存緊張時

可有可無的緩存

弱引用

GC

可有可無的緩存

虛引用

隨時

跟蹤對象回收時間

    b) 代碼。

 1 import java.lang.ref.PhantomReference;
 2 import java.lang.ref.ReferenceQueue;
 3 import java.lang.ref.SoftReference;
 4 import java.lang.ref.WeakReference;
 5 
 6 public class ReferenceDemo {
 7 
 8     public static void main(String[] args) {
 9         // 強引用
10         ReferenceDemo strongReference = new ReferenceDemo();
11         // 軟引用
12         SoftReference<ReferenceDemo> softReference = new SoftReference<ReferenceDemo>(new ReferenceDemo());
13         // 弱引用
14         WeakReference<ReferenceDemo> weakReference = new WeakReference<ReferenceDemo>(new ReferenceDemo());
15         // 虛引用
16         ReferenceQueue<ReferenceDemo> referenceQueue = new ReferenceQueue<ReferenceDemo>();
17         PhantomReference<ReferenceDemo> phantomReference = new PhantomReference<ReferenceDemo>(new ReferenceDemo(), referenceQueue);
18     }
19 
20 }

停頓現象

1. Stop-The-World(STW):垃圾回收時,會產生應用程序的停頓,整個應用被卡死,沒有任何響應。

2. 目的:終止所有線程執行,此時沒有新的垃圾產生,保證系統狀態在一個瞬間的一致性,益於標記垃圾對象。

垃圾回收器

串行回收器

1. 特點:

    a) 僅適用單線程垃圾回收;

    b) 獨占式垃圾回收(回收時所有線程暫停,即STW);

    c) 成熟。

2. 適合:單CPU。

3. 參數:

    a) “-XX:+UseSerialGC”:新生代、老年代都使用串行回收器。

    b) “-XX:+UseParNewGC”:新生代使用ParNew回收器,老年代使用串行回收器。

    c) “-XX:+UseParallelGC”:新生代使用ParallelGC回收器,老年代使用串行回收器。

新生代ParNew回收器

1. 特點:

    a) 串行回收器的簡單多線程化;

    b) 獨占式(STW)。

2. 適合:並發能力較強的CPU(單CPU上效果不比串行回收器好)。

3. 參數:

    a) “-XX:+UseParNewGC”:新生代使用ParNew回收器,老年代使用串行回收器。

    b) “-XX:+UseConcMarkSweepGC”:新生代使用ParNew回收器,老年代使用CMS回收器。

    c) “-XX:ParallelGCThread”:指定線程數,一般最好與CPU數量相當。默認時,若CPU數量≤8,則為CPU數量;否則為3+((5*CPU數量)/8)。

新生代ParallelGC回收器

1. 特點:

    a) 多線程;

    b) 獨占式;

    c) 非常關注系統吞吐量。

2. 參數:

    a) “-XX:+UseParallelGC”:新生代使用ParallelGC回收器,老年代使用串行回收器。

    b) “-XX:+UseParallelOldGC”:新生代使用ParallelGC回收器,老年代使用ParallelOldGC回收器。

    c) “-XX:MaxGCPauseMillis”:指定最大垃圾回收停頓時間。如果值過小,為了達到預期停頓時間,虛擬機可能使用一個較小的堆(小堆比大堆回收快),導致垃圾回收頻繁,增加了垃圾回收總時間,降低了吞吐量。

    d) “-XX:GCTimeRatio”:指定吞吐量大小,0~100的整數,默認99。如果為n,則垃圾回收時間不超過1/(1+n)。

    e) “-XX:+UseAdaptiveSizePolicy”:開啟自適應GC策略。新生代、eden和survivors的大小比例,晉升老年代對象的年齡等參數自動調整,以達到堆大小、吞吐量和停頓時間之間的平衡。適合手工調優困難的場景,僅指定最大堆“-Xmx”、目標吞吐量“-XX:GCTimeRatio”和停頓時間“-XX:MaxGCPauseMillis”即可。

    f) 注意:吞吐量“-XX:MaxGCPauseMillis”和停頓時間“-XX:GCTimeRatio”是相互矛盾的,不可兼得。減少停頓時間,同時會減少吞吐量;增加吞吐量,同時會增加停頓時間。

老年代ParallelOldGC回收器

1. 特點:

    a) 多線程;

    b) 獨占式;

    c) 非常關注系統吞吐量;

    d) 僅JDK 1.6可以使用。

2. 參數:“-XX:+UseParallelOldGC”:新生代使用ParallelGC回收器,老年代使用ParallelOldGC回收器。

CMS回收器

1. CMS:Concurrent Mark Sweep,並發標記清除。

2. 原理:

3. 特點:

    a) 多線程;

    b) 整體上,非獨占式(應用程序運行時回收)。

3. 參數:

    a) “-XX:+UseConcMarkSweepGC”:開啟CMS回收器。

    b) “-XX:ConcGCThread”:指定並發線程數。默認並發線程數是(並行線程數+3)/4,例如新生代ParNew回收器的並行線程數是“-XX:ParallelGCThread”。

    c) “-XX:ParallelCMSThread”:同上。

    d) 注意:並發是指回收器和應用線程交替執行,並行是指應用程序停止,同時由多個線程一起GC。CMS是並發的,所以當CPU緊張時,受到CMS線程的影響,應用程序的性能在GC時可能降低。

    e) “-XX:CMSInitiatingOccupancyFraction”:達到該堆使用率閥值時開始回收。默認68,即堆使用率68%時回收。由於CMS是並發的,GC時應用程序未中斷,該閥值保證應用程序仍有足夠可用內存,而不是堆飽和時才回收。調優技巧:若內存增長緩慢,則該值稍大,可有效降低CMS觸發頻率;反之,該值稍小,避免頻繁觸發老年代串行回收。

    f) “-XX:UseCMSCompactAtFullCollection”:開啟CMS回收后內存碎片整理(即壓縮)。該整理不是並發的。

    g) “-XX:CMSFullGCsBeforeCompaction”:指定多少次CMS后進行一次碎片整理。

    h) “-XX:+CMSClassUnloadingEnabled”:開啟CMS回收Perm區。

G1回收器

1. G1回收器:Garbage-First(意為優先收集垃圾比例高的區域),是JDK 1.7正式使用的全新垃圾回收器,長期目標是取代CMS回收器。

2. 特點:

    a) 並行性:多線程GC,有效利用多核。

    b) 並發性:部分工作可與應用程序同時執行,不會在整個回收期間阻塞應用程序。

    c) 分代GC:同時兼顧新生代和老年代(其他回收器要么工作在新生代,要么老年代)。堆結構方面,並不要求整個eden區、新生代或老年代都連續。

    d) 空間整理:每次回收都會有效復制對象(適當移動),減少空間碎片。

    e) 可預見性:由於分區原因,只選取部分區域回收,較好地控制了全局停頓。

3. 過程:

    a) 新生代GC;

    b) 並發標記周期;

    c) 混合收集;

    d) 如果需要,可能FullGC。

4. 參數:

    a) “-XX:+UseG1GC”:開啟G1。

    b) “-XX:MaxGCPauseMillis”:指定目標最大停頓時間。G1會調整新生代和老年代比例、堆大小、晉升年齡等,試圖達到預設目標。但不可能兼得,停頓時間縮短,GC次數會增加。默認200。

    c) “-XX:ParallelGCThread”:指定並行線程數。

    d) “-XX:InitiatingHeapOccupancyPercent”:達到該堆使用率閥值時開始並發標記周期。默認45,即堆使用率45%時開始。如果該值偏大,會導致並發周期遲遲不啟動,引起Full GC概率增加;如果該值過小,會並發周期頻繁,大量GC線程搶占CPU,導致應用程序性能下降。

5. 詳情:http://www.oracle.com/technetwork/tutorials/tutorials-1876574.html。

垃圾回收其他細節

禁用System.gc()

1. System.gc():默認時,觸發Full GC,對新生代、老年代回收。

2. 禁用原因:一般認為GC是自動的。

3. 禁用方法:“-XX:+DisableExplicitGC”參數。

開啟System.gc()並發回收

1. 開啟原因:默認時,System.gc()使用傳統方式Full GC,忽略“-XX:+UseG1GC”和“-XX:+UseConcMarkSweepGC”參數,無並發執行。

2. 開啟方法:“-XX:+ExplicitGCInvokesConcurrent”參數。

對象何時進入老年代

1. 老年對象進入老年代:對象經歷的GC次數達到“-XX:MaxTenuringThreshold”參數時,進入老年代。該參數默認為15。

2. 大對象進入老年代:對象體積很大,新生代eden區和survivors區都無法容納,則進入老年代。對於串行和ParNew回收器,晉升老年代體積閥值參數“-XX:PretenureSizeThreshold”,單位字節,默認為0,即有運行情況決定。

TLAB

1. TLAB:Thread Local Allocation Buffer,線程本地分配緩存。

2. 原理:

    a) 開啟TLAB時,虛擬機為每一個Java線程在eden區分配一塊TLAB空間。

    b) 由於堆是全局共享的,堆上分配的對象都要同步,多線程競爭激烈時效率會下降。線程專屬的TLAB避免了多線程沖突,提高對象分配效率。

3. 參數:

    a) “-XX:+UseTLAB”:開啟TLAB。默認已開啟。

4. 對象分配簡要流程。

性能監控工具

top命令

1. top命令:Linux命令,監控CPU、內存、進程。

2. 第1行:

    a) 任務隊列信息。

    b) 依次:系統當前時間、系統運行時間、當前登錄用戶數、平均負載(即任務隊列的平均長度)。

    c) 平均負載的3個值依次:1分鍾、5分鍾、15分鍾到現在平均值。

3. 第2行:

    a) 進程統計信息。

    b) 依次:正在運行的進程數、睡眠進程數、停止的進程數、僵屍進程數。

4. 第3行:

    a) CPU統計信息。

    b) 字段含義。

us

用戶空間CPU占用率

sy

內核空間CPU占用率

ni

用戶進程控件改變過優先級的進程CPU占用率

id

空閑CPU占用率

wa

等待輸入輸出的CPU時間百分比

hi

硬件中斷請求

si

軟件中斷請求

5. 第4行:

    a) 內存信息。

    b) 依次:物理內存總量、已使用物理內存、空間物理內存、內存緩沖表使用量。

6. 第5行:

    a) 交換區信息。

    b) 依次:交換區總量、已使用交換區、空閑交換區、緩沖交換區。

7. 進程信息區:

字段

含義

PID

進程ID

USER

所有者用戶名

PR

優先級

NI

nice,負值為高優先級、正值為低優先級

VIRT

虛擬內存占用總量,單位KBVIRT=SWAP+RES

RES

使用的、未被換出的物理內存,單位KBRES=CODE+DATA

SHR

共享內存,單位KB

%CPU

上次更新到現在的CPU時間占用率

%MEM

物理內存占用率

TIME+

使用的CPU時間總計,單位1/100

COMMAND

命令

vmstat命令

1. vmstat命令:Linux命令,監控CPU、內存、IO。

2. vmstat命令可指定采樣周期、采樣次數。

3. 字段含義。

procs

r

等待運行進程數

b

非中斷睡眠狀態進程數

memory

swpd

虛擬內存,單位KB

free

空閑內存,單位KB

buff

緩存內存,單位KB

swap

si

磁盤交換到內存的交換頁,單位KB/

so

內存交換到內存的交換頁,單位KB/

io

bi

發送到塊設備,單位塊/

bo

從塊設備接收,單位塊/

system

in

每秒中斷數,包括時鍾中斷

cs

每秒上下文切換次數

cpu

us

用戶CPU使用時間

sy

系統CPU使用時間

id

空間CPU時間

iostat命令

1. iostat命令:Linux命令,監控IO。

2. 參數:

    a) “-d”:僅輸出磁盤。

    b) “-x”:輸出詳情。

3. 字段含義。

tps

每秒傳輸次數

kB_read/s

每秒從設備讀取數據量

kB_wrtn/s

每秒向設備寫入數據量

kB_read

讀取總數據量

kB_wrtn

寫入總數據量

pidstat命令

1. pidstat命令:Linux命令,監控進程、線程。

2. 參數:

    a) “-p”:進程ID。

    b) “-u”:監控CPU。

    c) “-r”:監控內存。

    d) “-d”:監控IO。

    e) “-t”:顯示線程。

3. 舉例:

    a) 監控進程CPU。

    b) 監控線程CPU。

    c) 監控內存。

字段

含義

minflt/s

進程每秒minor faults(無需從磁盤調出內存頁)的總數

majflt/s

進程每秒major faults(需從磁盤調出內存頁)的總數

VSZ

虛擬內存,單位KB

RSS

物理內存,單位KB

%MEM

內存占用率

    d) 監控線程IO。

jps命令

1. jps命令:JDK工具,列出Java進程。

2. 參數:

    a) “-m”:顯示main()函數的參數。

    b) “-l”:顯示main()函數所在類的完整包名+類名。

    c) “-v”:顯示傳遞給JVM的參數。

jstat命令

1. jstat命令:JDK工具,查看虛擬機運行信息。

2. 個人看法:該命令參數較多,不如用其他可視化工具代替。

jinfo命令

1. jinfo命令:JDK工具,查看正在運行虛擬機的參數及修改部分參數。

2. 語法:

jinfo <option> <pid>

    option:

    a) -flag <name>:打印指定參數。

    b) -flag [+|-]<name>:設置參數的布爾值。

    c) -flag <name>=<value>:設置參數。

jmap命令

1. jmap命令:JDK工具,生成堆dump文件(即堆快照),及查看堆內對象統計信息。

2. 可使用jhat命令、Visual VM、MAT等工具分析dump文件。

3. 參數:

    a) “-heap”:顯示堆信息。

    b) “-histo[:live]”:顯示對象統計信息,可指定僅統計存活(“:live”子參數)

    c) “-clstats”:ClassLoader統計信息。

    d) “-dump:<dump-options>”:導出堆,子參數有

        i. “live”:僅存活對象;

        ii. “format=b”:二進制格式;

        iii. “file=<file>”:導出文件路徑。

jhat命令

1. jhat命令:JDK工具,分析堆dump文件。

2. 個人看法:使用Visual VM更方便。

3. 用法舉例:

    a) 執行命令;

    b) 訪問http://localhost:7000。

jstack命令

1. jstack命令:JDK工具,查看線程棧(也稱線程dump)。

2. 參數:“-l”附加信息。

jcmd命令

1. jstack命令:JDK 1.7以后的工具,多功能工具,可導出堆、查看進程、查看線程棧、執行GC等。

2. 待補充。

Visual VM工具

1. Visual VM:JDK 1.6 Update 7以后,功能強大的多合一故障診斷和性能監控可視化工具。

2. 代替:jstat、jmap、jhat、jstack等命令,甚至JConsole。

3. 主要功能。

    a) 連接應用程序:本地或JMX遠程。

    b) 監控概況:JVM參數、CPU、內存、堆、線程等。

    c) 線程dump及分析。

    d) 堆dump及分析,支持OQL。

    e) BTrace:不停機狀態,注入字節碼動態監控運行情況。

Mission Control工具

1. Mission Control:JDK 1.7 Update 40以后,診斷工具。

2. 個人看法:雖然貌似比Visual VM好,但Visual VM夠用且市場占有率更高。

解決OOM問題

堆溢出

1. 現象。

2. 2種解決方法。

    a) 增大“-Xmx”參數。

    b) Visual VM工具分析大量占用堆的對象。

直接內存溢出

1. 現象。

2. 2種解決方法。

    a) 顯式GC“System.gc()”。

    b) 不設置“-XX:MaxDirectMemorySize”參數(此時等於“-Xmx”)或增大該值。

過多線程導致溢出

1. 現象。

2. 2種解決方法。

    a) 減小“-Xmx”參數,操作系統可預留更多內存用於創建線程。

    b) 減小“-Xss”參數。

永久區/元數據區溢出

1. 現象。

2. 3中解決方法。

    a) 增大“-XX:MaxPermSize”/“-XX:MaxMetaspaceSize”參數。

    b) 減少需要的類數量。

    c) 使用ClassLoader合理裝載類,並定期回收。

GC效率低導致OOM

1. 現象。

2. 原因:當下列條件都滿足時,虛擬機認為GC效率低下,拋出OOM。

    a) 花在GC上的時間超過98%;

    b) 老年代釋放內存小於2%;

    c) eden區釋放內存小於2%;

    d) 最近連續5次GC都出現上述情況(同時出現,不是出現一個)。

3. 解決方法:該OOM屬輔助作用,提示堆太小,添加“-XX:-UseGCOverheadLimit”參數禁用該功能。

String實現細節

String對象特點

1. 不變性(immutable)。

    a) 解釋:String對象一旦生成,不能再修改。

    b) 好處:多線程共享,並頻繁訪問時,可省略同步和鎖的時間,提高性能。

2. 針對常量池的優化。

    a) 解釋:String對象值相同時,它們只引用常量池的同一個拷貝。

    b) 好處:節省內存。

3. 類的final定義。

    a) 解釋:String類不可能有任何子類。

    b) 好處:保安全性保護。

String常量池

1. 常量池:專門存放String常量的區域。

2. 位置:

    a) JDK 1.6之前,位於永久區;

    b) JDK 1.7以后,位於堆。

字節碼優化

執行字節碼從兩處優化:javac編譯時;通過JIT在運行時。

靜態編譯優化

1. 編譯時計算。

    a) 解釋:如果計算表達式的值能在編譯時確定,則表達式計算提前到編譯階段。

    b) 舉例1:

    c) 舉例2:

2. 變量字符串的連接。

    a) 解釋:變量字符串連接被轉為StringBuilder操作,避免每次字符串操作產生新對象。

    b) 舉例:自動優化前后的代碼。

3. 基於常量的條件語句裁剪。

    a) 解釋:任何邏輯在編譯時就確定時,不需要的邏輯會被裁剪。

    b) 舉例:實際多余的if...else...在編譯器被裁剪。

4. switch語句優化。

    a) 解釋:switch語句可生成tableswitch或loopupswith字節碼指令,前者效率高於后者,前者只能處理case連續的值,后者可處理不連續的值。

    b) 舉例:雖然不連續,但由於離散空間不大,編譯器插入值填充使用tableswitch。

JIT運行優化

1. JIT:Just-In-Time,即時編譯。

2. 目的:避免函數被解釋執行,而是將整個函數體編譯成機器碼,每次只執行編譯編譯后的機器碼,可大幅度提升效率。

3. 虛擬機3種執行模式。

    a) 解釋執行(Interpreted Mode):不做JIT編譯。

    b) 混合模式(Mixed Mode):默認。熱點代碼被編譯執行,其他解釋執行。熱點代碼的判斷依據是調用頻率。

    c) 編譯執行(Compiled Mode):所有函數編譯執行。

    d) 參數。

        i. “-Xint”:解釋。

        ii. “-Xmixed”:混合。

        iii. “-XComp”:編譯。

        iv. “-XX:CompileThreshold”:指定熱點代碼調用次數閥值。Client模式默認1500,Server模式默認10000。

        v. “-XX:+PrintCompilation”:打印即時編譯日志。

4. 多級編譯器。

    a) 客戶端編譯器(C1編譯器):Client模式使用的編譯器,編譯速度快。

    b) 服務端編譯器(C2編譯器):Server模式使用的編譯器,編譯優化多,編譯后代碼質量高,時間長於C1。

    c) 多級目的:在編譯速度和執行效率間取得平衡。

    d) 多級編譯5級:

        i. 0級(解釋執行):解釋執行,不采集性能監控數據;

        ii. 1級(簡單C1編譯):采用C1,最簡單的快速編譯,根據需要采集性能數據;

        iii. 2級(有限的C1編譯):采用C1,更多的優化編譯,可能根據1級采集的性能統計數據,進一步優化編譯代碼;

        iv. 3級(完全C1編譯):完全使用C1的所有功能,采集性能數據進行優化;

        v. 4級(C1編譯):完全使用C1的所有功能,完全優化。

    e) 參數:“-XX:+TieredCompilation”開啟多級編譯。

5. OSR棧上替換。

    a) 背景:函數要么解釋,要么編譯執行。從解釋切換到機器碼執行,如果尚未准備好編譯版本,則解釋執行,下次才機器碼執行。絕大部分場合適用。

    b) 問題:不適合調用次數不多,但函數體內包含大量循環的函數。例如:

    c) OSR:On Stack Replacement棧上替換。不等待函數體運行結束,在循環體就將代碼替換為編譯版本的技術。

6. 方法內聯。

    a) 目的:減少方法調用的次數,提高性能。JIT編譯器默認開啟。

    b) 參數:

        i. “-XX:+Inline”:開啟方法內聯優化。

        ii. “-XX:FreqInlineSize”:內聯體積上限,方法體積大於該值則不內聯。

    c) 舉例:優化前后。

7. 代碼緩存。

    a) 解釋:Code Cache。保存字節碼被編譯后的機器碼,緩存用完后JIT編譯停止,后續未編譯的字節碼解釋執行。代碼緩存的清理也由GC完成。

    b) 參數:“-XX:ReservedCodeCacheSize”指定緩存大小,默認32MB。

 

作者:netoxi
出處:http://www.cnblogs.com/netoxi
本文版權歸作者和博客園共有,歡迎轉載,未經同意須保留此段聲明,且在文章頁面明顯位置給出原文連接。歡迎指正與交流。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM