本文是深入理解java虚拟机的读书笔记
- package b;
- public class BB
- {
- static abstract class Human
- {
- }
- static class Man extends Human
- {
- }
- static class Woman extends Human
- {
- }
- public void sayHello(Human guy)
- {
- System.out.println("hello guy");
- }
- public void sayHello(Man guy)
- {
- System.out.println("hello man");
- }
- public void sayHello(Woman guy)
- {
- System.out.println("hello woman");
- }
- public static void main(String[] args)
- {
- BB b = new BB();
- Human man = new Man();//静态类型为Human
- Human woman = new Woman();
- b.sayHello(man);
- b.sayHello(woman);
- }
- }
- package b;
- public class AA
- {
- static abstract class Human
- {
- protected abstract void sayHello();
- }
- static class Man extends Human
- {
- @Override
- protected void sayHello()
- {
- System.out.println("man say hello");
- }
- }
- static class Woman extends Human
- {
- @Override
- protected void sayHello()
- {
- System.out.println("woman say hello");
- }
- }
- public static void main(String[] args)
- {
- Human man = new Man();
- Human woman = new Woman();
- man.sayHello();
- woman.sayHello();
- man = new Woman();
- man.sayHello();
- }
- }
方法的执行
解释执行
在jdk 1.0时代,Java虚拟机完全是解释执行的,随着技术的发展,现在主流的虚拟机中大都包含了即时编译器(JIT)。因此,虚拟机在执行代码过程中,到底是解释执行还是编译执行,只有它自己才能准确判断了,但是无论什么虚拟机,其原理基本符合现代经典的编译原理,如下图所示:
在Java中,javac编译器完成了词法分析、语法分析以及抽象语法树的过程,最终遍历语法树生成线性字节码指令流的过程,此过程发生在虚拟机外部。
基于栈的指令集与基于寄存器的指令集
Java编译器输入的指令流基本上是一种基于栈的指令集架构,指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。另外一种指令集架构则是基于寄存器的指令集架构,典型的应用是x86的二进制指令集,比如传统的PC以及Android的Davlik虚拟机。两者之间最直接的区别是,基于栈的指令集架构不需要硬件的支持,而基于寄存器的指令集架构则完全依赖硬件,这意味基于寄存器的指令集架构执行效率更高,单可移植性差,而基于栈的指令集架构的移植性更高,但执行效率相对较慢,初次之外,相同的操作,基于栈的指令集往往需要更多的指令,比如同样执行2+3这种逻辑操作,其指令分别如下:
基于栈的计算流程(以Java虚拟机为例):
iconst_2 //常量2入栈
istore_1
iconst_3 //常量3入栈
istore_2
iload_1
iload_2
iadd //常量2、3出栈,执行相加
istore_0 //结果5入栈
而基于寄存器的计算流程:
mov eax,2 //将eax寄存器的值设为1
add eax,3 //使eax寄存器的值加3
基于栈的代码执行示例
下面我们用简单的案例来解释一下JVM代码执行的过程,代码实例如下:
public class MainTest {
public static int add(){
int result=0;
int i=2;
int j=3;
int c=5;
return result =(i+j)*c;
}
public static void main(String[] args) {
MainTest.add();
}
}
使用javap指令查看字节码:
public MainTest();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 2: 0
public static int add();
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=0 //栈深度2,局部变量4个,参数0个
0: iconst_0 //对应result=0,0入栈
1: istore_0 //取出栈顶元素0,将其存放在第0个局部变量solt中
2: iconst_2 //对应i=2,2入栈
3: istore_1 //取出栈顶元素2,将其存放在第1个局部变量solt中
4: iconst_3 //对应 j=3,3入栈
5: istore_2 //取出栈顶元素3,将其存放在第2个局部变量solt中
6: iconst_5 //对应c=5,5入栈
7: istore_3 //取出栈顶元素,将其存放在第3个局部变量solt中
8: iload_1 //将局部变量表的第一个slot中的数值2复制到栈顶
9: iload_2 //将局部变量表中的第二个slot中的数值3复制到栈顶
10: iadd //两个栈顶元素2,3出栈,执行相加,将结果5重新入栈
11: iload_3 //将局部变量表中的第三个slot中的数字5复制到栈顶
12: imul //两个栈顶元素出栈5,5出栈,执行相乘,然后入栈
13: dup //复制栈顶元素25,并将复制值压入栈顶.
14: istore_0 //取出栈顶元素25,将其存放在第0个局部变量solt中
15: ireturn //将栈顶元素25返回给它的调用者
LineNumberTable:
line 4: 0
line 5: 2
line 6: 4
line 7: 6
line 8: 8
public static void main(java.lang.String[]);
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: invokestatic #2 // Method add:()I
3: pop
4: return
LineNumberTable:
line 12: 0
line 13: 4
}
执行过程中代码、操作数栈和局部变量表的变化情况如下:
- 也成为容量槽,虚拟规范中并没有规定一个Slot应该占据多大的内存空间。 ↩
- 这里的严格匹配指的是字节码操作的栈中的实际元素类型必须要字节码规定的元素类型一致。比如iadd指令规定操作两个整形数据,那么在操作栈中的实际元素的时候,栈中的两个元素也必须是整形。 ↩
- Animal dog=new Dog();其中的Animal我们称之为静态类型,而Dog称之为动态类型。两者都可以发生变化,区别在于静态类型只在使用时发生变化,变量本身的静态类型不会被改变,最终的静态类型是在编译期间可知的,而实际类型则是在运行期才可确定。 ↩
- Animal dog=new Dog();其中的Animal我们称之为静态类型,而Dog称之为动态类型。两者都可以发生变化,区别在于静态类型只在使用时发生变化,变量本身的静态类型不会被改变,最终的静态类型是在编译期间可知的,而实际类型则是在运行期才可确定。 ↩
- 宗量:方法的接受者与方法的参数称为方法的宗量。
举个例子:
public void dispatcher(){ int result=this.execute(8,9); } public void execute(int pointX,pointY){ //TODO }
在dispatcher()方法中调用了execute(8,9),那此时的方法接受者为当前this指向的对象,8、9为方法的参数,this对象和参数就是我们所说的宗量。
http://blog.csdn.net/dd864140130/article/details/49515403