每一个栈帧中存储着java
1.局部变量表(Local Variables)python
2.操做数栈(Operand Stack)(或表达式栈)编程
3.动态连接(Dynamic Linking)(或执行"运行时常量池"的方法引用)----深刻理解Java多态特性必读!!数组
4.方法返回地址(Return Adress)(或方法正常退出或者异常退出的定义)缓存
5.一些附加信息安全
其中部分参考书目上,称方法返回地址、动态连接、附加信息为帧数据区架构
1.局部变量表也被称之为局部变量数组或本地变量表jvm
2.定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量这些数据类型包括各种基本数据类型、对象引用(reference),以及returnAddressleixing编程语言
3.因为局部变量表是创建在线程的栈上,是线程私有的数据,所以不存在数据安全问题函数
4.局部变量表所需的容量大小是在编译期肯定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的
5.方法嵌套调用的次数由栈的大小决定。通常来讲,栈越大,方法嵌套调用次数越多。对一个函数而言,他的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以知足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间。
6.局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机经过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。
利用javap命令对字节码文件进行解析查看main()方法对应栈帧的局部变量表,如图:
也能够在IDEA 上安装jclasslib byte viewcoder插件查看方法内部字节码信息剖析,以main()方法为例
1.参数值的存放老是在局部变量数组的index0开始,到数组长度-1的索引结束
2.局部变量表,最基本的存储单元是Slot(变量槽)
3.局部变量表中存放编译期可知的各类基本数据类型(8种),引用类型(reference),returnAddress类型的变量。
4.在局部变量表里,32位之内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot。
byte、short、char、float在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true;
long和double则占据两个slot。
5.JVM会为局部变量表中的每个slot都分配一个访问索引,经过这个索引便可成功访问到局部变量表中指定的局部变量值
6.当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照声明顺序被复制到局部变量表中的每个slot上
7.若是须要访问局部变量表中一个64bit的局部变量值时,只须要使用前一个索引便可。(好比:访问long或者double类型变量)
8.若是当前帧是由构造方法或者实例方法建立的(意思是当前帧所对应的方法是构造器方法或者是普通的实例方法),那么该对象引用this将会存放在index为0的slot处,其他的参数按照参数表顺序排列。
9.静态方法中不能引用this,是由于静态方法所对应的栈帧当中的局部变量表中不存在this
示例代码:
public class LocalVariablesTest { private int count = 1; //静态方法不能使用this public static void testStatic(){ //编译错误,由于this变量不存在与当前方法的局部变量表中!!! System.out.println(this.count); } }
栈帧中的局部变量表中的槽位是能够重复利用的,若是一个局部变量过了其做用域,那么在其做用域以后申明的新的局部变量就颇有可能会复用过时局部变量的槽位,从而达到节省资源的目的。
private void test2() { int a = 0; { int b = 0; b = a+1; } //变量c使用以前以及经销毁的变量b占据的slot位置 int c = a+1; }
上述代码对应的栈帧中局部变量表中一共有多少个slot,或者说局部变量表的长度是几?
答案是3:
变量b的做用域是
{ int b = 0; b = a+1; }
this占0号、a单独占1个槽号、c重复使用了b的槽号
变量的分类:
1.栈 :可使用数组或者链表来实现
2.每个独立的栈帧中除了包含局部变量表之外,还包含一个后进先出的操做数栈,也能够成为表达式栈
3.操做数栈,在方法执行过程当中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)或出栈(pop)
某些字节码指令将值压入操做数栈,其他的字节码指令将操做数取出栈,使用他们后再把结果压入栈。(如字节码指令bipush操做)
好比:执行复制、交换、求和等操做
代码举例
③压入8;④8出栈,存储8进入局部变量表;
⑤从局部变量表中把索引为1和2的是数据取出来,放到操做数栈;⑥iadd相加操做
⑦iadd操做结果23出栈⑧将23存储在局部变量表索引为3的位置上istore_3
1.运行时常量池位于方法区(注意: JDK1.7 及以后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。)
字节码中的常量池结构以下:
为何须要常量池呢?
常量池的做用,就是为了提供一些符号和常量,便于指令的识别。下面提供一张测试类的运行时字节码文件格式
2.每个栈帧内部都包含一个指向运行时常量池Constant pool或该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码可以实现动态连接。好比invokedynamic指令
3.在Java源文件被编译成字节码文件中时,全部的变量和方法引用都做为符号引用(symbolic Refenrence)保存在class字节码文件(javap反编译查看)的常量池里。好比:描述一个方法调用了另外的其余方法时,就是经过常量池中指向方法的符号引用来表示的,那么动态连接的做用就是为了将这些符号引用(#)最终转换为调用方法的直接引用。
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关
对应的方法的绑定机制为:早起绑定(Early Binding)和晚期绑定(Late Bingding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。
随着高级语言的横空出世,相似于java同样的基于面向对象的编程语言现在愈来愈多,尽管这类编程语言在语法风格上存在必定的差异,可是它们彼此之间始终保持着一个共性,那就是都支持封装,集成和多态等面向对象特性,既然这一类的编程语言具有多态特性,那么天然也就具有早期绑定和晚期绑定两种绑定方式。
Java中任何一个普通的方法其实都具有虚函数的特征,它们至关于C++语言中的虚函数(C++中则须要使用关键字virtual来显式定义)。若是在Java程序中不但愿某个方法拥有虚函数的特征时,则可使用关键字final来标记这个方法。
子类对象的多态性使用前提:
①类的继承关系(父类的声明)②方法的重写(子类的实现)
实际开发编写代码中用的接口,实际执行是导入的的三方jar包已经实现的功能
非虚方法
其余全部体现多态特性的方法称为虚方法
普通调用指令:
1.invokestatic:调用静态方法,解析阶段肯定惟一方法版本;
2.invokespecial:调用<init>方法、私有及父类方法,解析阶段肯定惟一方法版本;
3.invokevirtual调用全部虚方法;
4.invokeinterface:调用接口方法;
动态调用指令(Java7新增):
5.invokedynamic:动态解析出须要调用的方法,而后执行 .
前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户肯定方法版本。
其中invokestatic指令和invokespecial指令调用的方法称为非虚方法
其中invokevirtual(final修饰的除外,JVM会把final方法调用也归为invokevirtual指令,但要注意final方法调用不是虚方法)、invokeinterface指令调用的方法称称为虚方法。
/** * 解析调用中非虚方法、虚方法的测试 */ class Father { public Father(){ System.out.println("Father默认构造器"); } public static void showStatic(String s){ System.out.println("Father show static"+s); } public final void showFinal(){ System.out.println("Father show final"); } public void showCommon(){ System.out.println("Father show common"); } } public class Son extends Father{ public Son(){ super(); } public Son(int age){ this(); } public static void main(String[] args) { Son son = new Son(); son.show(); } //不是重写的父类方法,由于静态方法不能被重写 public static void showStatic(String s){ System.out.println("Son show static"+s); } private void showPrivate(String s){ System.out.println("Son show private"+s); } public void show(){ //invokestatic showStatic(" 大头儿子"); //invokestatic super.showStatic(" 大头儿子"); //invokespecial showPrivate(" hello!"); //invokespecial super.showCommon(); //invokevirtual 由于此方法声明有final 不能被子类重写,因此也认为该方法是非虚方法 showFinal(); //虚方法以下 //invokevirtual showCommon();//没有显式加super,被认为是虚方法,由于子类可能重写showCommon info(); MethodInterface in = null; //invokeinterface 不肯定接口实现类是哪个 须要重写 in.methodA(); } public void info(){ } } interface MethodInterface { void methodA(); }
Java:String info = "硅谷";//静态语言 JS:var name = "硅谷“;var name = 10;//动态语言 Pythom: info = 130;//更加完全的动态语言
举个例子:咱们定义三个类、一个Friendly接口
interface Friendly{ void sayHello(); void sayGoodbye(); }
Dog类的虚方法表
class Dog{ public void sayHello(){ } public String toString(){ return "Dog"; } }
可卡犬虚方法表:可卡犬如果使用toString方法无需向上找Object类,只需找到Dog类便可;这是一个效率的提高
class CockerSpaniel extends Dog implements Friendly{ public void sayHello(){ super.sayHello(); } public void sayGoodbye(){ } }
猫类的虚方法表:
class Cat implements Friendly{ public void eat(){ } public void sayHello(){ } public void sayGoodbye(){ } protected void finalize(){ } public String toString(){ } }
1.执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口;
2.在方法执行的过程当中遇到了异常(Exception),而且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜素到匹配的异常处理器,就会致使方法退出,简称异常完成出口
方法执行过程当中抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找处处理异常的代码。
咱们写一个demo演示:
字节码当中的异常处理表:下表的行号不是上图的代码的行号,而是其对应字节码当中的行号
在字节码当中的4~11行是可能存在异常的代码,11表明字节码中可以处理该异常的位置是第11行也就是上图中的第72行
栈帧中还容许携带与java虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息。(不少资料都忽略了附加信息)
参考教程:尚硅谷官方--宋红康
参考书目:深刻理解JVM--周志明