目錄 · 初步認識 · Java里程碑(關鍵部分) · 理解虛擬機 · Java虛擬機種類 · Java語言規範 · Java虛擬機規範 · 基本結構 · Java堆(Heap) · Java棧(Stacks) · 方法區(Method Area) · 直接記憶體(Direct Memory) · 本 ...
目錄
· 初步認識
· 理解虛擬機
· Java語言規範
· 基本結構
· 常用參數
· 設置參數
· 查看參數
· 跟蹤垃圾回收
· 跟蹤類載入/卸載
· 設置堆分佈
· 處理堆溢出
· 配置方法區
· 配置棧
· 配置直接記憶體
· 配置工作模式
· 垃圾回收演算法
· 垃圾回收
· 分代演算法(Generational Collecting)
· 判斷可觸及性
· 停頓現象
· 垃圾回收器
· 串列回收器
· CMS回收器
· G1回收器
· 垃圾回收其他細節
· TLAB
· 性能監控工具
· top命令
· vmstat命令
· iostat命令
· jps命令
· jstat命令
· jinfo命令
· jmap命令
· jhat命令
· jstack命令
· jcmd命令
· 解決OOM問題
· 堆溢出
· 直接記憶體溢出
· 過多線程導致溢出
· 位元組碼優化
· 靜態編譯優化
· 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) 欄位含義。