1、运行时栈桢结构
栈桢(为什么我每次都读成桢栈)是用于支持虚拟机进行方法调用和方法执行的数据结构,栈桢存储了方法的
- 局部变量表
- 操作数栈
- 动态链接
- 方法返回地址
package test_technology;
/**
*
* @author zhengchao
*/
public class testGC {
public static void main(String[] args){
byte[] placeholder = new byte[64*1024*1024];
System.gc();
}
}
为了便于观察gc的结构,我们在虚拟机运行参数中加上:-verbose:gc来看看垃圾收集的过程。我们就可以在控制台看见gc运行后的结果。分析结果,我们发现placeholder没有被回收,因为它还处于作用域之内。
{
byte[] placeholder1 = new byte[512*1024*1024];
}
System.gc();
我们这么写,发现也没有被回收。{
byte[] placeholder1 = new byte[512*1024*1024];
}
int a =0;
System.gc();
我们这么写,发现placeholder被回收了!
分析如下:placeholder能否被回收的根本原因是,局部变量表中的slot是否还存有关于placeholder数组对象的引用。
第一次修改中,代码虽然已经离开了placeholder的作用域,但在此之后,没有任何对局部变量表的读写
操作,placeholder原本所占用的slot还没有被其他变量所复用,所有作为gc roots一部分的局部变量表仍
然保持着对它的关联。这种关联没有被及时打断,一般情况下,这无所谓,但如果遇到一个方法,其后
面的代码有一些耗时很长的操作,而前面又定义了占用了大量内存、实际已经不会再用的变量,手动将其
设置为null值便会有用处。
操作数栈:后入先出的栈,比如iadd指令在运行时,将操作数栈最接近栈顶的两个元素出栈,并相加,再把结果入栈,
动态连接:后续详述。
方法返回地址:方法退出之后,即当前栈桢出栈,需要返回到方法被调用的位置,程序才能继续执行。一般pc计数器的值可以作为返回地址。
2、方法调用
- 解析:所有方法调用中的目标方法在Class文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用。
- 分派:
package jvm;
/**
*
* @author zhengchao
*/
public class StaticDispatch {
static abstract class Human{}
static class Man extends Human{
}
static class Woman extends Human{
}
public void sayHello(Human guy){
System.out.println("hello,guy!");
}
public void sayHello(Man guy){
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy){
System.out.println("hello,lady!");
}
public static void main(String[] args){
Human man = new Man();
Human woman = new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello(man);
sr.sayHello(woman);
}
}
运行结果:run:
hello,guy!
hello,guy!
成功构建 (总时间: 0 秒)
结果可能有一点出乎你对重载的意料,分析如下:public static void main(String[] args){
Human man = new Man();
Human woman = new Woman();
StaticDispatch sr = new StaticDispatch();
sr.sayHello((Man)man);
sr.sayHello((Woman)woman);
}
运行结果:run:
hello,gentleman!
hello,lady!
成功构建 (总时间: 0 秒)
上面这个例子是为了说明静态分派,静态分派的典型应用是方法重载。动态分派:重写
package jvm;
/**
*
* @author zhengchao
*/
public class DynamicDispatch {
static abstract class Human{
protected abstract void sayHello();
}
static class Man extends Human{
@Override
protected void sayHello() {
System.out.println("man say hello!");
}
}
static class Woman extends Human{
@Override
protected void sayHello() {
System.out.println("woman say hello!");
}
}
public void sayHello(Woman guy){
System.out.println("hello,lady!");
}
public static void main(String[] args){
Human man = new Man();
Human woman = new Woman();
man.sayHello();
woman.sayHello();
man = new Woman();
man.sayHello();
}
}
运行结果:run:
man say hello!
woman say hello!
woman say hello!
成功构建 (总时间: 0 秒)
这个结果,很多人应该不会有疑问,符合大家一直的思想。重写是根据实际类型来分派方法的执行版本。