java虚拟机规范定义了虚拟机执行字节码的概念模型。
通俗的说就是,输入java字节码,对字节码进行解析,得到执行后的结果。
这个就不得不提,运行时栈帧的结构了,在java的中,栈帧是java方法调用的的数据结构,每个方法的调用都伴随一个方法的入栈出栈。
局部变量表:
局部变量表就是用来存储,栈帧调用过程中的各个数据,单位为slot(槽),long,double占用两个slot,其余的基本类型
和引用类型都占用一个slot,实例方法中slot 0就是当前对象的引用指针的地址 即this,并且slot是可以复用的,
当程序计数器的指示值超过,某个变量的范围的时候,这个slot可以被其他变量复用的。
public class SlotRepeat {
/**
* 虚拟机参数,-verbose:gc
*/
public static void main(String[] args) {
gc3();
}
/**
* 运行结果,发现调用gc的时候 并没有回收 System.gc();会发生full gc,
* [Full GC (System.gc()) 103056K->102999K(226816K), 0.0159223 secs]
*/
private static void gc1() {
byte [] b = new byte[1024*1024*100];
System.gc();
}
/**
* 运行结果,发现调用gc的时候 还是没回收
* 这个时候b已经不再作用域了为什么还没回收呢?
*/
private static void gc2() {
{
byte [] b = new byte[1024*1024*100];
}
System.gc();
}
/**
* 干啥了,居然回收了,加了个int a =1;
* 这是因为虽然b不再作用域了,但是当前slot仍然存在引用,并没有被覆盖,会在清除,
* 扫描gcroot的时候 仍然会把他党委可用的引用,当进行a=1的时候slot的值被覆盖,
* 则可用进行回收了
*/
private static void gc3() {
{
byte [] b = new byte[1024*1024*100];
}
int a =1;
System.gc();
}
操作数栈:
操作数栈是一个新进后出的数据结构,在进行非法调用的时候,会有各种java字节码指令对操作数栈进行读入和写出数据,
即入栈,出栈,算术运算也是通过操作数栈来完成的
动态链接:
每个栈帧都持有当前方法在常量池的引用,是为了支持方法调用的动态链接(dynamic linking)
之前学习类的解析阶段的时候,会把常量池的符合引用转换为直接引用,对于解析方法调用,有一个前提条件,就是
这个方法必须是唯一确定的,并且该方法是运行期不可改变的,也就是方法必须在编译阶段就确定了方法调用的版本
,这样的方法有 构造方法,私有方法,静态方法,都是可以确定唯一方法的。
静态分派,根据静态类型的分派,在编译阶段就知道了调用那个重载方法了。
public class StaticDispatcher {
public static void main(String[] args) {
new StaticDispatcher().eat(new StaticDispatcher().new Human());
new StaticDispatcher().eat(new StaticDispatcher().new Student());
}
void eat( Human human){//这个地方的Human就是静态类型,编译时期可知
System.out.println("Human eat");
}
void eat( Student student){//这个地方的Student就是静态类型,编译时期可知
System.out.println("Student eat");
}
class Student extends Human{
}
class Human{
}
}
也有一些方法是在运行时期决定实际调用的方法的。
这个就是java特性之一了 多态,也就是子类方法重写,
这个是在运行期决定方法的调用者。
1,首先在当前局部变量的slot中第0个变量加载栈顶
2,根据当前对象的方法描述和简单名称进行匹配,并且进行访问权限检查(private public ),如果通过则返回当前方法的直接引用,进行调用
3,如果上面匹配失败,则到其父类进行方法的匹配,还匹配不了就是 抛出异常
public class DynamicDispatcher {
public static void main(String[] args) {
//在编译时期是不知道具体的方法调用的,只有在运行期才知道具体方法的调用,
//虚拟机实际上是不会进行如此频繁的,由子类到父类
Human human = new DynamicDispatcher().new Student();
human.eat();
Human human1 = new DynamicDispatcher().new Human();
human1.eat();
}
class Student extends Human{
void eat( ){
System.out.println("Student eat");
}
}
class Human{
void eat(){
System.out.println("Human eat");
}
}
}
方法返回地址
即方法调用返回给调用者的地址