4.1类文件结构
一个简单的HelloWorld.java
package cn.itcast.jvm.t5
//HelloWorld示例
public class HelloWorld{
public static void main(String[] args){
System.out.println("hello eorld");
}
}
执行javac -parameters -d . HelloWorld.java
ClassFile的类文件结构
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version;//Class 的小版本号
u2 major_version;//Class 的大版本号
u2 constant_pool_count;//常量池的数量
cp_info constant_pool[constant_pool_count-1];//常量池
u2 access_flags;//Class 的访问标记
u2 this_class;//当前类
u2 super_class;//父类
u2 interfaces_count;//接口
u2 interfaces[interfaces_count];//一个类可以实现多个接口
u2 fields_count;//Class 文件的字段属性
field_info fields[fields_count];//一个类会可以有多个字段
u2 methods_count;//Class 文件的方法数量
method_info methods[methods_count];//一个类可以有个多个方法
u2 attributes_count;//此类的属性表中的属性数
attribute_info attributes[attributes_count];//属性表集合
}
魔数
0~3字节,表示它是否是【class】类型的文件
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
版本
4~7字节,表示类的版本00 34(52)表示是Java8
0000000 ca fe ba be 00 00 00 34 00 23 0a 00 06 00 15 09
常量池
……
访问标识与继承信息
……
Field信息
……
Method信息
……
附加属性
……
4.2字节码指令
接着上一节,研究一下两组字节码指令,一个是:
public cn.itcast.jvm.t5.HelloWorld();构造方法的字节码指令
2a b7 00 01 b1
- 2a=>aload_0加载slot 0的局部变量,即this,做为下面的irvokespecial构造方法调用的参数
- b7=>invokespecial预备调用构造方法,哪个方法呢?
- 00 01引用常量池中#1项,即【
Method java/lang/Object."<init> ":() V】 - b1表示返回
另一个是public static void main(java.lang.String[]);主方法的字节码指令
b2 00 02 12 03 b6 00 04 b1
- b2=>getstatic用来加载静态变量,哪个静态变量呢?
- 0002引用常量池中#2 项, 即 【Field java/lang/System.out:Ljava/io/PrintStream;
- 12=>ldc加载参数,哪个参数呢?
- 03引用常量池中#3项,即【string hello world】
- b6=> invokevirtual预备调用成员方法,哪个方法呢?
- 00 04引用常量池中#4项, 即【Method java/io/PrintStream.println:(Ljava/lang/String;)V】
- b1表示返回
javap工具
自己分析类文件结构太麻烦了,Oracle 提供了javap工具来反编译class文件
[root@localhost ~]# javap -v HelloWorld.class
……
……
(后续补充)
4.3编译期处理
所谓的语法糖,其实就是指java编译器把*.java源码编译为*.class字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是java编译器给我们的一个额外福利(给糖吃嘛)
注意,以下代码的分析,借助了javap工具,idea的反编译功能,idea 插件jclasslib等工具。另外,编译器转换的结果直接就是class字节码,只是为了便于阅读,给出了几乎等价的java源码方式,并不是编译器还会转换出中间的java 源码,切记。
默认构造器
public class Candy1 {
}
编译成class后的代码:
public class Candy1 {
//这个无参构造是编译器帮助我们加上的
public Candy1(){
super();//即调用父类object 的无参构造方法,即调用 java/lang/object."<init>":()V
}
}
自动拆装箱
这个特性是JDK5开始加入的,代码片段1:
public class Candy2 {
public static void main(String[] args) {
Integer x = 1;
int y = x;
}
}
这段代码在JDK5之前是无法编译通过的,必须改写为代码片段2:
public class Candy2 {
public static void main(String[] args){
Integer x = Integer.valueOf(1);
int y = x.intValue();
}
}
显然之前版本的代码太麻烦了,需要在基本类型和包装类型之间来回转换(尤其是集合类中操作的都是包装类型),因此这些转换的事情在JDK5以后都由编译器在编译阶段完成。即代码片段1都会在编译阶段被转换为代码片段2
泛型集合取值
泛型也是在JDK5开始加入的特性,但java在编译泛型代码后会执行泛型擦除的动作,即泛型信息在编译为字节码之后就丢失了,实际的类型都当做了object类型来处理:
public class Candy3{
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
list.add(10);//实际调用的是 List.add(Object e)
Integer x=list.get(0);//实际调用的是 Object obj=List.get(int index);
}
}
所以在取值时,编译器真正生成的字节码中,还要额外做一个类型转换的操作:
//需要将Object转为 Integer
Integer × = (Integer)list.get(0);
如果前面的x变量类型修改为int基本类型那么最终生成的字节码是:
//需要将 Object转为 Integer,并执行拆箱操作
int x =((Integer)list.get(0)).intValue();
还好这些麻烦事都不用自己做。
可变参数
待补充
4.4类加载阶段
待补充
4.5类加载器
待补充
4.6运行期处理优化
待补充

7700

被折叠的 条评论
为什么被折叠?



