jvm运行时线程私有的内存区域
虚拟机栈分为4个部分:局部变量表、操作数栈、动态链接、返回地址和附加信息
局部变量表
局部变量表在编译的时候,已经计算好了分配的内存空间
局部变量表的最小单位 slot
java么有说明slot占多少个字节,多少位,但是,是这样说的。1个slot可以存放int、float、short、boolean、 refrence、byte、char、returnAddress 。这些字符最大的是4个字节,32位。
所以1个slot 可以放32位。
如果是long和double 的话,是64位的,需要用连续的2个slot空间。
虚拟机是通过索引定位的方使用局部变量表,是从0开始到局部变量表最大的slot数量,如果是32位的,索引n就代表使用了第n个slot。 如果是64位,则会说明,同时使用第n和第n+1个slot,这两个slot不能单独使用。
栈帧之间的通信
栈帧之间不需要进行参数复制,是通过重叠来进行通信的。
重叠:两个栈帧本来是完全相互独立的,jvm对栈帧进行了优化,让两个栈帧重叠在一起,这样就可以共用一部分数据,无需通过额外的参数进行传递。
slot的复用会影响垃圾回收行为:
我们在虚拟机运行参数中加上“-verbose:gc”来看看垃圾收集的过程
测试:因为局部变量表中引用的对象,是GC roots的直接引用。
public static void main(String[] args) {
byte[] placeholder = new byte[64*1024*1024];
System.gc();
}
没有GC,是因为变量placeholder还在作用域范围内,所以不能GC
public static void main(String[] args) {
byte[] placeholder = new byte[64*1024*1024];
}
System.gc();
}
加入了花括号之后,placeholder的作用域被限制在花括号之内,从代码逻辑上讲,在执行System.gc()的时候,placeholder已经不可能再被访问了,但执行一下这段程序,会发现运行结果如下,还是有64MB的内存没有被回收,这又是为什么呢?
public static void main(String[] args) {
byte[] placeholder = new byte[64*1024*1024];
}
int a =0;
System.gc();
}
这次被回收了。 是什么原因呢?
第二次为什么没有被回收,第三次被回收了呢。。
这是因为第二次的变量虽然离开了作用域,但是slot还没有被其他的变量复用,局部变量表中还保留着该变量,所以GC没有回收。
第三次 placeholder 被变量 a 复用。所以不在GC roots 链上了。可以回收
操作数栈
操作数栈,在编译器就已经计算好了栈深。但是刚开始的时候,栈深为0.在运行期间栈深不断变化。
他是一个后入先出(Last In First Out,LIFO)栈
操作数栈每一个元素可以是任意类型,32位的占1个栈深,64位的占2个栈深
假如两个int相加的命令 iadd , 两个 int 数据,在类加载的检验阶段。就必须检验这两个数据和字节码的质量是否相同。
当执行这个指令时,会将这两个int值出栈并相加,然后将相加的结果入栈。
动态链接
每个栈帧都包含着一个指向常量池的符号引用。
Class字节码中的方法调用指令就是以常量池中的符号引用作为参数,来调用方法的。
符号引用会在类加载过程中的解析阶段或者是初始化阶段转化为直接引用。这种叫静态引用
在程序运行期间,符号引用转化为直接引用,叫动态引用。
方法返回
触发返回的条件有2种
1、正常返回:程序运行时,遇到返回指令(return) 或是该方法运行完成。
2、异常返回:程序出现异常,并且没有处理异常的机制。
方法返回的时候会做3件事情:
1、恢复上层方法的局部变量表和操作数栈
2、如果有返回值,将返回值压入到调用者的操作数栈中
3、调整程序技术器的指针