`
grzrt
  • 浏览: 187789 次
  • 性别: 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);

        }
分享到:
评论

相关推荐

    JVM内幕:java虚拟机详解

    Java虚拟机(JVM)是运行Java应用程序的核心组件,它提供了一个可移植、安全且高性能的环境。本文将深入探讨JVM的内部架构及其各个组成部分的功能。 #### 二、Java虚拟机的内部组件 Java虚拟机的内部组件可以分为两大...

    JVM中[虚拟机栈]的所有内容-pdf

    每个线程在启动时都会创建一个独立的虚拟机栈,用于存储每个线程在执行过程中产生的栈帧(Stack Frame)。栈帧是虚拟机栈中存储的基本单元,与Java方法调用紧密关联。 栈帧是一个内存区块,包含了多个数据结构,如...

    理解JVM虚拟机原理(学习资料).docx

    Java虚拟机(JVM)是Java编程语言的核心组成部分,它为Java程序提供了跨平台的运行环境。Java的“一次编译,到处运行”的特性得益于JVM的存在。JVM是一个软件,它在不同的操作系统上都有相应的版本,如Windows、...

    2024年java面试题-jvm之java虚拟机面试题

    ### 2024年Java面试题:JVM之Java虚拟机面试题 #### Java内存模型及基础知识 **1. Java代码如何被计算机识别** Java程序最初由开发人员使用Java语法编写,这些代码人类可读但计算机无法直接理解。为了使计算机...

    15.运行时栈帧结构1

    运行时栈帧结构是Java虚拟机(JVM)中的一种数据结构,用于支持虚拟机进行方法调用和方法执行。每个方法的调用开始到结束都对应着一个栈帧在虚拟机栈中的入栈到出栈的过程。栈帧中包括了局部变量表、操作数栈、动态...

    JVM虚拟机复习宝典

    ### JVM虚拟机复习宝典:深入理解Java内存区域与对象创建机制 #### 一、Java内存区域概述 Java虚拟机(JVM)管理的内存主要分为两大部分:线程独占的内存区域和线程间共享的内存区域。下面将详细介绍这两部分内存...

    JVM虚拟机面试题汇总

    - **动态链接(Dynamic Link)**:包含一个指向运行时常量池中该栈帧所属方法的引用。 - **方法返回地址(Return Address)**:调用者上下文的信息,用来恢复之前的字节码指令地址。 3. **本地方法栈(Native Method ...

    深入浅出jvm虚拟机视频大全(jvm性能调优+内存模型+虚拟机原理)

    ### JVM虚拟机基础概念 ...通过深入学习JVM虚拟机的相关知识,不仅能够帮助开发者更好地理解Java程序的运行机制,还能够在实际开发过程中针对具体问题采取有效的优化措施,提升程序的运行效率和用户体验。

    javajvm虚拟机原理学习教案.pptx

    Java虚拟机(JVM)是Java程序运行的核心,它是一个抽象的计算机系统,负责解释和执行Java代码。在深入理解JVM的原理时,我们主要关注以下几个方面: 1. **Java虚拟机的生命周期**: - 每当我们运行一个Java程序,...

    Java虚拟机(JVM)面试题 51道.pdf

    Java虚拟机(JVM)是Java程序的运行环境,它是Java语言的核心组件之一。JVM 负责将Java字节码转换为机器代码,并执行机器代码。以下是JVM面试题的一些知识点: Java内存模型 Java内存模型是JVM中的一种抽象概念,...

    深入理解JVM-java虚拟机栈.docx

    Java虚拟机栈(JVM Stack)是Java虚拟机的核心组件之一,它负责管理方法执行时的内存模型。栈是线程私有的,每个线程在启动时都会创建一个虚拟机栈,栈的生命周期与线程同步,即随线程创建而创建,随线程结束而销毁...

    JVM分享java虚拟机

    类装载器负责加载类文件到JVM内存中,运行数据区包括堆、栈、方法区和程序计数器等,它们各自存储不同类型的运行时数据。执行引擎则是JVM的心脏,执行字节码并进行垃圾回收。本地方法接口用于调用非Java语言编写的...

    javajvm虚拟机原理PPT学习教案.pptx

    Java虚拟机(JVM)是Java程序运行的核心,它是一个抽象的计算机系统,负责解释和执行Java字节码。在本教程中,我们将深入探讨JVM的生命周期、体系结构、类加载过程、运行时数据区以及Java类文件的结构。 **Java...

    JVM(Java虚拟机)详解.pdf

    JVM(Java虚拟机)是一个抽象的计算模型,提供了一个运行环境,能够运行 Java 字节码。JVM 可以解读指令代码并与底层进行交互,包括操作系统平台和执行指令并管理资源的硬件体系结构。 二、JVM 内存模型 JVM 内存...

    JVM知识点汇总

    JVM在运行时,将内存空间分为若干个区域,主要包括方法区、堆内存、虚拟机栈、本地方法栈、程序计数器五个部分。 1. 方法区 方法区主要用于存储类信息、常量、静态变量等数据。在jdk1.7之前,方法区是堆的一个逻辑...

    javajvm虚拟机原理PPT课件.pptx

    Java虚拟机(Java Virtual Machine,JVM)是Java平台的核心组件之一,对Java程序的执行和管理起着至关重要的作用。下面是Java虚拟机的主要知识点: Java虚拟机生命周期 Java虚拟机的生命周期可分为三个主要阶段:...

    JVM学习笔记.docx

    Java虚拟机(JVM)是Java程序运行的核心,它负责解释和执行字节码,提供了一个平台无关的运行环境。本篇JVM学习笔记主要涵盖了以下几个核心知识点: 1. **运行时数据区**: - **程序计数器**:记录当前线程执行的...

    5 运行时数据区之虚拟机栈.md,学习代码i

    在JVM内部,有多个运行时数据区域,其中虚拟机栈是非常关键的一个部分。本篇文章将深入探讨虚拟机栈的工作原理及其在Java程序执行过程中的角色。 虚拟机栈,又称为线程栈,每个Java线程都会有一个与之对应的虚拟机...

Global site tag (gtag.js) - Google Analytics