- 浏览: 369596 次
- 性别:
- 来自: 杭州
文章分类
最新评论
-
surpassno:
很不错,学习了
一个对象占用多少字节? -
ysyzww:
你这么牛逼,你父母知道吗
maven使用技巧 -
妖人不要跑:
JDK中反序列化对象的过程(ObjectInputStream#readObject) -
lanhz:
谢楼主,构建成功了
Mac OSX 10.9 上build openjdk8和openjdk7 -
zqb666kkk:
通过了吗 ?????
淘宝北京专场java面试题(2011-12-31)
一、概述
jvm spec只给出了执行引擎的概念模型,并没有规定具体实现细节。执行引擎在执行时候可以解释执行、编译执行或直接由嵌入芯片的指令执行。引擎的行为使用指令集来定义。
java的目标是一次编写到处运行,为了达到这个目标,jvm指令集就不能依赖于任何硬件平台的指令,jvm指令集中就只有对栈的操作,没有对特定于硬件平台的寄存器的操作。当然jvm运行期优化的时候,可以针对不同的硬件平台提供不同的优化实现,比如充分利用硬件平台的寄存器提高访问速度。既然jvm执行引擎只有对栈的操作,那么我们下边就开始了解下栈的机构。
二、栈和栈帧
栈是线程私有的内存区域,每个线程都有一个栈,线程生则栈生,线程亡则栈灭(这里有一些栈的描述)。栈又由栈帧组成,每个方法调用都生成一个栈帧,方法调用结束则弹出栈帧。
栈帧又由多个部分组成:
1、局部变量表。包含方法参数和方法内部声明的局部变量,如果是实例方法,还有当前对象的this引用。局部变量表的大小在编译期就已经确定了,Locals:2即是;局部变量表所有的值也确定了,Local variable table:即是。此处可以先看class文件中方法的属性中局部变量表信息:
类的实例方法:
public class BigObejct { int[] value; private static final int M1 = 1024 * 1024; public BigObejct() { //4 * 1m = 4m this.value = new int[M1]; } public void setValue(int[] value){ this.value = value; } }
setValue的本地变量表信息:
// Method descriptor #23 ([I)V // Stack: 2, Locals: 2 public void setValue(int[] value); 0 aload_0 [this] 1 aload_1 [value] 2 putfield com.yymt.jvm.BigObejct.value : int[] [16] 5 return Line numbers: [pc: 0, line: 11] [pc: 5, line: 12] Local variable table: [pc: 0, pc: 6] local: this index: 0 type: com.yymt.jvm.BigObejct [pc: 0, pc: 6] local: value index: 1 type: int[]
运行时本地变量表怎么查看?整个栈帧内容怎么查看?在eclipse中调试时候可以Variable窗口可以看到局部变量信息,但是跟局部变量表并不是一一对应的,因为局部变量在运行期只在start_pc之后才被创建并存活到超出作用域。
2、操作栈。出入栈操作就是对该操作数栈的操作,操作数栈的最大栈深在运行期也已经确定,1中Stack:2,表示最大栈深为2。如果考虑上运行期优化技术里的标量替换和栈上分配对象,此处的栈是显然不够用的。后续jvm团队会如何解决呢?
3、解析相关的数据,即指向常量池的指针。在方法运行过程中,可能会用到常量池中的表项,所以需要持有一个到常量池的引用。
4、方法调用返回相关的信息,记录一些信息恢复调用者的栈帧和计数器。需要记录方法调用返回后返回到何处,调用者pc计数器指向哪条指令?方法返回有两种方式,正常的调用返回和异常返回。正常调用返回如果有返回值,则把返回值压入调用者栈中,把pc计数器指向调用者下一条指令,继续调用者的执行。如果没有返回值则只设置pc计数器。异常返回则直接弹出栈帧,同样恢复调用者的栈帧和计数器,调用者根据是否捕捉异常决定是弹出栈帧到上层还是捕捉异常处理。
5、异常相关信息。栈帧中还必须保存一个到异常表的引用,当方法抛出异常时候进行处理。
6、其他信息,如调试相关信息。
以上3、4、5、6一起也称作帧数据区。
三、方法调用
分派是指根据参数和接受者?决定方法调用的版本。
1、静态分派和动态分派
根据接受者类型和参数类型,在编译器静态决定调用哪个方法叫做静态分派。根据接受者类型,在运行期动态决定调用哪个方法叫做动态分派。java中调用重载的方法属于静态分派,在编译器根据类型信息就决定了方法调用的版本。调用重写的方法属于动态分派,在运行期根据实际的类型信息决定调用方法的版本。
package com.yymt.jvm.method.dispatch; public class Dispatcher { static class Base { public void printMessage() { System.out.println("Base Message"); } } static class Sub extends Base { public void printMessage() { System.out.println("Sub Message"); } } public static void accept(Base base) { System.out.println("Accept Base"); } public static void accept(Sub sub) { System.out.println("Accept Sub"); } public static void staticDispatch() { Base base = new Base(); Base sub = new Sub(); accept(base); accept(sub); } public static void main(String[] args) { staticDispatch(); // System.out.println("========="); // dynamicDispatch(); } public static void dynamicDispatch() { Base base = new Base(); Base b2s = new Sub(); Sub sub = new Sub(); base.printMessage(); b2s.printMessage(); sub.printMessage(); } }
Accept Base Accept Base
调用accept方法的时候都是调用的accept(Base)方法,编译器已经根据参数的静态类型决定了调用的方法,从字节码(红色)就可以判定出来:
// Method descriptor #6 ()V
// Stack: 2, Locals: 2
public static void staticDispatch();
0 new com.yymt.jvm.method.dispatch.Dispatcher$Base [38]
3 dup
4 invokespecial com.yymt.jvm.method.dispatch.Dispatcher$Base() [40]
7 astore_0 [base]
8 new com.yymt.jvm.method.dispatch.Dispatcher$Sub [41]
11 dup
12 invokespecial com.yymt.jvm.method.dispatch.Dispatcher$Sub() [43]
15 astore_1 [sub]
16 aload_0 [base]
17 invokestatic com.yymt.jvm.method.dispatch.Dispatcher.accept(com.yymt.jvm.method.dispatch.Dispatcher$Base) : void [44]
20 aload_1 [sub]
21 invokestatic com.yymt.jvm.method.dispatch.Dispatcher.accept(com.yymt.jvm.method.dispatch.Dispatcher$Base) : void [44]
24 return
Line numbers:
[pc: 0, line: 26]
[pc: 8, line: 27]
[pc: 16, line: 28]
[pc: 20, line: 29]
[pc: 24, line: 30]
Local variable table:
[pc: 8, pc: 25] local: base index: 0 type: com.yymt.jvm.method.dispatch.Dispatcher.Base
[pc: 16, pc: 25] local: sub index: 1 type: com.yymt.jvm.method.dispatch.Dispatcher.Base
public static void dynamicDispatch() {
Base base = new Base();
Base b2s = new Sub();
Sub sub = new Sub();
base.printMessage();
b2s.printMessage();
sub.printMessage();
}
这个的输出大概都能猜出:
Base Message
Sub Message
Sub Message
调用printMessage的地方,都是运行期根据方法实际类型动态决定调用哪个类的实例方法的:
// Method descriptor #6 ()V
// Stack: 2, Locals: 3
public static void dynamicDispatch();
0 new com.yymt.jvm.method.dispatch.Dispatcher$Base [38]
3 dup
4 invokespecial com.yymt.jvm.method.dispatch.Dispatcher$Base() [40]
7 astore_0 [base]
8 new com.yymt.jvm.method.dispatch.Dispatcher$Sub [41]
11 dup
12 invokespecial com.yymt.jvm.method.dispatch.Dispatcher$Sub() [43]
15 astore_1 [b2s]
16 new com.yymt.jvm.method.dispatch.Dispatcher$Sub [41]
19 dup
20 invokespecial com.yymt.jvm.method.dispatch.Dispatcher$Sub() [43]
23 astore_2 [sub]
24 aload_0 [base]
25 invokevirtual com.yymt.jvm.method.dispatch.Dispatcher$Base.printMessage() : void [53]
28 aload_1 [b2s]
29 invokevirtual com.yymt.jvm.method.dispatch.Dispatcher$Base.printMessage() : void [53]
32 aload_2 [sub]
33 invokevirtual com.yymt.jvm.method.dispatch.Dispatcher$Sub.printMessage() : void [56]
36 return
Line numbers:
[pc: 0, line: 39]
[pc: 8, line: 40]
[pc: 16, line: 41]
[pc: 24, line: 42]
[pc: 28, line: 43]
[pc: 32, line: 44]
[pc: 36, line: 45]
Local variable table:
[pc: 8, pc: 37] local: base index: 0 type: com.yymt.jvm.method.dispatch.Dispatcher.Base
[pc: 16, pc: 37] local: b2s index: 1 type: com.yymt.jvm.method.dispatch.Dispatcher.Base
[pc: 24, pc: 37] local: sub index: 2 type: com.yymt.jvm.method.dispatch.Dispatcher.Sub
从上边的字节码出看两处aload_0/aload_1/aload_2分别从本地变量表中将base、b2s和sub引用压入栈中,invokevirtual指令会根据引用去引用指向的对象的类的方法表中查找具有相同名称和描述符的方法,如果找到了则直接调用;如果没有找到则去其父类的方法表中找,如果找到了则调用;如果没有找到继续向继承关系上级去找,如果找不到就抛出java.lang.AbstractMethodError。问题是,invokevirtual指令后边Constant_Methodref_info的直接引用是方法表的偏移量,在子类找和在父类查找的时候,怎么确保同样的偏移量指向的是相同签名的方法?如b2s和sub实例都指向相同的方法入口。下边讲到虚拟机动态分派的时候会讲到。
此处方法调用的直接引用是特定于hotspot vm的实现的。
2、单分派和多分派
先解释个名词,总量:方法的接受者和方法的参数一起被称作宗量。分派时候根据影响方法调用的宗量个数不同,分派又分为单分派和多分派,如果方法调用只受一个宗量影响的叫单分派,受多个宗量影响的叫多分派。来这里了解更多。Java语言目前为止属于静态多分派,动态单分派,根据java语言的不断发展也许以后会支持动态多分派的。java的静态多分派是指,方法调用在编译期根据方法接受者的静态类型和参数的静态类型共同决定;动态多分派是指,在运行期,究竟调用哪个方法只由接受者的静态类型决定。此处接受者在编译期和运行期都决定了调用哪个方法,算是影响了两次?
package com.yymt.jvm.method.dispatch; public class SinMulDispatcher { public static class Car { public void printName() { System.out.println("I'm a Car"); } } public static class BYDCar extends Car { public void printName() { System.out.println("I'm a BYD Car"); } } public static class Father { public void chooseCar(Car car) { System.out.println("Father choose Car"); } public void chooseCar(BYDCar car) { System.out.println("Father choose BYDCar"); } } public static class Son extends Father { public void chooseCar(Car car) { System.out.println("Son choose Car"); } public void chooseCar(BYDCar car) { System.out.println("Son choose BYDCar"); } } /** * @param args */ public static void main(String[] args) { Car car = new Car(); Car byd = new BYDCar(); Father father = new Father(); Father son = new Son(); father.chooseCar(car); son.chooseCar(byd); } }
输出为:
Father choose Car Son choose Car
在编译期,根据接受者静态类型Father和参数静态类型Car,一同决定了调用Father.chooseCar(Car),而不是Object.chooseCar:
// Method descriptor #15 ([Ljava/lang/String;)V
// Stack: 2, Locals: 5
public static void main(java.lang.String[] args);
0 new com.yymt.jvm.method.dispatch.SinMulDispatcher$Car [16]
3 dup
4 invokespecial com.yymt.jvm.method.dispatch.SinMulDispatcher$Car() [18]
7 astore_1 [car]
8 new com.yymt.jvm.method.dispatch.SinMulDispatcher$BYDCar [19]
11 dup
12 invokespecial com.yymt.jvm.method.dispatch.SinMulDispatcher$BYDCar() [21]
15 astore_2 [byd]
16 new com.yymt.jvm.method.dispatch.SinMulDispatcher$Father [22]
19 dup
20 invokespecial com.yymt.jvm.method.dispatch.SinMulDispatcher$Father() [24]
23 astore_3 [father]
24 new com.yymt.jvm.method.dispatch.SinMulDispatcher$Son [25]
27 dup
28 invokespecial com.yymt.jvm.method.dispatch.SinMulDispatcher$Son() [27]
31 astore 4 [son]
33 aload_3 [father]
34 aload_1 [car]
35 invokevirtual com.yymt.jvm.method.dispatch.SinMulDispatcher$Father.chooseCar(com.yymt.jvm.method.dispatch.SinMulDispatcher$Car) : void [28]
38 aload 4 [son]
40 aload_2 [byd]
41 invokevirtual com.yymt.jvm.method.dispatch.SinMulDispatcher$Father.chooseCar(com.yymt.jvm.method.dispatch.SinMulDispatcher$Car) : void [28]
44 return
Line numbers:
[pc: 0, line: 42]
[pc: 8, line: 43]
[pc: 16, line: 45]
[pc: 24, line: 46]
[pc: 33, line: 48]
[pc: 38, line: 49]
[pc: 44, line: 50]
Local variable table:
[pc: 0, pc: 45] local: args index: 0 type: java.lang.String[]
[pc: 8, pc: 45] local: car index: 1 type: com.yymt.jvm.method.dispatch.SinMulDispatcher.Car
[pc: 16, pc: 45] local: byd index: 2 type: com.yymt.jvm.method.dispatch.SinMulDispatcher.Car
[pc: 24, pc: 45] local: father index: 3 type: com.yymt.jvm.method.dispatch.SinMulDispatcher.Father
[pc: 33, pc: 45] local: son index: 4 type: com.yymt.jvm.method.dispatch.SinMulDispatcher.Father
在运行期,invokevirtual指令根据前边压入的调用者类型,动态决定分别调用了Father.chooseCar(Car)和Son.chooseCar(Car)。
3、虚拟机动态分派的实现
HotSpot VM虚拟机动态分派是通过方法表实现的。在jvm装载完类型后连接阶段的准备子阶段,会在方法区为类变量分配内存,同时会为别的结构分配内存,如方法表。而对象在内存中会有一个指向方法区的指针,可以通过对象来找到对象的方法表,进而找到方法。方法表里只有虚方法,即非静态、非私有、非初始化、非final的实例方法,也成为虚方法。常量池解析的时候,对于虚方法,直接引用会是方法表的偏移量。私有、静态、初始化、final方法都指向方法区中方法的直接地址的,运行期这种非虚方法很容易优化,不需要动态派发。
每个类型的方法表,都会包含超类的方法。超类方法在方法表中的顺序跟超类方法表顺序一致,这样就可以实现子类方法表索引跟父类方法表索引相同时候,指向的方法也相同。
四、字节码执行引擎
字节码执行是基于对操作数的出栈和入栈操作进行的,相对比较简单,没有寄存器。当然运行期优化时候把字节码编译为本地代码的时候,会充分利用机器的寄存器的。
评论
eclipse中直接打开class文件就看到了
发表评论
-
一次Direct buffer memory引发的OutOfMemoryError问题排查
2014-10-28 17:22 0留坑位 -
jvm运行期打印汇编信息
2014-04-09 23:00 3105如果只在jvm参数中加入-XX:+Prin ... -
Mac OSX 10.9 上build openjdk8和openjdk7
2014-03-29 18:29 14181先分享下自己build出来的fastdeb ... -
内存充足情况下应用一直CMS GC的问题分析
2014-03-26 22:39 0前几天日常上线发布后,收到大量的CMS GC ... -
查看java对象在内存中的布局
2014-03-20 22:39 12962接着上篇《一个对象占用多少字节?》中遇到的 ... -
一个对象占用多少字节?
2014-03-18 21:56 34846老早之前写过一篇博客,是关于一个Integ ... -
cpu字长、操作系统字长和jvm中各数据类型占用的字节数关系
2014-03-16 02:05 4654cpu字长是指cpu同时参与运算的二进制位 ... -
cache line对内存访问的影响
2014-03-12 20:48 1318cache line对内存访问的影响很早就 ... -
一次线上问题的排查过程——时钟精度变化导致的cpu占用率高的问题
2013-09-16 21:14 4072最近升级了一次tair(缓存系统)的cli ... -
Kilim源码分析之五 ---- 织入之变量活跃性分析
2013-03-20 21:00 1164/** * In live va ... -
Kilim源码分析之四 ---- 织入之内联subroutine
2013-03-20 20:00 1459小于1.5编译级别时,如果不显示inlin ... -
Kilim源码分析之三 ---- 织入之构造/合并BasicBlock
2013-03-20 19:50 1397上一篇分析 ... -
在编译级别1.4时jvm编译try/catch/finally块的方式
2013-03-12 21:22 1445先上一段很简单,且不考虑健壮性的源码: ... -
ASM4.0源码走读之三 readCode方法分析方法代码
2013-03-09 00:19 1716继第一篇,我们来看看readCode的代码 ... -
ASM4.0源码走读之二 指令的类型
2013-03-08 23:51 1654在深入分析ClassReader.read ... -
ASM4.0源码走读之一
2013-03-08 23:18 1770了解java class ... -
java协程框架----kilim实现机制解析
2013-03-08 16:14 6568java语言处理多任务的模式是基于多线程,java语言级 ... -
Kilim源码分析之二 ---- 织入入口及可织入判断
2013-03-20 19:33 30801、织入入口,配置 1.1、织入入口 ... -
由一个小程序引发的思考 — 关于字段和方法的分派
2011-11-05 14:32 1775面向对象三大特征封装 ... -
JVM学习笔记十四 之 线程模型和锁
2011-10-24 02:30 0os线程模型、jvm线程、java线程调度、状态 线程安全程 ...
相关推荐
jvm字节码自动加载jvm字节码自动加载jvm字节码自动加载jvm字节码自动加载jvm字节码自动加载
一份JVM学习的笔记,含查看JVM运行时信息\JVM垃圾收集信息\JVM锁信息等
java之jvm学习笔记五(实践写自己的类装载器)
JVM学习笔记.docx
java之jvm学习笔记十一(访问控制器)-源码
11.字节码执行
jVM学习笔记.ppt
这篇文章我们以输出 "Hello, World" 来开始字节码之旅,如果之前没有怎么接触过字节码的话,这篇⽂章应该能够让你对字节码有⼀个最基本的认识。
java之jvm学习笔记十而(访问控制器的栈校验机制)
从JVM内存模型、常用JVM参数、垃圾回收算法和垃圾回收器等几个角度学习JVM
JVM优化3(Tomcat参数调优,JVM参数调优,jvm字节码,代码优化),供大家查阅!!!!!!!!!!!!!!
java之jvm学习笔记十而(访问控制器的栈校验机制)-步骤2源码
JVM学习笔记(缓慢更新).md
自己总结的jvm中字节码与类的加载的笔记,绘制了详细的思维导图,每个思维导图中均有详细的博文解释,方便大家学习和理解,免费分享给大家。适合jvm的爱好者和学习者
JVM优化3(Tomcat参数调优,JVM参数调优,jvm字节码,代码优化).zip
jvm经典笔记
JVM 学习笔记(Java虚拟机)
详细介绍了JVM执行子系统的工作原理,包括类文件结构与字节码指令(Class类文件结构、JVM字节码指令简介)、JVM类加载机制(类加载器、类加载时机、类加载过程)、字节码执行引擎(运行时候的栈结构、方法调用、方法...
java之jvm学习笔记九(策略文件)