1. Java對象分配流程
2. 棧上分配
2.1 本質:Java虛擬機提供的一項優化技術
2.2 基本思想: 將線程私有的對象打散分配在棧上
2.3 優點:
2.3.1 可以在函數調用結束后自行銷毀對象,不需要垃圾回收器的介入,有效避免垃圾回收帶來的負面影響
2.3.2 棧上分配速度快,提高系統性能
2.4 局限性: 棧空間小,對於大對象無法實現棧上分配
2.4 技術基礎: 逃逸分析
2.4.1 逃逸分析的目的: 判斷對象的作用域是否超出函數體[即:判斷是否逃逸出函數體]
//user的作用域超出了函數setUser的范圍,是逃逸對象 //當函數結束調用時,不會自行銷毀user private User user; public void setUser(){ user = new User(); user.setId(1); user.setName("blueStarWei"); } //u只在函數內部生效,不是逃逸對象 //當函數調用結束,會自行銷毀對象u public void createUser(){ User u = new User(); u.setId(2); u.setName("JVM"); }
2.5 棧上分配示例
package com.blueStarWei.templet; public class AllotOnStack { public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { alloc(); } long end = System.currentTimeMillis(); System.out.println(end - start); } private static void alloc() { User user = new User(); user.setId(1); user.setName("blueStarWei"); } }
2.5.1 上述代碼調用了1億次alloc(),如果是分配到堆上,大概需要1.5GB的堆空間,如果堆空間小於該值,必然會觸發GC。
2.5.2 使用如下參數運行,發現不會觸發GC
-server -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
2.5.3 使用如下參數(任意一行)運行,會發現觸大量GC
//不使用逃逸分析 -server -Xmx15m -Xms15m -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations //不使用標量替換 -server -Xmx15m -Xms15m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:-EliminateAllocations
2.5.3.1 可以發現:棧上分配依賴於逃逸分析和標量替換
2.5.4 GC日志
[GC (Allocation Failure) 4095K->528K(15872K), 0.0025208 secs] [GC (Allocation Failure) 4624K->552K(15872K), 0.0012518 secs] [GC (Allocation Failure) 4648K->608K(15872K), 0.0009262 secs] ......(省略) 3718
2.5.4.1 GC日志解析
參數 | 作用 |
備注 |
GC | 用來區分是 Minor GC 還是 Full GC 的標志(Flag). |
這里的 |
Allocation Failure |
引起垃圾回收的原因. | 本次GC是因為年輕代中沒有任何合適的區域能夠存放需要分配的數據結構而觸發的. |
4095K->528K |
在本次GC之前和之后的年輕代內存使用情況. | 本次GC前,年輕代使用空間4095K, GC后年輕代使用空間為528K |
(15872K) |
年輕代的總的大小 | |
0.0025208 secs |
本次GC使用時間(單位:秒) |
2.5.5 JVM參數解析
參數 | 作用 | 備注 |
-server |
使用server模式 | 只有在server模式下,才可以棄用逃逸分析 |
-Xmx15m |
設置最大堆空間為15m | 如果在堆上分配,必然觸發大量GC |
-Xms15m |
設初始對空間為15m | |
-XX:+DoEscapeAnalysis |
啟用逃逸分析 | 默認啟用 |
-XX:-DoEscapeAnalysis |
關閉逃逸分析 | |
-XX:+PrintGC |
打印GC日志 | |
-XX:-UseTLAB | 關閉TLAB | TLAB(Thread Local Allocation Buffer) 線程本地分配緩存區 |
-XX:+EliminateAllocations |
啟用標量替換,允許對象打散分配到棧上 | 默認啟用 |
-XX:-EliminateAllocations |
關閉標量替換 |
3. TLAB 分配
TLAB,全稱Thread Local Allocation Buffer, 即:線程本地分配緩存。這是一塊線程專用的內存分配區域。TLAB占用的是eden區的空間。在TLAB啟用的情況下(默認開啟),JVM會為每一個線程分配一塊TLAB區域。
3.1 為什么需要TLAB?
這是為了加速對象的分配。由於對象一般分配在堆上,而堆是線程共用的,因此可能會有多個線程在堆上申請空間,而每一次的對象分配都必須線程同步,會使分配的效率下降。考慮到對象分配幾乎是Java中最常用的操作,因此JVM使用了TLAB這樣的線程專有區域來避免多線程沖突,提高對象分配的效率。
3.2 局限性: TLAB空間一般不會太大(占用eden區),所以大對象無法進行TLAB分配,只能直接分配到堆上。
3.3 分配策略:
一個100KB的TLAB區域,如果已經使用了80KB,當需要分配一個30KB的對象時,TLAB是如何分配的呢?
此時,虛擬機有兩種選擇:第一,廢棄當前的TLAB(會浪費20KB的空3.4 間);第二,將這個30KB的對象直接分配到堆上,保留當前TLAB(當有小於20KB的對象請求TLAB分配時可以直接使用該TLAB區域)。
JVM選擇的策略是:在虛擬機內部維護一個叫refill_waste的值,當請求對象大於refill_waste時,會選擇在堆中分配,反之,則會廢棄當前TLAB,新建TLAB來分配新對象。
【默認情況下,TLAB和refill_waste都是會在運行時不斷調整的,使系統的運行狀態達到最優。】
3.4 JVM參數解析
參數 | 作用 | 備注 |
-XX:+UseTLAB | 啟用TLAB | 默認啟用 |
-XX:TLABRefillWasteFraction | 設置允許空間浪費的比例 | 默認值:64,即:使用1/64的TLAB空間大小作為refill_waste值 |
-XX:-ResizeTLAB | 禁止系統自動調整TLAB大小 | |
-XX:TLABSize | 指定TLAB大小 | 單位:B |
4. 附件
4.1 User類
packagepackag com.blueStarWei.templet; public class User { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
5. 參考文獻
5.1 《實戰Java虛擬機 - JVM故障診斷與性能優化》
5.2 棧上分配、TLAB : https://blog.csdn.net/yangsnow_rain_wind/article/details/80434323
5.3 快速解讀GC日志 : https://blog.csdn.net/renfufei/article/details/49230943