Java虚拟机运行时栈帧结构(周志明书上P237页)

栈帧是什么?

栈帧是一种数据结构,用于虚拟机进行方法的调用和执行。

栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元。


2018.1.2更新(在网上看到一个更好的解释):

栈帧(Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。

栈帧在什么地方?

内存 -> 运行时数据区 -> 某个线程对应的虚拟机栈 -> 这里就是栈帧了

栈帧的含义?

每个方法的执行和结束对应着栈帧的入栈和出栈。

入栈表示被调用,出栈表示执行完毕或者返回异常。

一个虚拟机栈对应一个线程,当前CPU调度的那个线程叫做活动线程;一个栈帧对应一个方法,活动线程的虚拟机栈里最顶部的栈帧代表了当前正在执行的方法,而这个栈帧也被叫做‘当前栈帧’。

栈帧既然是个数据结构,都有哪些数据?

局部变量表、操作数栈、动态链接、方法返回地址、附加信息。

栈帧的大小是什么时候确定的?

编译程序代码的时候,就已经确定了局部变量表和操作数栈的大小,而且在方法表的Code属性中写好了。不会受到运行期数据的影响。

什么是局部变量表

是一片逻辑连续的内存空间,最小单位是Slot,用来存放方法参数和方法内部定义的局部变量。我觉得可以想成Slot数组....JVMS7:“any parameters are passed in consecutive local variables starting from local variable 0”

虚拟机没有明确指明一个Slot的内存空间大小。但是boolean、byte、char、short、int、float、reference、returnAddress类型的数据都可以用32位空间或更小的内存来存放。这些类型占用一个Slot。Java中的long和double类型是64位,占用两个Slot。(只有double和long是jvms里明确规定的64位数据类型)

虚拟机如何调用这个局部变量表?

局部变量表是有索引的,就像数组一样。从0开始,到表的最大索引,也就是Slot的数量-1。

要注意的是,方法参数的个数 + 局部变量的个数 ≠ Slot的数量。因为Slot的空间是可以复用的,当pc计数器的值已经超出了某个变量的作用域时,下一个变量不必使用新的Slot空间,可以去覆盖前面那个空间。(这部分内容在P183页)

特别地,JVMS7:

On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language)

手动翻译:在一个实例方法的调用时,局部变量表的第0位是一个指向当前对象的引用,也就是Java里的this。

局部变量表Slot复用对垃圾收的影响(书上的P239页)

先了解一下System.gc()机制:

public class Main{
public static void main(String [] args){
byte[] placeholder = new byte[64*1024*1024];
System.gc();
}
}

对于上面dos输出的结果,我是这样理解的:

第一行,Allocation Filure(空间分配失败)引起了Minor GC。因为创建的对象太大,新生代装不下,所以进行了一次GC。

第二行,由于新生代GC完了后,还是装不下,这时就应该把它直接放到老年代,为了老年代又足够的空间来迎接这个大对象,所以老年代进行一次Full GC。

第三行,是代码中的手动gc,发现这次手动gc并没有回收掉这个大对象。因为,placeholder这个对象,还在作用域....就不该回收....


这回System.gc()该回收掉placeholder了吧?

public class Main{
public static void main(String [] args){
{
byte[] placeholder = new byte[64*1024*1024];
}
System.gc();
}
}

要不是回收时间不一样...还真看不出什么区别...

明显,还是没有回收掉这个placeholder大对象。

为什么呢?

因为虚拟机并不急着让placeholder回收掉,因为,在我这个程序中,对虚拟机来说,回不回收placeholder,对内存没有丝毫影响,剩余的空间一样都是浪费(空闲)着,回收了反倒还浪费时间。


这样做才能成功回收:

public class Main{
public static void main(String [] args){
{
byte[] placeholder = new byte[64*1024*1024];
}
int a = 0;
System.gc();
}
}

其实服用之前,虽然placeholder退出了作用域,但是虚拟机并没有做什么事,只是知道pc指针已经超出了placeholder的作用域,知道placeholder过期了。所以placeholder仍保持者GC Roots之间的关联。

当a=0复用了前面对象的空间时,就打断了GC Roots与局部变量表中的placeholder之间的关联。因为a复用了这片空间(虽然只是用了一小部分)。此时GC Root无法达到placeholder对象,满足回收条件。

然后System.gc()就成功回收了。


也就是说在复用之前并不会判定为‘垃圾’,在复用后才会被判定为‘垃圾’。刚才使用一个int a来复用,这个复用看起来很轻量。

如果使用一个新的大对象来复用,那么GC是如何发生的呢?看下面代码:

public class Main{
public static void main(String [] args)throws InterruptedException{
{
byte[] placeholder = new byte[64*1024*1024];
}
byte[]arr= new byte[20*1024*1024];
System.gc();
}
} 

解读dos下的输出:

第一行,因为即将创建的placeholder太大,新生代装不下,所以进行一次GC。

第二行, 因为GC之后还是装不下placeholder,所以把这个大对象直接放进老年代里。迎接这个大对象之前,先清一清自己的空间(Full GC),怕自己装不下。

第三行,因为即将创建的arr太大,新生代装不下,所以进行一次GC。

第四行,因为GC之后还是装不下arr, 所以把这个大对象直接放进老年代里。迎接这个大对象之前,先清一清自己的空间(Full GC),怕自己装不下。

但是,可以看到这一次Full GC并没有把placeholder清理掉,因为还没开始复用呢。

随后创建好了arr, 也就是复用了placeholder的空间。这时才把placeholder判定为垃圾。

第五行,是代码里手写的System.gc()方法。这时把placeholder这个垃圾清理掉。


有没有发现这个Full GC来的不是很恰到好处?因为没有及时清理掉placeholder。

为什么没有清理掉呢?因为局部变量表里的placeholder数据还和GC Root连着,导致没有判定它为垃圾。

能不能及时断开这个连接,让这个Full GC起到它该起的作用呢?

可以巧用null来解决,看下面代码:

public class Main{
public static void main(String [] args)throws InterruptedException{
{
byte[] placeholder = new byte[64*1024*1024];
placeholder = null;
}
byte[]arr= new byte[20*1024*1024];
System.gc();
}
}  

解读dos下的输出:

第一行,因为即将创建的placeholder太大,新生代装不下,所以进行一次GC。

第二行, 因为GC之后还是装不下placeholder,所以把这个大对象直接放进老年代里。迎接这个大对象之前,先清一清自己的空间(Full GC),怕自己装不下。

随后placeholder= null;

第三行,因为即将创建的arr太大,新生代装不下,所以进行一次GC。

第四行,因为GC之后还是装不下arr, 所以把这个大对象直接放进老年代里。迎接这个大对象之前,先清一清自己的空间(Full GC),怕自己装不下。

可以看到这一次Full GC把placeholder清理掉了。

随后创建好了arr,复用了placeholder。

第五行,是代码里手写的System.gc()方法。

什么事

什么是操作数栈(参考JVMS7)

Each frame (§2.6) contains a last-in-first-out (LIFO) stack known as its operand stack.

翻译:每个栈帧都包含一个被叫做操作数栈的后进先出的栈。叫操作栈,或者操作数栈。

Where it is clear by context, we will sometimes refer to the operand stack of the current frame as simply the operand stack.

翻译:通常情况下,操作数栈指的就是当前栈桢的操作数栈。

操作数栈有什么用?

The operand stack is empty when the frame that contains it is created. The Java virtual machine supplies instructions to load constants or values from local variables or fields onto the operand stack. Other Java virtual machine instructions take operands from the operand stack, operate on them, and push the result back onto the operand stack. The operand stack is also used to prepare parameters to be passed to methods and to receive method results.

翻译+归纳:

1.栈桢刚创建时,里面的操作数栈是空的。

2.Java虚拟机提供指令来让操作数栈对一些数据进行入栈操作,比如可以把局部变量表里的数据、实例的字段等数据入栈。

3.同时也有指令来支持出栈操作。

4.向其他方法传参的参数,也存在操作数栈中。

5.其他方法返回的结果,返回时存在操作数栈中。

操作数栈本身就是一个普通的栈吗?

其实栈就是栈,再加上数据结构所支持的一些指令和操作。

但是,这里的栈也是有约束的。

操作数栈是区分类型的,操作数栈中严格区分类型,而且指令和类型也好严格匹配。

栈桢和栈桢是完全独立的吗?

本来栈桢作为虚拟机栈的一个单元,应该是栈桢之间完全独立的。

但是,虚拟机进行了一些优化:为了避免过多的 方法间参数的复制传递、方法返回值的复制传递 等一些操作,就让一部分数据进行栈桢间共享。

什么是动态链接?

一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,总得知道被调用者的名字吧?(你可以不认识它本身,但调用它就需要知道他的名字)。符号引用就相当于名字,这些被调用者的名字就存放在Java字节码文件里。

名字是知道了,但是Java真正运行起来的时候,真的能靠这个名字(符号引用)就能找到相应的类和方法吗?

需要解析成相应的直接引用,利用直接引用来准确地找到。

举个例子,就相当于我在0X0300H这个地址存入了一个数526,为了方便编程,我把这个给这个地址起了个别名叫A, 以后我编程的时候(运行之前)可以用别名A来暗示访问这个空间的数据,但其实程序运行起来后,实质上还是去寻找0X0300H这片空间来获取526这个数据的。

这样的符号引用和直接引用在运行时进行解析和链接的过程,叫动态链接。

动态链接的前提

每一个栈帧内部都要包含一个指向运行时常量池的引用,来支持动态链接的实现。

附加信息

JVMS里没看到啊....但是书里提了,然后说"JVM里没有明文规定"....

方法返回地址

方法正常调用完成

返回一个值给调用它的方法,方法正常完成发生在一个方法执行过程 中遇到了方法返回的字节码指令(§2.11.8)的时候,使用哪种返回指令取决于方法返回值的数 据类型(如果有返回值的话)。

JVMS7中的2.6.4 Normal Method Invocation Completion中写道:

This occurs when the invoked method executes one of the return instructions (§2.11.8), the choice of which must be appropriate for the type of the value being returned (if any).

手动翻译+理解:Java虚拟机根据不同数据类型有不同的底层return指令。当被调用方法执行某条return指令时,会选择相应的return指令来让值返回(如果该方法有返回值的话)。

The current frame (§2.6) is used in this case to restore the state of the invoker, including its local variables and operand stack, with the program counter of the invoker appropriately incremented to skip past the method invocation instruction. Execution then continues normally in the invoking method's frame with the returned value (if any) pushed onto the operand stack of that frame.

手动翻译:在这种情况,当前栈桢就被用来恢复调用者的状态,都恢复哪些呢?恢复局部变量表、操作数栈 和 程序计数器(pc指针),而这个程序计数器要适当地增加,来指向下一条指令(也就是调用函数的下一句)。使调用者方法能够正常地继续执行下去,而且返回值push到了调用方法的操作数栈中。

方法异常调用完成

异常时不会返回值给调用者。

未完待续。。。这方面我再学习学习。。

参考博客:

英中繁簡編程術語對照http://www.moon-soft.com/doc/30155.htm

http://blog.csdn.net/dd864140130/article/details/49515403

http://blog.csdn.net/u013678930/article/details/51980460

https://www.zhihu.com/question/29056872

https://segmentfault.com/a/1190000010648021

http://blog.csdn.net/captian_900331/article/details/52512204

https://www.zhihu.com/question/53822079/answer/136699108

http://hllvm.group.iteye.com/group/topic/33366

http://blog.csdn.net/renfufei/article/details/49230943

http://blog.csdn.net/newhappy2008/article/details/7596027

Java虚拟机运行时栈帧结构--《深入理解Java虚拟机》学习笔记及个人理解(二)的更多相关文章

  1. 深入理解java虚拟机(十) Java 虚拟机运行时栈帧结构

    运行时栈帧结构 栈帧(Stack Frame) 是用于虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素.每一个方法从调用到方法返回都对应着一个栈帧入栈出栈的过程. 每一个栈帧在编 ...

  2. 【转载】深入理解Java虚拟机笔记---运行时栈帧结构

    栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素.栈帧存储了方法的局部变量表,操作 ...

  3. 计算机是如何计算的、运行时栈帧分析(神奇i++续)

    关于i++的疑问 通过JVM javap -c 查看字节码执行步骤了解了i++之后,衍生了一个问题: int num1=50; num1++*2执行的是imul(将栈顶两int类型数相乘,结果入栈), ...

  4. java虚拟机规范-运行时栈帧

    前言 java虚拟机是java跨平台的基石,本文的描述以jdk7.0为准,其他版本可能会有一些微调. 引用 java虚拟机规范 java虚拟机规范-运行时数据区 java内存运行时的栈帧结构 java ...

  5. JDK1.8-Java虚拟机运行时数据区域和HotSpot虚拟机的内存模型

    目录 介绍 官方文档规定的运行时数据区域 程序计数器 Java虚拟机栈 本地方法栈 虚拟机栈和本地方法栈溢出 Java堆 演示堆内存溢出 方法区 运行时常量池 演示方法区溢出 HotSpot虚拟机的内 ...

  6. 【JVM从小白学成大佬】2.Java虚拟机运行时数据区

    目录 1.运行时数据区介绍 2.堆(Heap) 是否可能有两个对象共用一段内存的事故? 3.方法区(Method Area) 4.程序计数器(Program Counter Register) 5.虚 ...

  7. 【JVM学习】2.Java虚拟机运行时数据区

    来源: 公众号: 猿人谷 这里我们先说句题外话,相信大家在面试中经常被问到介绍Java内存模型,我在面试别人时也会经常问这个问题.但是,往往都会令我比较尴尬,我还话音未落,面试者就会"背诵& ...

  8. Java虚拟机-运行时数据区域

    Java虚拟机管理的内存包括如图所示的运行时数据区域: 下面分别进行介绍: 1)程序计数器(Program Counter Register) 占用的内存空间比较小,主要作用就是标识当前线程执行的字节 ...

  9. 笔记:Java虚拟机运行时数据区

    Java虚拟机在执行Java程序的过程中会把它管的内存划分为以下若干个不同的区域: 1.程序计数器 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器:由于Java虚拟机的 ...

随机推荐

  1. [Note] Stream Computing

    Stream Computing 概念对比 静态数据和流数据 静态数据,例如数据仓库中存放的大量历史数据,特点是不会发生更新,可以利用数据挖掘技术和 OLAP(On-Line Analytical P ...

  2. Java中的自定义数组队列

    在Java中,作为所有数据结构中存储和获取速度最快的一种,数组凭借其这种简单易用的优势在各个方面都能大显神威.但是数组也有自身的局限性.数组的长度必须是固定的一旦定义之后就无法动态的更改,这就会造成这 ...

  3. python替换脚本

    任何场合都用的到的全文替换 #!/usr/bin/python import sys if len(sys.argv) < 5: print 'usage: python %s from to ...

  4. Ubutu Chrome 出现adobe flash is out of date的解决方法

    我们需要到官网下载flash player,网址:https://get.adobe.com/flashplayer/ 不过这里要说明一下: 一般的浏览器使用的是npapi,即adobe flash ...

  5. 利用PowerDesigner15在win7系统下对MySQL 进行反向工程(三)

    利用PowerDesigner15在win7系统下对MySQL 进行反向工程 1.选择"数据库-->Generate Database...",查看数据库表的SQL语句 2. ...

  6. python中的迭代器&&生成器&&装饰器

    迭代器iterator 迭代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束. 迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退.另外, ...

  7. OpenGL shader渲染贴图

    simple.vert #version core layout (location = ) in vec3 position; layout (location = ) in vec3 color; ...

  8. 易理解java代码8皇后问题

    马上就要蓝桥杯比赛了,我这些算法还是不会,确实有点慌,今天一天早上睡到很晚不愿起床,然后才开始研究8皇后问题.这也是典型的回溯与递归问题.其实本质上和马踏棋盘问题非常类似,八皇后问题呢,就是要判断主对 ...

  9. 异常-----freemarker.template.TemplateException:Error executing macro:mainSelect

    1.错误描述 freemarker.template.TemplateException:Error executing macro:mainSelect require parameter:id i ...

  10. (python)剑指Offer(第二版)面试题14:剪绳子

    题目 给你一根长度为n的绳子,请把绳子剪成m段 (m和n都是整数,n>1并且m>1)每段绳子的长度记为k[0],k[1],…,k[m].请问k[0]k[1]…*k[m]可能的最大乘积是多少 ...