JVM学习之:虚拟机中的运行时栈帧总结(一)

  每 个人都知道,各种各样的动画视频,都是由一帧一帧图片连续切换结果的结果而产生的,其实虚拟机的运行和动画也类似,每个在虚拟机中运行的程序也是由许多的 帧的切换产生的结果,只是这些帧里面存放的是方法的局部变量,操作数栈,动态链接,方法返回地址和一些额外的附加信息组成,在虚拟机中包含这些信息的帧称 为“栈帧”,每个方法的执行,在虚拟机中都是对应的栈帧在虚拟机栈中的入栈到出栈的过程其中比较重要的一点时,如果虚拟机中同时有多个线程在执行,那么各个线程的栈帧都是相互独立,互不侵犯的,所以这也导致了,局部变量在多线程的环境下也是线程安全的

          一个方法的调用链可能会很长,于是当调用一个方法时,可能会有很多的方法都处于执行状态,但是对于执行引擎来讲,至于位于虚拟机栈顶的栈帧才是有效的,这个栈帧被称为 当前栈 这个栈帧所关联的方法称为当前方法,执行引擎的所有指令都是针对当前栈帧进行操作的。

       前面已经提到一个栈帧包括局部变量表,操作数栈,动态链接,方法返回地址和一些额外的附加信息组成,接下来对各个部分做一个简单的介绍。

(一)局部变量表

通过名字可以看出这个里面放的都是局部变量,例如方法参数,方法内部定义的局部变量。一般情况下,在 java 程序被编译为 class 文件的时候这个表的容量最大值就已经确定下来,是存在方法的 Code 属性的 Max_locals 数据项中

在局部变量表中 Slot 时最小的存储单位,虚拟机规范并没有明确指明一个 Slot 为多少位, Slot 具体的大小也会随着操作系统和虚拟机的不同而不同,一般情况下可以当成时 32 位来看待,但是规定了一个 Slot 必须可以存放 boolean,byte,char,int,float,reference (可能 32 位也可能时 64 位) ,returnAddress. 而对于在虚拟机规范中被明确定义位 64 位的 Long Double 而言,需要用两个连续的 Slot 来存放,由于时连个 Slot 来存储,所以在对 Long Double 进行操作的时候就会存在原子性的问题,不过虚拟机会对它作出原子性保证(因为每个线程之间的栈帧是相互独立的,所以也不会由线程安全的问题)。

既然局部变量中存放了很多的局部变量,那么怎么来访问每个变量了?虚拟机规范中指出,虚拟机会利用索引编号的递增来对局部变量表中定义的变量进行依次访问(从 0 开始),而对于实例方法(非 static 方法),其局部变量表的第 0 个索引就是我们熟悉的 this, 这也是为什么在实例方法中我们可以使用 this.name.... 的原因。

下面来谈谈 Slot 对虚拟机的垃圾回收的影响。由于在一个方法中,某个方法内的局部变量的作用范围也不一定可以覆盖整个方法,这就可能导致 Slot 资源的浪费,如果这个 Slot 对应的资源足够的大,那么 Slot 对资源的浪费也就可能会影响到整个虚拟机栈的使用,为了解决这个问题,虚拟机规范中规定了 Slot 的可重用性,即当一个方法中的某个局部变量超出了变量

  的有效范围时,那么那个变量的Slot 可以被另外一个局部变量来使用。被重用的 Slot 便 失去了和原来堆中实例的联系,这样堆中的实例便可以被垃圾回收器回收,当然一般情况下这些辅助的操作可能对系统性能的提升由很小的影响,但是,如果在那个 局部变量“过期”之后还有很多的代码要执行,或者说后面由比较耗时的操作,而且在变量过期前,已经消耗了比较多的系统资源,那么这个辅助动作可能就非常有 用了。

下面将通过三个例子来说明重用 Slot 对垃圾回收带来的好处

package com.eric.jvm.engineer;


public class SlotTest {

        /**

         * 
主要验证重复利用


Slot
对于垃圾回收的帮助 



         ×



1
)运行参数:


-verbose:gc -XX:+PrintGCDetails

         * 



2



64M
的对象大于了目前年轻代的空间,根据大对象直接进入老年代的原则,在观察结果的时候需要关注


ParOldGen

         * */


        public static int M = 1024 << 10;


        public static void main(String[] args) {

                new SlotTest().test2();

        }


        /*

         * replace 
在执行


gc
操作的时候还没有超过它的作用域,也就是堆中还有实例和它直接关联所以不会被回收掉



         * 

         * [GC [PSYoungGen: 614K->352K(17856K)] 66150K->65888K(124224K),

         * 0.0024710 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC

         * (System) [PSYoungGen: 352K->0K(17856K)] [ParOldGen:

         * 65536K->65759K(106368K)] 65888K->65759K(124224K) [PSPermGen:

         * 2403K->2401K(21248K)], 0.0102720 secs] [Times: user=0.02 sys=0.00,

         * real=0.01 secs]

         */

        public void test1() {

                // 64M

                byte[] replace = new byte[M << 6];

                System.gc();

        }


        /*

         * 
在执行


gc
时,虽然


replace
已经过期,但是由于它的


Slot
中仍然存有相关的局部变量信息,所以


gc 
还是不可以 对


64M
的内存进行回收



         * 

         * [GC [PSYoungGen: 614K->288K(17856K)] 66150K->65824K(124224K),

         * 0.0019600 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] [Full GC

         * (System) [PSYoungGen: 288K->0K(17856K)] [ParOldGen:

         * 65536K->65758K(106368K)] 65824K->65758K(124224K) [PSPermGen:

         * 2403K->2401K(21248K)], 0.0139210 secs] [Times: user=0.02 sys=0.00,

         * real=0.01 secs]

         */

        public void test2() {

                {

                        byte[] replace = new byte[M << 6];

                }

                System.gc();

        }


        /*
在执行


gc
之前,由于


a
复用了


replace 



Slot
,所以此时可以认为


replace
在堆中的实例没有相关的引用,因此在


gc
的时候会将它回收



         * [GC [PSYoungGen: 614K->368K(17856K)] 66150K->65904K(124224K),

         * 0.0019430 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC

         * (System) [PSYoungGen: 368K->0K(17856K)] [ParOldGen:

         * 65536K->223K(106368K)] 65904K->223K(124224K) [PSPermGen:

         * 2403K->2401K(21248K)], 0.0107030 secs] [Times: user=0.01 sys=0.01,

         * real=0.01 secs]

         */

        public void test3() {

                {

                        byte[] replace = new byte[M << 6];

                }

                int a = 0;

                System.gc();

        }


}


对于上面代码中的 test3(), 也可以用 replace=null 来达到同样的效果。但是由于赋 null 值的操作在经过虚拟机 JIT 编译优化之后就会被消除掉,所以在这种情况下设置 null 值是没有意义的,其实就是 test3() 中的做法也是在特殊的情况下才会考虑的做法(后续的方法执行比较耗资源和时间,且前面的操作已经消耗了过多的资源),一般情况下只需要正确的保证每个局部变量有正确的变量作用域就可以了


最后要说明的是,由于局部变量不像实例变量或类变量那样会在准备阶段或者或者初始化阶段对其进行赋值,所以局部变量在没有赋值的情况下是不可以使用的,如果出现下面的情况,那么编译的时候就会提示“局部变量没有赋值”

        public void test4(){

                int a;

                System.out.println(a);

        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值