阅读 深入理解JVM笔记四

一 、栈帧结构:

        栈帧是一种数据结构,每一个方法的执行到结束,对应了一个栈帧的入栈到出栈:

        1.局部变量表:

            存放方法参数和方法内部定义的局部变量,在编译字节码文件时,就已经基本确定局部变量表的最大容量。

            局部变量表的容量以变量槽(slot)为单位。规定一个slot应该能存放boolean,byte,char,short,int,float,reference,

returnAddress八种类型。long和double通常会用到两个slot,这里牵扯到的原子性操作(long和double的非原子协定),后文详述。

            为了节省空间,局部变量表的slot是允许重用的,局部变量的作用域往往不全是整个方法,当某个变量的作用域已经过去,可能会在该变量的slot上放置新的局部变量。这里会带来一个问题:看下述代码

public class MainClass {
	public static void main(String[] args) {
		{
			byte[] b=new byte[64*1024*1024];
		}
		int a=0;
		System.gc();
	}
}

            在没有int a=0这句话赋值的时候,系统的full gc将不回收b,因为b此刻在局部变量表上,GCRoot有引用关系。而一旦加了赋值语句,因为b脱离了作用域,a占用了b的slot,从而导致b失去引用,被full gc回收。

            由此带来一个奇技:一些大数据不再使用的时候,手动将其设置为null,以便在局部方法中就对其进行清理。(emm,但是书上并不推荐这种操作,建议采用优雅的作用域限定来回收变量)

            请注意:局部变量不像类变量那样,不会赋予初值!      


        2.操作数栈:

            后入先出的数据结构,运算和调用方法传递参数都是通过这里来完成的。具体执行流程见后文。往往两栈帧之间是相互独立的,但大部分虚拟机会给予它们一部分重叠,以达到节省传递的开销。


        3.动态连接:

             每一个栈帧持有一个运行时常量池中该栈帧对应方法的引用,后文详述


        4.方法返回地址:

             退出方法有两种方式:遇到了方法返回的字节码指令,此时将返回值交给方法调用者,成为完成正常完成出口;另一种是遇到了异常,此时退出方法,返回到上层方法调用处,没有返回值,称为异常完成出口。

            方法退出可能会执行:恢复上层方法局部变量表、操作数栈,把返回值压入栈,将pc计数器指向下一条指令。


二、方法调用:

        1.解析:.class文件在类加载的阶段,会将一部分的符号引用直接变成直接引用。这种解析能够成立的唯一条件是方法在程序内是有确定版本的。通常来说:静态方法、私有方法、实例构造器、父类方法四类方法,会在类加载的时候直接转化为直接引用,这类方法被称为非虚方法,字节码指令里使用invokestatic(静态方法),invokespecial(私有方法、实例构造器、父类方法)来调用这些方法,除此之外,final方法也是一种非虚方法,但是不通过上述两种字节码指令调用。(这些方法都无法被覆盖、隐藏)

           

        2.静态分派:

            观察这句代码:

        A a=new B();

            A被称为静态类型,B被称为实际类型。静态类型在编译期是可知的,因此不会改变;实际类型不可知,在运行期才可以知道确切类型。因此在编译期,设计方法重载时,选择方法就必须依靠静态类型来确定。这个过程被称为静态分派,方法的重载是其典型的应用。

            但是偶尔也会出现,虽然能够确定方法的重载版本,但是并不唯一,只能选择一个更加接近的版本,如:

public class MainClass {

	public static void hi(Object obj) {
		System.out.println("Object");
	}

	public static void hi(Character character) {
		System.out.println("Character");
	}

	public static void hi(Serializable ser) {
		System.out.println("Serializable");
	}

	public static void hi(Comparable<Character> com) {
		System.out.println("Comparable");
	}

	public static void hi(int i) {
		System.out.println("int");
	}

	public static void hi(long l) {
		System.out.println("long");
	}

	public static void hi(double d) {
		System.out.println("double");
	}

	public static void hi(char c) {
		System.out.println("char");
	}

	public static void hi(char... ch) {
		System.out.println("char...");
	}

	public static void main(String[] args) {
		hi('a');
	}
}

            输出char,注释掉char重载方法,输出int,注释掉int重载方法,输出long,以此类推按照:

            char->int->long->double->Character->?->Object->char...

            这里的?表示Character类实现了Serializable和Comparable<Character>接口,由于他们是并行的,编译期不能选择一个更好的,产生了争议。


        3.动态分派:

            主要应用在重写上:指令码invokevirtual:记录栈帧顶数据实际类型C,查找C中是否有相符方法、校验访问权限(IllegalAccessError),无误则返回方法的引用。否则从下向上依次到父类中寻找(AbstarctMethodError)。

                

        4.分派小结:

                方法的接受者(调用者)与方法的参数统称为方法的宗量。静态分派是依据宗量决定方法版本,动态分派则是依据实际类型决定实际返回的方法引用。静态分派是多分派(依据多个宗量决定)、动态分派是单分派(只根据一个宗量决定)。


        5.动态语言:

                考虑这样一句代码:

         obj.println("hello world");

                考虑两个完全不相关的类A,B他们都有println(String)的方法。obj在运行时可能是A,也可能是B,那么对于静态的java来说,一定要给定一个静态类型,我们只能给Object类,但是Object没有println(String)方法,方法引用将不能正确的执行,从而会报错。

                因此引入了动态语言:其核心在于,变量本身不具备类型,而变量的值才具有类型(javaScript是一个典型的例子)。

                我们可以使用java.lang.invoke包进行动态调用方法。其中的MethodHandle就是所谓的方法句柄,来为我们调用需要的运行期方法。

                当然,运用反射也可以完成这样的需求,不过反射和句柄有如下对比:

                1)反射模拟Java代码层次的调用,句柄模拟字节码层次的调用(lookup()下的findVirtual,findStatic,findSpecial)。

                2)反射是重量级的工具,具备的信息十分的多。句柄是轻量级工具。

                3)句柄可以通过类似虚拟机优化的方式进行优化,反射不行。

        一个小问题:

        A类继承B类继承C类。如何在A类重写的方法中调用C类的原生方法?(调用B类很简单,super就行了,JDK1.7之前不提供java.lang.invoke包,有什么方法可以实现吗?)


        6.基于栈的解释器执行过程:

            观察一段代码:

            

            用javap:

            

            对应的bipush是讲一个整型变量压入操作数栈,istore则是将栈顶数据推出存放到局部变量表。后续操作类似,直到iload操作,从局部变量表中赋值数据压入栈帧,iadd操作推出栈顶两个元素相加再压回栈帧,imul推出栈顶两个元素相乘再压回栈帧,ireturn方法结束。

            

            

猜你喜欢

转载自blog.csdn.net/zkANewer/article/details/79802329