1. Java對象分配流程
1、如果對象沒有逃逸,且棧空間足夠。則首選打散後棧上分配。
2、1失敗後,嘗試堆中線程專屬內存塊分配。(eden堆中每個線程都有一塊專屬內存,避免多線程同步申請空間)
3、2失敗後,判斷是否觸發條件直接進入老年代。(一般儘量避免這種情況出現)
4、共享eden區分配。
2. 棧上分配
2.1 本質:Java虛擬機提供的一項優化技術。
2.2 基本思想: 將線程私有的對象打散分配在棧上。(打散 = 標量替換,是將一個整體對象,拆分成基本類型屬性存儲)
2.3 優點:
2.3.1 可以在函數調用結束後自行銷燬對象避免GC,不需要垃圾回收器的介入,有效避免垃圾回收帶來的負面影響。
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). |
這裏的 |
|
引起垃圾回收的原因. | 本次GC是因爲年輕代中沒有任何合適的區域能夠存放需要分配的數據結構而觸發的. |
|
在本次GC之前和之後的年輕代內存使用情況. | 本次GC前,年輕代使用空間4095K, GC後年輕代使用空間爲528K |
|
年輕代的總的大小 | |
|
本次GC使用時間(單位:秒) |
2.5.5 JVM參數解析
參數 | 作用 | 備註 |
|
使用server模式 | 只有在server模式下,纔可以棄用逃逸分析 |
|
設置最大堆空間爲15m | 如果在堆上分配,必然觸發大量GC |
|
設初始對空間爲15m | |
|
啓用逃逸分析 | 默認啓用 |
|
關閉逃逸分析 | |
|
打印GC日誌 | |
-XX:-UseTLAB | 關閉TLAB |
TLAB(Thread Local Allocation Buffer) 線程本地分配緩存區 |
|
啓用標量替換,允許對象打散分配到棧上 |
默認啓用 |
|
關閉標量替換 |
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;
}
}