第三章 类文件结构与javap的使用
2016-02-18 13:22
633 查看
注:本文主要参考自《深入理解java虚拟机(第二版)》
1、javap的使用与类文件结构
使用过程:
java源代码:
View Code
说明:
上述文件中/*xxx*/这样的注释是我添加的,//这样的注释是javap自己生成的
需要知道的是,上述的文件并非是生成的*.class文件,*.class文件的内容是一串接近于机器码的十六进制字符,开头是一个魔数"0xCAFEBABE",该魔数是确定一个文件是否是class文件的标准。之后就是class编译版本(minor version,major version),然后下边的顺序与TCV.txt的顺序一样了。
在TCV.txt文件中,多了一个无参构造器方法,该无参构造器调用的是TestClass的父类Object的无参构造器(即执行了super()方法),这个无参构造器是在javac变异的第三步"语义分析"的时候添加的,具体的查看第二章 Javac编译原理
注意:
常量池的存放内容
存放所有的方法名
field名
方法签名(方法参数+返回值)
类型名
class文件中的常量值
常量池的前四部分可以称作是符号引用(即只有一些名称,但没有实际的地址,在运行期进行类的加载过后,会为这些东西分配实际的内存,到时候符号引用就会转化为直接引用,就能被JVM用了)
常量池的组成:符号引用、常量(这个常量包含我们代码中定义的常量,eg、字符串常量,也包括class文件中的常量,eg.SourceFile)。
主版本号的对应(eg.50对应jdk6,51对应jdk7),查看《深入理解java虚拟机(第二版)》P167
Stack:操作数栈的深度(这个值就是类加载阶段为操作数栈分配的深度)
Locals:局部变量的分配空间(单位是slot,不是个数),对于double和long这两个64bit的,需要两个slot,对于其他<=32bit的,只需要一个slot
Args_size:方法参数的个数,包括方法参数、this(this只针对实例方法,static方法不会自动添加this)
inc()方法:我详细注释了该方法的执行过程,这也就是JVM执行一个方法的基本流程(基于栈)
提醒:
Code部分是我们主要关注的部分,这一部分中关键的部分就是每一条字节码指令的意义是什么。具体的可以查看《深入分析Java Web技术内幕(修订版)》P124-P135
总结:
掌握类文件结构,有利于我们理解类加载机制,而了解了类加载机制,最直接的好处,就是我们可以自己编写类加载工具,例如,smarty框架就是自己编写了一个类加载器
读懂执行javap之后的字节码指令有利于我们理解java代码的执行流程,对我们定位问题也有一定的好处(虽然我在开发中还没有用这种方式定位过问题)
1、javap的使用与类文件结构
使用过程:
java源代码:
Compiled from "TestClass.java" public class compile.TestClass extends java.lang.Object SourceFile: "TestClass.java" /* 源文件名称 */ minor version: 0 /* 次版本号 */ major version: 50 /* 主版本号,50-->jdk6 */ Constant pool: /* 常量池:存放所有的方法名、field名、方法签名(方法参数+返回值)、类型名、class文件中的常量值 */ const #1 = Method #4.#18; // java/lang/Object."<init>":()V const #2 = Field #3.#19; // compile/TestClass.m:I const #3 = class #20; // compile/TestClass const #4 = class #21; // java/lang/Object const #5 = Asciz m; /*field名*/ const #6 = Asciz I; /*类型名*/ const #7 = Asciz <init>; /*方法名(构造器)*/ const #8 = Asciz ()V; /*方法签名(方法参数+返回值)*/ const #9 = Asciz Code; const #10 = Asciz LineNumberTable; /*class文件中的常量值:Java源码的行号与字节码指令对应关系*/ const #11 = Asciz LocalVariableTable; /*class文件中的常量值:局部变量表*/ const #12 = Asciz this; const #13 = Asciz Lcompile/TestClass;; /*当前类的类型"Lxxx;"表示xxx引用类型*/ const #14 = Asciz inc; /*方法名*/ const #15 = Asciz ()I; /*方法签名(方法参数+返回值)*/ const #16 = Asciz SourceFile; /*class文件中的常量值:源文件名称*/ const #17 = Asciz TestClass.java; /*class文件中的常量值:源文件名称*/ const #18 = NameAndType #7:#8;// "<init>":()V const #19 = NameAndType #5:#6;// m:I const #20 = Asciz compile/TestClass; /*类型名*/ const #21 = Asciz java/lang/Object; /*类型名*/ { public compile.TestClass(); Code: /* 方法字节码 */ /* Stack:操作数栈的深度(这个值就是类加载阶段为操作数栈分配的深度) * Locals:局部变量的分配空间(单位是slot,不是个数),对于double和long这两个64bit的,需要两个slot,对于其他<=32bit的,只需要一个slot * Args_size:方法参数的个数,包括方法参数、this(this只针对实例方法,static方法不会自动添加this) */ Stack=1, Locals=1, Args_size=1 0: aload_0 /*将第0个Slot中的引用类型的本地变量推到操作数栈顶,这里就是LocalVariableTable的this*/ 1: invokespecial #1; //Method java/lang/Object."<init>":()V /* invokespecial #1:调用#1常量代表的方法,这里就是super(),当前栈顶的元素作为该方法#1的接收者 */ 4: return /*返回该方法,该方法的返回值为Void,执行了return指令,方法结束*/ LineNumberTable: /* Java源码的行号与字节码指令对应关系 */ line 5: 0 LocalVariableTable: /* 局部变量表 */ Start Length Slot Name Signature 0 5 0 this Lcompile/TestClass; public int inc(); Code: Stack=2, Locals=1, Args_size=1 0: aload_0 /*将第0个Slot中的引用类型的本地变量推到操作数栈顶,这里就是LocalVariableTable的this*/ 1: getfield #2; //Field m:I /*getfield #2:获取常量表中定义的#2实例(即实例m),然后将m推到操作数栈顶*/ 4: iconst_1 /*向栈顶压入一个int常量1*/ 5: iadd /*将栈顶的两个元素相加(这里是1和m),然后将结果压入栈顶*/ 6: ireturn /*从当前方法返回栈顶的int型数值结果*/ LineNumberTable: line 9: 0 LocalVariableTable: Start Length Slot Name Signature 0 7 0 this Lcompile/TestClass; }
View Code
说明:
上述文件中/*xxx*/这样的注释是我添加的,//这样的注释是javap自己生成的
需要知道的是,上述的文件并非是生成的*.class文件,*.class文件的内容是一串接近于机器码的十六进制字符,开头是一个魔数"0xCAFEBABE",该魔数是确定一个文件是否是class文件的标准。之后就是class编译版本(minor version,major version),然后下边的顺序与TCV.txt的顺序一样了。
在TCV.txt文件中,多了一个无参构造器方法,该无参构造器调用的是TestClass的父类Object的无参构造器(即执行了super()方法),这个无参构造器是在javac变异的第三步"语义分析"的时候添加的,具体的查看第二章 Javac编译原理
注意:
常量池的存放内容
存放所有的方法名
field名
方法签名(方法参数+返回值)
类型名
class文件中的常量值
常量池的前四部分可以称作是符号引用(即只有一些名称,但没有实际的地址,在运行期进行类的加载过后,会为这些东西分配实际的内存,到时候符号引用就会转化为直接引用,就能被JVM用了)
常量池的组成:符号引用、常量(这个常量包含我们代码中定义的常量,eg、字符串常量,也包括class文件中的常量,eg.SourceFile)。
主版本号的对应(eg.50对应jdk6,51对应jdk7),查看《深入理解java虚拟机(第二版)》P167
Stack:操作数栈的深度(这个值就是类加载阶段为操作数栈分配的深度)
Locals:局部变量的分配空间(单位是slot,不是个数),对于double和long这两个64bit的,需要两个slot,对于其他<=32bit的,只需要一个slot
Args_size:方法参数的个数,包括方法参数、this(this只针对实例方法,static方法不会自动添加this)
inc()方法:我详细注释了该方法的执行过程,这也就是JVM执行一个方法的基本流程(基于栈)
提醒:
Code部分是我们主要关注的部分,这一部分中关键的部分就是每一条字节码指令的意义是什么。具体的可以查看《深入分析Java Web技术内幕(修订版)》P124-P135
总结:
掌握类文件结构,有利于我们理解类加载机制,而了解了类加载机制,最直接的好处,就是我们可以自己编写类加载工具,例如,smarty框架就是自己编写了一个类加载器
读懂执行javap之后的字节码指令有利于我们理解java代码的执行流程,对我们定位问题也有一定的好处(虽然我在开发中还没有用这种方式定位过问题)
相关文章推荐
- win7安装Genymotion 和 virtualBox eclipse插 使用离线ova 附下载
- RxJava 详解
- 1、MyEclipse的常用配置
- Java线程(五):Timer和TimerTask
- Java线程(四):线程中断、线程让步、线程睡眠、线程合并
- java文本编辑器插件kinderditor用法解决本地图片上传不能用
- 使用Jaxb中xjc命令由schema文件生成相应的实体类
- Java学习笔记2016.2.18 static、final关键字
- Java学习笔记2016.2.18 多态、抽象类、接口
- java中集合存储子类的泛型表示方法;
- Rxjava学习博客
- android 6.0不能在eclipse下生成R文件的问题
- java encapsulation
- eclipse创建一个标准的maven web项目
- [置顶] Java设计模式综述
- Java反射与内省
- Java 父类子类的对象初始化过程及例子
- JDK、JRE、JVM的区别及JavaSE、JavaEE和JavaME的区别
- Javaweb里面的filter,listener,servlet
- Eclipse Import Project