`
grzrt
  • 浏览: 182310 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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

    博客分类:
  • JAVA
阅读更多

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

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

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

(一)局部变量表

通过名字可以看出这个里面放的都是局部变量,例如方法参数,方法内部定义的局部变量。一般情况下,在 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);

        }
分享到:
评论

相关推荐

    深入理解Java虚拟机视频教程(jvm性能调优+内存模型+虚拟机原理)视频教程

    第95节运行时栈帧结构00:08:46分钟 | 第96节局部变量表00:20:48分钟 | 第97节操作数栈00:08:36分钟 | 第98节动态连接00:02:56分钟 | 第99节方法返回地址和附加信息00:03:24分钟 | 第100节方法调用-解析调用00:...

    Java虚拟机(JVM)面试题(总结最全面的面试题!!!)

    Java虚拟机(JVM)面试题(总结最全面的面试题!!!) 文章目录Java内存模型我们开发人员编写的Java代码是怎么让电脑认识的为什么说java是跨平台语言Jdk和Jre和JVM的区别说一下 JVM由那些部分组成,运行流程是什么...

    深入理解JVM内存结构及运行原理全套视频加资料.txt

    包括JVM执行过程、虚拟机类加载机制、运行时数据区、GC、类加载器、内存分配与回收策略等,全套视频加资料高清无密码  第1讲 说在前面的话 免费 00:05:07  第2讲 整个部分要讲的内容说明 免费 00:06:58  第3讲...

    最新java面试专题01-JVM

    最新jvm面试题合集,涵盖JVM运行时数据区、垃圾回收算法、垃圾回收器、类加载机制、JIT即时编译等核心知识点及常见面试题,一书在手,天下我有。 JVM内存结构:JVM的内存结构主要包括堆内存、方法区、栈(包括Java...

    深入Java虚拟机(原书第2版).pdf【附光盘内容】

    2.3.4 非标准运行时库 2.3.5 对虚拟机的依赖 2.3.6 对用户界面的依赖 2.3.7 java平台实现中的bug 2.3.8 测试 2.4 平台无关的七个步骤 2.5 平台无关性的策略 2.6 平台无关性和网络移动对象 2.7...

    深入理解_Java_虚拟机 JVM_高级特性与最佳实践

    / 189 7.4.1 类与类加载器 / 189 7.4.2 双亲委派模型 / 191 7.4.3 破坏双亲委派模型 / 194 7.5 本章小结 / 197 第8章 虚拟机字节码执行引擎 / 198 8.1 概述 / 198 8.2 运行时栈帧结构 / 199 8.2.1 局部变量...

    JVM学习笔记一(线程私有的内存区域)

    虚拟机栈在线程运行时,每执行一个方法,都会对应生成一个栈帧,放入栈中。每个时刻正在运行的方法是虚拟机栈顶部的栈帧,方法的执行就是一个出栈与入栈的操作。虚拟机栈的大小默认为1M,可以使用参数-Xss来进行调整...

    Java虚拟机.docx

    2.虚拟机栈:线程运行需要的内存空间,每个方法调用对应一个栈帧(每个方法运行时需要的内存),处于栈顶部的栈帧称为活动栈帧。 (1)垃圾回收是否涉及栈内存:不会,因为栈内存调用完成之后会弹出栈 (2)栈内容分配越...

    JVM教程吐血整理干货.md

    JVM运行时内存分区 程序计数器 程序计数器的特点 Java虚拟机栈 栈帧 局部变量表 操作数栈 动态连接 方法出口 本地方法栈 堆 方法区 JavaVirtualMachineError StackOverflowError OutOfMemoryError JVM PS:JVM部分...

    Java虚拟机规范.Java SE 8版

    第2章概述Java虚拟机的整体架构,包括class文件格式、数据类型、原始类型、引用类型、运行时数据区、栈帧、浮点算法、异常等,这对理解本书后面的内容有重要帮助;第3章详述如何将Java语言编写的程序转换为Java...

    JVM实战高手.zip

    而 Java 堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期才知道那些对象会创建,这部分内存的分配和回收都是动态的,垃圾...

    Java虚拟机

    8.2 运行时栈帧结构 8.2.1 局部变量表 8.2.2 操作数栈 8.2.3 动态连接 8.2.4 方法返回地址 8.2.5 附加信息 8.3 方法调用 8.3.1 解析 8.3.2 分派 8.3.3 动态类型语言支持 8.4 基于栈的字节码解释执行引擎 ...

    Java常见面试问题整理.docx

    直接内存:不受JVM GC管理,直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/...

    java8rt.jar源码-JVM:学习JVM

    方法从调用到执行完成的过程中,就对应这一个栈帧在虚拟机中入栈到出栈的过程。 局部变量表:存放了编译器可知的8大基本类型和应用类型,其中Long和double是64位的,会占用两个局部变量空间,其余的数据类型只会占用...

    JAVA面试冲刺—通过代码深入理解JVM—(未完待续)

    深入理解JVM一、什么是JVM二、JAVA的运行机制三、JVM架构图四、类加载器子系统1、类加载器子系统作用2、加载(Loading)3、链接(Linking)3.1 验证 (Verify)3.2 准备(Prepare)3.3 解析(Resolve)3、初始化4、...

    高级java开发并发问题

    线程池:一个管理线程的池子。 #为什么平时都是使用线程池创建线程,直接new一个线程不好吗? 嗯,手动创建线程有两个缺点 1.不受控风险 2.频繁创建开销大 为什么不受控? 系统资源有限,每个人针对不同业务都可以...

    JVM内存模型及垃圾回收

    通俗的来讲,jvm主要分为5个部分 程序计数器、虚拟机枝、本地方法枝、 Java 堆、 方法区, 引用大佬总结的概括程序计数器用于存放下一条运行的指令,虚拟机栈和本地方法栈用于存放函数调用堆栈信息, Java 堆用于...

    大厂真题之京东-Java实习生

    1、哪些情况下的对象会被垃圾回收机制处理掉? 利用可达性分析算法,虚拟机会将一些对象定义为 GC ...2)若有必要执行,会把对象放到一个 队列中,JVM 会开一个线程去回收它们,这是对象最后一次可以逃逸清理的机会。

Global site tag (gtag.js) - Google Analytics