1 概述
javac将语法树输出成字节码,可以达到虚拟机执行时折叠语法树节点的目的。
2 运行时栈帧结构
Java虚拟机以方法作为最基本的执行单元,栈帧(Stack frame)用来支撑虚拟机方法调用和方法执行背后的数据结构。 ) 堆栈元素。
2.1 局部变量表
局部变量表(Local Table)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。 方法的Code属性的数据项决定了方法需要分配的局部变量表。 最大容量。
局部变量表的容量以变量槽(Slot)为最小单位。 根据Java虚拟机规范,每个变量槽应该可以存储一种,byte,char,short,int,float,或类型的数据。
要表示对对象实例的引用,需要支持两件事:
调用方法时,虚拟机使用局部变量表完成实参到形参的传递
例子一
byte[] placeholder = new byte[64 * 1024 * 1024];
System.gc(); // 未回收
例子二
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
System.gc(); // 未回收
实例三
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
int a = 0; // 变量槽复用
System.gc(); // 回收
假设前面占用内存大,后面的方法比较耗时,可以手动设置局部变量为null; 但是一般不建议手动设置局部变量为null
2.2 操作数栈
操作数栈(Stack)也常被称为操作栈。 32位数据元素占用的栈容量为1,64位数据元素占用的栈容量为2。javac的数据流分析工作保证了操作数栈的深度不会超过数据中设置的最大值物品。
2.3 动态链接
每个堆栈帧都包含对运行时常量池中堆栈帧所属方法的引用。 保存此引用是为了支持方法调用过程中的动态链接 ( )。 Class文件的常量池中有大量的符号引用。 ,当字节码中的方法调用指令使用指向常量池中的方法的符号引用作为参数时,这些符号引用中的一部分会在类加载阶段或第一次使用时被转换为直接引用. 这种转换称为静态解析。 另一部分会在每次运行时转化为直接引用,称为动态链接。
2.4 方法返回地址
一个方法执行完后,退出只有两种方式,一种是正常调用完成( ),另一种是遇到异常没有被捕获。 该方法不会返回任何值,称为异常调用完成()
2.5 附加信息
附加信息包括与调试和性能相关的信息,这完全取决于虚拟机的实现。 一般将动态连接和方法返回地址附加信息归为一类,称为栈帧信息。
3个方法调用
方法调用并不意味着方法被执行,它的唯一任务是确定调用了哪个方法。
3.1 分析
所有方法调用的目标方法都是Class文件中常量池的符号引用。 在类加载的解析阶段,一些符号引用会被转化为直接引用。 这种解析的前提是:方法实际上在运行前就有一个可确定的版本,而这个方法的调用版本在运行时是不可变的。 这些方法的调用称为(),在符号编译时可知,在运行时不可变。 主要有两大类:静态方法和私有方法。 前者直接与类型关联,后者无法被外部访问。 每个方法各自的特点决定了它们不能通过继承或者其他方式重写其他版本(性能优化点),所以都适合在类加载阶段进行解析。
Java虚拟机支持以下五种调用字节码指令的方法:
指令可以调用的方法都可以在解析阶段确定唯一的调用版本。 Java语言中满足这个条件的方法一共有:
解析调用必须是静态过程,完全在编译时确定; 另一种调用形式:()调用要复杂得多,可能是静态的也可能是动态的,一共有4种:
3.2 静态调度
重载方法静态调度演示
public class Test {
static abstract class A {
}
static class B extends A {
}
static class C extends A {
}
public void echo(A a) {
System.out.println("A");
}
public void echo(B b) {
System.out.println("B");
}
public void echo(C c) {
System.out.println("C");
}
public static void main(String[] args) {
A b = new B();
A c = new C();
Test test = new Test();
test.echo(b); // A
test.echo(c); // A
}
}
上面代码中,A称为变量的静态类型(Type)或外观类型(Type),B称为变量的实际类型(Type)或运行时类型(Type),
它需要在编译时确定。 对于重载的()方法,会选择 ,方法调用时会选择静态类型。比如下面这个方法在编译期是未知的,所以定义为类型A
A d = new Random().nextBoolean() ? new B() : new C();
test.echo(d); // A
重载方法的优先级选择也是在编译时通过静态赋值来完成的。
重载方法匹配优先级的例子
import java.io.Serializable;
public class Test {
static void echo(char arg) {
System.out.println("char");
}
static void echo(int arg) {
System.out.println("int");
}
static void echo(long arg) {
System.out.println("long");
}
static void echo(float arg) {
System.out.println("float");
}
static void echo(double arg) {
System.out.println("double");
}
static void echo(Character arg) {
System.out.println("Character");
}
static void echo(Serializable arg) {
System.out.println("Serializable");
}
static void echo(Object arg) {
System.out.println("Object");
}
static void echo(char... arg) {
System.out.println("char...");
}
public static void main(String[] args) {
echo('a');
}
}
3.3 动态调度
动态调度的实现与()密切相关。
动态调度的例子
package vm;
import java.io.Serializable;
public class Test {
static abstract class A {
protected abstract void echo();
}
static class B extends A {
@Override
protected void echo() {
System.out.println("B");
}
}
static class C extends A {
@Override
protected void echo() {
System.out.println("C");
}
}
public static void main(String[] args) {
A b = new B();
A c = new C();
b.echo(); // B
c.echo(); // C
b = new C();
b.echo(); // C
}
}
第 0 行到第 15 行是准备动作。 建立b和c的内存空间,调用B和C类型的实例构造函数,将这两个实例的引用存放在第一和第二局部变量表的变量槽中。 ,对应以下两行:
A b = new B();
A c = new C();
在第16-21行,第16行和第20行的aload指令分别将刚刚创建的两个对象的引用压入栈顶。 这两个对象是要执行的echo方法的拥有者,调用或(); 第 17 和 21 行是方法调用指令。 虽然是一样的,但是可以通过指令来分析。 运行时分析过程分为:
这个过程就是Java方法()的本质
字段没有多态性。 虽然父类中的同名字段会存在子类的内存中,但是子类的字段会遮蔽父类中的同名字段。
字段不是多态的
package vm;
public class Test {
static class A {
public int a = 1;
public A() {
a = 2;
echo();
}
void echo() {
System.out.println("A#a " + a);
}
}
static class B extends A {
public int a = 3;
public B() {
a = 4;
echo();
}
@Override
void echo() {
System.out.println("B#a " + a);
}
}
public static void main(String[] args) {
A a = new B();
System.out.println("a.a " + a.a);
// 父类初始化 虚方法调用,子类echo(子类的字段b)方法,访问子类的字段 输出 B#a 0
// 子类初始化 虚方法调用,子类echo(子类的字段b)方法,访问子类的字段 输出 B#a 4
// 静态调用 访问父类字段 输出 a.a 2
}
}