栈帧是一种数据结构,用于虚拟机进行方法的调用和执行。java
栈帧是虚拟机栈的栈元素,也就是入栈和出栈的一个单元。编程
2018.1.2更新(在网上看到一个更好的解释):segmentfault
栈帧(Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态连接 (Dynamic Linking)、方法返回值和异常分派(Dispatch Exception)。数组
内存 -> 运行时数据区 -> 某个线程对应的虚拟机栈 -> 这里就是栈帧了数据结构
每一个方法的执行和结束对应着栈帧的入栈和出栈。app
入栈表示被调用,出栈表示执行完毕或者返回异常。jvm
一个虚拟机栈对应一个线程,当前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。
先了解一下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()方法。
什么事
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