专栏名称: ImportNew
伯乐在线旗下账号,专注Java技术分享,包括Java基础技术、进阶技能、架构设计和Java技术领域动态等。
目录
相关文章推荐
Java编程精选  ·  Spring Boot + MDC ... ·  2 天前  
Java编程精选  ·  前美团员工自曝:35岁被裁后gap一年多,身 ... ·  5 天前  
芋道源码  ·  极简版Java敏感词检测工具 ·  6 天前  
51好读  ›  专栏  ›  ImportNew

Java 虚拟机 JVM 局部变量表实战

ImportNew  · 公众号  · Java  · 2016-12-29 21:37

正文

(点击上方公众号,可快速关注)


来源:分享牛

链接:blog.csdn.net/qq_30739519/article/details/51043512

如有好文章投稿,请点击 → 这里了解详情


Java局部变量表是栈帧重要组中部分之一。他主要保存函数的参数以及局部的变量信息。局部变量表中的变量作用域是当前调用的函数。函数调用结束后,随着函数栈帧的销毁。局部变量表也会随之销毁,释放空间。


由于局部变量表存在栈帧中。所以,如果函数参数和局部变量比较多,会使的局部变量表膨胀,每一次调用会占用更多的栈空间。最终结局就是栈空间内存一定的情况下调用的次数减少。


1.1.1. 局部变量表变量影响


下面的代码演示在栈空间内存一定的情况下,参数以及局部变量的大小对函数调用次数的影响。第一个函数recursion()不包含任何的参数和局部变量,第二个函数recursion()包含3个参数和4个局部变量,因此我们也可以算出局部变量表中包含了13个变量信息。第一个局部变量表拥有更深的调用层次。代码如下:


private static int count=0;  

public static void recursion(int a,int b,int c){  

long l1=12;  

short sl=1;  

byte b1=1;  

String s="1";  

System.out.println("count="+count);  

count++;  

recursion(1,2,3);  

}  

public static void recursion(){  

System.out.println("count="+count);  

count++;  

recursion();  

}


使用jvm参数-Xss128K执行上面第一个无参的recursion()函数,输出结果如下:


count=4495 

Exception in thread "main" java.lang.StackOverflowError  

at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)


使用jvm参数-Xss128K执行上面第二个个有参的recursion()函数,输出结果如下:


count=3865 

Exception in thread "main" java.lang.StackOverflowError  

at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)  

at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564)  

at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619)


可以得出结论:


在同等的栈容量下,局部变量少的函数可以支持更深的函数调用。调用次数也就越多。


如何证明结论是正确的呢?在这里我们借助jclasslib工具来查看局部变量表的局部变量信息。


下图显示了无参的recursion()函数最大的局部变量表大小为0个字。有参的recursion()函数最大的局部变量表大小为8个字。在这里说明一下:


int、short、byte、对象引用等占用一个字。long和double在局部变量中需要占用2个字。


(字 一个字在32位计算机中为4个字节的长度)


我们来算一下recursion(1,2,3);方法局部变量表的大小。


三个参数为int 所以 是3


long l1=12; 2


short sl=1; 1


byte b1=1; 1


String s=”1″; 1


所以说 一共是8个字。


需要强调的一点是这里说的局部变量表指的是java栈空间的一部分,不要跟下面说的classs字节码中的局部变量表混淆。




下面展示class字节码中的局部变量表的内容:



从图中看以看出来一些信息:


在Class文件的局部变量表定义中,显示了每一个局部变量的作用范围、所在槽位的索引信息(index列信息),


变量的名称(name列)和数据类型信息(descriptor列)


数据类型信息映射:


I—int类型


D–double类型


B-byte类型


Ljava/lange/Integer –Integer类型


Ljava/lange/String–String类型


S–short类型


栈帧中的局部变量的槽位是可以重复使用的。如果一个声明的变量过了其作用域,那么其作用域之后申请的变量有可能复用过期的局部变量的槽位,从而能够达到节省资源目的。


1.1.2. 局部变量表槽位的复用


下面得代码显示了局部变量表槽位的复用。localVar1()函数中,局部变量a和b得范围都是到了函数的末尾所以b是没有办法复用a的卡槽所在的位置。localVar2()函数中,局部变量a在}之后不在有效了,所以b是可以复用a的卡槽的位置都是int类型所以是1个字。程序如下所示:


public void localVar1(){

 

int a=0;

 

System.out.println(a);

 

int b=0;

 

}

 

public void localVar2(){

 

{

 

int a=0;

 

System.out.println(a);

 

}

 

int b=0;

 

}


使用jclasslib工具来查看局部变量表的局部变量localVar1()信息如下图:




该函数最大的局部变量大小3个字,卡槽0位为thsi引用(实例方法的第一个局部变量都是this),第一个卡槽位变量为a,第二个卡槽位变量为b,每个变量是1个字所以一共是三个字。


使用jclasslib工具来查看局部变量表的局部变量localVar2()信息如下图:




该函数最大的局部变量大小2个字,卡槽0位为thsi引用(实例方法的第一个局部变量都是this),第一个卡槽位变量为a,第二个卡槽位变量为b,每个变量是1个字 但是b变量复用了a卡槽位所以一共是2个字。


局部变量表也是作为垃圾回收gc的重要参考点,只要被局部变量表中直接或者间接引用的对象都不会被回收。所以必须要理解局部变量表才能理解gc回收机制。


下面的主要讲解说明局部变量对垃圾回收的影响。


1.1.3. 局部变量对垃圾回收的影响


程序代码如下所示:


jvm参数-XX:+PrintGC参数 垃圾回收前后堆得大小


JvmTestLocalVarGc t=new JvmTestLocalVarGc();


t.localvarGc1();


结果输出:[Full GC 3875K->3546K(15872K), 0.0050719 secs]


在申请空间后,立即调用GC垃圾回收,很明显,由于byte被b强引用所以无法回收这块空间。


JvmTestLocalVarGc t=new JvmTestLocalVarGc();


t.localvarGc2();


结果输出:[Full GC 3875K->474K(15872K), 0.0036066 secs]


在垃圾回收前,现将b设置为null,使byte数组拾取引用,所以GC后byte数组被直接垃圾回收了。


JvmTestLocalVarGc t=new JvmTestLocalVarGc();


t.localvarGc3();


结果输出:[Full GC 3875K->3546K(15872K), 0.0069622 secs]


在进行垃圾回收前,先使局部变量b实现,虽然b离开了作用域,但是变量b亦然存放在局部变量表中。并且指向byte数组,故byte数组亦然没有被回收。


JvmTestLocalVarGc t=new JvmTestLocalVarGc();


t.localvarGc4();


结果输出:[Full GC 3875K->474K(15872K), 0.0037666 secs]


在垃圾回收前,不仅是b失效了,c复用了变量b的字,由于b被销毁,所以byte数组被销毁了。


JvmTestLocalVarGc t=new JvmTestLocalVarGc();


t.localvarGc5();


结果输出:[Full GC 3875K->3546K(15872K), 0.0054367 secs]


[Full GC 3546K->474K(15936K), 0.0036164 secs]


对于localvarGc5()调用localvarGc1()方法,很明显localvarGc1()中没有回收byte数组,但在其返回后他的栈帧被销毁了,自然栈帧中所有的局部变量也没销毁了,容器没了,值当然也不存在了嘛。


所以byte数组失去饮用。在localvarGc5()中被回收了。


觉得本文对你有帮助?请分享给更多人

关注「ImportNew」,看技术干货