局部变量表:保存函数的参数以及局部变量用的,局部变量表中的变量只在当前函数调用中有效,当函数调用结束后,随着函数栈帧的销毁,局部变量表也会随之销毁。
操作数栈:主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。只支持出栈入栈操作。
帧数据区:栈帧需要一些数据来支持常量池解析、正常方法返回和异常处理等。在帧数据区中保存着访问常量池的指针,方便程序访问常量池。
此外,当函数返回或者出现异常时,虚拟机必须恢复调用者函数的栈帧,并让调用者函数继续执行下去。
对于异常处理,虚拟机必须有一个异常处理表,方便在发生异常的时候找到处理异常的代码,因此异常处理表也是帧数据区中重要的一部分
栈上分配:优化技术
基本思想:对于那些线程私有的对象,可以将它们打散分配在栈上,而不是分配在堆上。
分配在栈上的好处在于函数调用结束后可以自行销毁,而不需要垃圾回收器的介入,从而提高系统的性能。
技术基础:逃逸分析
逃逸分析的目的是,判断对象的作用域是否有可能逃逸出函数体
/**
* Created by wb-xxd249566 on 2017/3/31.
*/
public class EscapeAnalysis {
private static User u;
public static void alloc(){
u = new User();
u.id=5;
u.name="xuexd";
}
}
对象User u是类的成员变量,该字段有可能被任何线程访问,因此属于逃逸对象。
/**
* Created by wb-xxd249566 on 2017/3/31.
*/
public class EscapeAnalysis {
public static void alloc(){
User u = new User();
u.id=5;
u.name="xuexd";
}
}
在此处,User以局部变量的形式存在,并且该对象并没有被alloc()函数返回,或者出现了任何形式的公开,因此它未发生逃逸。
对于这种情况,虚拟机就有可能将User分配在栈上,而不是堆上。
/**
* Created by wb-xxd249566 on 2017/3/31.
*/
public class OnStackTest {
public static class User{
public int id = 0;
public String name = "";
}
public static void alloc(){
User u = new User();
u.id = 5;
u.name = "xuexd";
}
public static void main(String[] args) throws InterruptedException{
long b = System.currentTimeMillis();
for (int i=0;i<100000000;i++){
alloc();
}
long e = System.currentTimeMillis();
System.out.println(e-b);
}
}
运行使用参数:-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations
-server 在Server模式下,才可以启用逃逸分析
-XX:+DoEscapeAnalysis 启用逃逸分析技术
-Xmx10m -Xms10m 指定堆空间最大为10MB,显然,如果对象在堆上分配,必然会引起大量的GC
-XX:+EliminateAllocations 开启标量替换(默认打开),允许对象打散分配在栈上,比如对象拥有id和name俩个字段,那么这俩个字段将会被视为俩个独立的局部变量进行分配
-XX:-UseTLAB 关闭TLAB
只有一次GC输出,程序就执行完毕了,说明在执行过程中,User对象的分配过程被优化了。
将参数设为-XX:-EliminateAllocations,即关闭标量替换后,则疯狂打印GC:
栈上分配依赖逃逸分析和标量替换的实现。
对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效避免垃圾回收带来的负面影响。
但由于和堆空间相比,栈空间较小,因此对于大对象无法也不适合在栈上分配。