因为跨平台性的设计,JAVA的指令都是根据栈来设计的。
优势:跨平台,指令集小,编译器容易实现。
缺点:性能降低,实现一样的功能须要更多指令。java
栈是运行时的单位,堆是储存的单位。数组
每一个线程在建立的时候都会建立一个虚拟机栈,其内部保存一个个的栈帧,对应着一次次的Java方法调用。
虚拟机栈主管Java程序的运行,它保存方法的局部变量,部分结果并参与方法的调用和返回。缓存
咱们可使用参数-Xss来设置虚拟机栈的大小。
IDEA能够在run -> Edit Configurations进行设置。安全
public class StackFrameTest {架构
public static void main(String[] args) { StackFrameTest test = new StackFrameTest(); test.methon1(); } private void methon1() { System.out.println("方法一开始执行"); methon2(); System.out.println("方法一执行结束"); } private void methon2() { System.out.println("方法二开始执行"); methon3(); System.out.println("方法二执行结束"); } private void methon3() { System.out.println("方法三开始执行"); System.out.println("方法三执行结束"); } }
每一个栈帧中存储着如下内容:函数
局部变量表和操做数栈主要影响着栈帧的大小,栈帧的大小影响着虚拟机栈能存放多少栈帧。性能
局部变量表也被称为局部变量数组或本地变量表。测试
public class LocalVariablesTest {this
public static void main(String[] args) { LocalVariablesTest test = new LocalVariablesTest(); int i = 10; } }
javap反编译后查看局部变量表,结果如图:
其中,spa
除了javap指令,咱们也能够经过JClasslib插件查看局部变量表。
局部变量表内容同上,start和length两个属性决定变量的做用域,变量在start标志的下一行开始生效。
类方法为何不能使用this关键字?
由于在类方法的局部变量表中不存在this变量。
栈帧中的局部变量表中的slot是能够重用的,若是一个局部变量过了其做用域,那么在其做用域后申请的新局部变量就颇有可能复用这个过时变量的槽位。测试代码:
public void test() { int a = 1; { int b = 0; b = a + 1; } int c = a + 1; }
测试代码的局部变量图以下:
经过索引,咱们能够看出c复用了b的slot。
变量的分类:
根据数据类型分类:
根据在类中声明的位置分类:
成员变量
public void testadd() { byte i = 15; int j = 8; int k = i + j; }
反编译后的字节码指令以下:
0 bipush 15 2 istore_1 3 bipush 8 5 istore_2 6 iload_1 7 iload_2 8 iadd 9 istore_3 10 return
字节码指令解析:
0:将数值15压入操做数栈。
2: 将操做数栈的当前栈帧压入局部变量表,索引为1。
3:将数值8压入操做数栈。
5:将操做数栈的当前栈帧压入局部变量表,索引为2。
6:读取局部变量表中索引为1的变量进操做数栈。
7:读取局部变量表中索引为2的变量进操做数栈。
8:执行加操做,并将结果压入操做数栈。
9:将操做数栈的当前栈帧压入局部变量表,索引为3。
10:方法正常返回结束。
有些地方可能会将方法返回地址,动态连接和附加信息称为帧数据区。
public class DynamicLinkingTest { int num = 1; public void methonA() { System.out.println("methonA"); } public void methonB() { System.out.println("methonB"); methonA(); num++; } }
反编译以后的执行指令以下:
Code: stack=3, locals=1, args_size=1 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #6 // String methonB 5: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: aload_0 9: invokevirtual #7 // Method methonA:()V 12: aload_0 13: dup 14: getfield #2 // Field num:I 17: iconst_1 18: iadd 19: putfield #2 // Field num:I 22: return
咱们能够看到在指令后出现大量的#开头的符号引用,这些引用能够在编译后的常量池中找到对应的实际方法,测试代码编译后的常量池以下图:
为了提供一些符号和常量,便于指令的识别,减小内存,提升复用。
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关。
根据两种连接对应的绑定机制,绑定是指一个字段,方法或者类在符号引用被替换成直接引用的过程,这仅仅发生一次。
非虚方法:若是方法在编译期就肯定了具体的调用版本,这个版本在运行时是不可变的,这样的方法称为非虚方法。如静态方法,私有方法,final方法,实例构造器,父类方法都是非虚方法。其余方法都称为虚方法。
虚拟机中提供了一下几条方法调用指令:
普通调用指令
动态调用指令
前四条指令方法的调用执行不可人为干预,而动态调用支持由用户肯定方法版本。其中invokestatic和invokespecial指令调用的方法称为非虚方法,其他的(final修饰的除外)称为虚方法。
没有显式使用super的方法调用也会被认为是虚方法,使用invokevirtual调用。
JDK7中为了实现动态类型语言支持而作出的改进,出现了invokedynamic指令。但直到JDK8的Lambda表达式的出现,invokedynamic指令的生成,在JAVA种才有了直接的生成方式。
这两种语言最重要的区别在于对类型的检查是在编译期间仍是在运行期间,前者为静态类型语言,后者为动态类型语言。直白的说,静态语言判断变量的类型,动态语言判断变量值的类型。
方法重写的本质
IllegalAccessError异常介绍:
程序试图访问或修改一个属性或调用一个方法,这个属性或方法你没有权限访问。通常来讲这个会引发编译器异常,若是发生在运行期间,就说明一个类发生了不兼容的改变。
为了提升性能,JVM在方法区创建一个虚方法表来实现,使用索引表来代替查找。
虚方法表主要在类加载的连接解析阶段建立。
存放调用该方法的程序计数器的值。在方法结束以后都须要返回到该方法被调用的位置,方法正常退出时,调用者的程序计数器的值做为返回地址,即调用该方法指令的下一条指令的地址。而经过异常退出须要经过异常表肯定,栈帧中不保存这部分信息。
因此,经过异常完成的方法退出不会给它的上层调用者任何的返回值。
补充:方法返回指令!
这个部分可能会有,也可能没有,主要看虚拟机的具体实现。