本文是何适 JVM 修仙系列第 4 篇,文末有本系列文章汇总。
当面试官问你对象都分配哪里,你把 JVM 内存结构介绍一下然后说分配在堆上,没啥问题,给你打 8 分。如果你还能聊一聊栈上分配,一定是加分项,我想面试官会考虑给你 10 分。
是什么
将线程私有的不可能被其他线程访问的对象打散分配在栈上,而不是分配在堆上。打散分配意思是将对象的不同属性分别分配给不同的局部变量。
好处
缺点
栈空间较小,大对象不适合在栈上分配。
如何判断对象是线程私有的呢,就要通过逃逸分析。逃逸指的是逃出当前线程,所以逃逸对象就是可以被其他线程访问的对象,非逃逸对象就是线程私有的对象。
举例:逃逸对象和非逃逸对象
public class StackTest {
public static User user1;
public static void runAway1() {
user1 = new User();// 逃逸对象
}
public static void runAway2() {
User user2 = new User();// 非逃逸对象
}
}
举例:非逃逸对象栈上分配
public class StackTest {
public static User user1;
public static void runAway1() {
user1 = new User();// 逃逸对象
user1.id = 1;
user1.name = "user";
}
public static void runAway2() {
User user2 = new User();// 非逃逸对象
user2.id = 2;
user2.name = "user";
}
public static void main(String[] args) {
for (int i = 0; i <= 99999999; i++) {
// runAway1();
runAway2();
}
}
}
class User {
int id;
String name;
}
执行代码时设置如下参数:
-server -Xss128K -Xmx100m -Xms100m -XX:+PrintGC
-server:server模式下才能设置栈上分配
-Xss128K:栈最大内存
-Xmx100m:堆最大内存
-XX:+PrintGC:打印GC日志
代码中创建一个 User 对象需要 16 字节内存,循环 1 亿次,大概需要 1.5GB 空间。
循环执行runAway1();
1 亿次,堆内存 100M 小于 1.5G,所以会发生 GC,打印很多 GC 日志。
[GC (Allocation Failure) 25600K->800K(98304K), 0.0009909 secs]
[GC (Allocation Failure) 26400K->832K(98304K), 0.0006649 secs]
[GC (Allocation Failure) 26432K->776K(98304K), 0.0006009 secs]
[GC (Allocation Failure) 26376K->816K(98304K), 0.0009932 secs]
[GC (Allocation Failure) 26416K->696K(98304K), 0.0009381 secs]
...
循环执行runAway2();
1 亿次,由于对象 user2 是线程私有的逃逸对象,执行一次 runAway2()创建一个 User 对象,一次 runAway2()执行完随着局部变量销毁,user2 对象也就销毁了,所以下次执行 runAway2()再创建 user2 对象时还有原来大小的栈空间。循环执行,不会有内存不够的问题,当然也就不会打印出 GC 日志。
【原创】JVM 系列 01 | 开篇 【原创】JVM 系列 02 | Java 虚拟机结构 【原创】JVM 系列 03 | Java 栈—方法是如何调用的?