java虚拟机 jvm 栈数据区

时间:2022-12-29 09:42:16

java栈帧还是需要一些数据支持常量池的解析、正常方法的返回和异常的处理。大部分的java字节码指令需要进行常量池的访问,在栈帧数据区中保存着访问常量池的指针,方便程序访问java常量池。如下图所示:

 java虚拟机 jvm 栈数据区

java虚拟机 jvm 栈数据区

当函数返回或者程序出现异常的时候,jvm虚拟机必须恢复调用者函数的栈帧,并且让调用者函数继续执行。什么意思呢?举一个通俗的例子:

a()调用b()b()返回的时候肯定继续让a()继续执行对吧。b()抛出异常的时候a()肯定也需要处理对吧。

对于异常的处理,jvm是如何处理的呢?虚拟机肯定必须有一个异常处理表,方便发生异常的时候找到处理异常的代码,方便程序继续运转,因此异常处理表Exception table也是帧数据区中很重要的一部分。怎么获取Exception table 中的信息呢操作如下:

执行命令

javap -verbose命令输出它的字节码(不懂这个命令的可以javap -help查看)

from   to  target type  

     0     8     19   any

表示在字节码 0-8个字节可能抛出异常,如果遇到异常,则跳转到字节码偏移量19出执行,当方法抛出异常的时候,虚拟机就会查找类似的异常表进行处理,如果无法找到异常表中对应的信息,则会结束当前程序的调用,返回调用函数,并在调用函数中抛出这个异常信息,并查找函数的异常表进行处理。

通俗易懂的说:

a()调用b()  b()方法中有异常的时候,b()中处理了则继续执行,程序没有处理也就没有异常表信息直接抛异常到a()方法,b()栈移除,a()处理了继续流转,没有处理直接异常抛出。

1.1.1. 栈上分配

栈上分配是java虚拟机提供的一种优化技术,基本思想是,对于那些线程私有对象(不可能被其他线程访问的对象),可以将他们打散分配到栈上,而不是分配在堆上,(是不是很毁三观啊,是不是以前认识的都不对啊白着急)。分配在栈上面的好处函数调用完成后直接自杀销毁,而不需要垃圾回收机制介入回收,所以这样对于性能的提升还是蛮有帮助的。

 

栈上分配有一个前提条件:开启逃逸分析(必须否则不会栈上分配),逃逸分析的目的就是判断对象的作用域是否有可能逃逸出函数体。如下代码:

private static Useruser;

public static void alloc(){

user=new User();

user.setId(5);

user.setName("springok");

}

对象User user 因为是static  方法中因此可以被任何线程访问到。所以属于逃逸对象。

下面的代码显示了一个非逃逸对象:

public static void alloc(){

 User user;

user=new User();

user.setId(5);

user.setName("springok");

}

因为不可以被其他的线程访问到。对象user以局部变量形式存在,该对象没有被返回出去,也没有任何地方可以访问到在其他的方法中,因此是非逃逸的,所以虚拟机可能将user分配在栈上。下面的代码对其结论进行证明:

 

public static void alloc() {

User user = new User();

user.setId(5);

user.setName("springok");

}

 

public static void main(String[]args) {

long begin = System.currentTimeMillis();

 

for (int i = 0;i < 100000000;i++) {

alloc();

}

long end = System.currentTimeMillis();

System.out.println(end-begin);

}

上面代码进行一亿次调用进行对象的创建,累计分配的内存可能达到了1.5G,如果堆空间小于这个1.5G肯定会GC垃圾回收,所以我们开启垃圾回收的参数。

运行配置的参数如下:

-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-UseTLAB -XX:+EliminateAllocations

jvm参数使用说明:

-server  因为在server模式才能启用逃逸分析。

-XX:+DoEscapeAnalysis 启用逃逸分析

-Xmx10m 堆空间最大10MB.如果对象在堆上创建肯定会GC垃圾回收的。

-XX:+PrintGC 打印GC日志。

-XX:+EliminateAllocations 开启标量替换默认就是开启的,允许对象打散分配到栈上。

user对象拥有的idname属性将会被视为独立的局部变量分配。

-XX:-UseTLAB 关闭TLAB

程序执行后完整的打印如下:

5

可以看到没有任何形式的GC输出,程序执行完毕了。说明user对象确实在分配过程被优化了。

如果关闭逃逸分析或者标量替换任意一个,再次执行程序,就会看到大量的GC日志如下:

[GC 3226K->474K(9920K), 0.0001595 secs]

[GC 3226K->474K(9920K), 0.0001488 secs]

[GC 3226K->474K(9920K), 0.0001726 secs]

[GC 3226K->474K(9920K), 0.0001614 secs]

1556

代码侧面印证了结论的正确性。

第一说明了上面的结论,栈上分配依赖逃逸分析和标量替换的实现,第二确实开启了对象被分配在栈上,而不是堆上。因为在堆上肯定会GC垃圾回收的。

对于大量零散的小对象,栈上分配提供了很好的优化策略,栈上分配速度快,可以避免垃圾回收。但由于和堆相比栈的空间小,因此大对象不适合在栈上分配。