1. Java对象分配流程
1、如果对象没有逃逸,且栈空间足够。则首选打散后栈上分配。
2、1失败后,尝试堆中线程专属内存块分配。(eden堆中每个线程都有一块专属内存,避免多线程同步申请空间)
3、2失败后,判断是否触发条件直接进入老年代。(一般尽量避免这种情况出现)
4、共享eden区分配。
2. 栈上分配
2.1 本质:Java虚拟机提供的一项优化技术。
线程私有的对象打散分配在栈上。(打散 = 标量替换,是将一个整体对象,拆分成基本类型属性存储)
2.3 优点:
自行销毁对象避免GC,不需要垃圾回收器的介入,有效避免垃圾回收带来的负面影响。
速度很快,提高系统性能。
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.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占用的是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;
}
}