new对象时,堆会发生抢占吗
答:会
new对象时。指针会向右移动一个对象大小的距离,假如一个线程A正在给字符串对象S分配内存。另外一个线程B同时为ArrayList对象1分配内存,两个线程就发生了抢占。
JVM怎么解决堆内存分配的竞争问题?
为了解决堆内存分配的抢占问题,JVM为每个线程保留了一小块的内存空间,被称为TLAB也就是线程本地分配缓冲区。用于存放该线程分配的对象。
当线程需要分配对象时,直接从TLAB中分配,只有当TLAB用尽或者对象太大需要直接在堆中分配时,才会使用全局分配指针。
这⾥简单测试⼀下 TLAB。
可以通过 java -XX:+PrintFlagsFinal -version | grep TLAB 命令查看当前 JVM 是否开启了 TLAB。
如果开启了 TLAB,会看到类似以下的输出,其中 bool UseTLAB 的值为 true。
我们编写⼀个简单的测试类,创建⼤量对象并强制触发垃圾回收,查看 TLAB 的使⽤情况。
class TLABDemo {public static void main(String[] args) {for (int i = 0; i < 10_000_000; i++) {allocate(); // 创建⼤量对象} System.gc(); // 强制触发垃圾回收}private static void allocate() {// ⼩对象分配,通常会使⽤ TLABbyte[] bytes = new byte[64];}
}
在 VM 参数中添加 -XX:+UseTLAB -XX:+PrintTLAB -XX:+PrintGCDetails -XX:+PrintGCDateStamps ,运⾏后可以看到这样的内容:
waste:未使⽤的 TLAB 空间。
alloc:分配到 TLAB 的空间。
refills:TLAB 被重新填充的次数。
可以看到,当前线程的 TLAB ⽬标⼤⼩为 10,496 KB( desired_size: 10496KB );未发⽣慢分配( slow allocs: 0 );分配效率直接拉满( alloc: 1.00000 52494KB )。
当使⽤ -XX:-UseTLAB -XX:+PrintGCDetails 关闭 TLAB 时,会看到类似以下的输出:
直接出现了两次 GC,因为没有 TLAB,Eden 区更快被填满,导致年轻代 GC。年轻代 GC 频繁触发,⼀部分⻓⽣命周期对象被晋升到⽼年代,间接导致⽼年代 GC 触发。