Class类文件结构
JVM的无关性
- 平台无关性:一次编写,到处运行
- 语言无关性:字节码(Byte-Code)
- .java通过javac编译成class文件
- .rb通过jrubyc编译成class
- .groovy通过groovyc编译器编译成class
- 其他于洋通过对应的编译器编译成class文件,kafka是通过scala写的,也是可以在jvm上运行的
字节码
-
向后兼容性比较好
- class在jdk1.2就稳定下来了,基本上没修改了
-
字节码除了class文件这种磁盘文件,也可以通过类加载器把class文件加载到内存中,甚至可以动态生成
字节码查看工具
-
Sublime
- 打开是十六进制文件
- 其实class文件是8位字节为基础的二进制流文件
-
javap
- javap -v class文件名
-
jclasslib
- idea中 view->Show Bytecode With jclasslib
-
jd-gui.exe
- 反汇编软件
Class文件格式详解
1.Class文件是一组以8位字节为基础单位的二进制流
-
里面的内容非常紧凑,没有分隔符
-
magic(u4):魔数,4个字节,2个16进制位标识1个字节(8位二进制表示1个字节),所以需要8位16进制,或者32位二进制
- class文件的开头必须是cafe babe(十六进制展示),说明是合法的class文件
-
minor_verion(u2):小版本,2个字节,4位16进制,或者16位二进制
- cafe babe后面跟的数0000
-
major_version(u2):大版本,2个字节,4位16进制,或者16位二进制
- 0000后面的0034,转换成10进制就是52,52.0就是jdk1.8的默认值,jdk1.7默认是51.0,整数是大版本,小数是小版本
-
constant_pool_count(u2):常量值容量计数值,计数是从1开始,而不是0!所以16代表15项常量
- 0034后面的0010,转化成10进制就是16
-
constant_pool
-
①CONSTANT_Utf8_info
-
tag(u1):值为1
-
length(u2):UTF-8字符串所占的字节数
-
bytes(u1):长度为length的字符串
-
-
②CONSTANT_Integer_info
- tag(u1):值为3
- bytes(u4):按照最高位在前存储的int
-
③CONSTANT_Float_info
- tag(u1):值为4
- bytes(u4):按照最高位在前存储的float
-
④CONSTANT_Long_info
- tag(u1):值为5
- bytes(u8):按照最高位在前存储的long
-
⑤CONSTANT_Double_info
- tag(u1):值为6
- bytes(u8):按照最高位在前存储的double
-
⑥CONSTANT_Class_info
- tag(u1):值为7
- index(u2):指向全限定名常量项的索引
-
⑦CONSTANT_String_info
- tag(u1):值为8
- index(u2):指向字符串字面量的索引
-
⑧CONSTANT_Fieldref_info
- tag(u1):值为9
- index(u2):指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项
- index(u2):指向字段描述符CONSTANT_NameAndType_info的索引项
-
⑨CONSTANT_Methodref_info
- tag(u1):值为10
- index(u2):指向声明方法的类描述符CONSTANT_Class_info的索引项
- index(u2):指向名称及类型描述符CONSTANT_NameAndType_info的索引项
-
⑩CONSTANT_InterfaceMethodref_info
- tag(u1):值为11
- index(u2):指向声明方法的接口描述符CONSTANT_Class_info的索引项
- index(u2):指向名称及类型描述符CONSTANT_NameAndType_info的索引项
-
⑪CONSTANT_NameAndType_info
- tag(u1):值为12
- index(u2):指向该字段或方法名称常量的索引
- index(u2):指向该字段或方法描述符常量项的索引
-
⑫CONSTANT_MethodHandle_info
- tag(u1):值为15
- reference_kind(u1):值在1~9之间,它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为
- reference_index(u2):值必须是对常量池的有效索引
-
⑬CONSTANT_MethodType_info
- tag(u1):值为16
- descriptor_index(u2):值必须是堆常量池的有效索引,常量池在该索引下的项必须是CONSTANT_Utf8_info接口,表示方法的描述符
-
⑭CONSTANT_InvokeDynamic_info
- tag(u1):值为18
- bootstrap_method_attr_index(u2):值必须是对当前class文件中引导方法表的bootstrap_methods[]数组的有效索引
- name_and_type_index(u2):值必须是对当前常量池的有效索引,常量池在该索引出的项必须是CONSTANT_MethodType_info结构,表示方法名和方法描述符
-
可扩展性还是很强,比如可以新增一个13的tag
-
-
access_flags(u2):表示某个类或者接口的访问权限和属性
- public、private等等
-
this_class(u2):类索引,该值必须是对常量池中某个常量的一个有效索引值,该索引处的成员必须是一个CONSTANT_Class_info类型的结构体,表示这个class文件所定义的类或者接口
- 找到这个类的索引,为了能快速把这个new出来
-
super_class(u2):父类索引
- 父类的类索引,也是为了快速找到父索引
-
interfaces_count(u2):接口计数器,表示当前类或者接口直接继承接口的数量
- 表示当前类继承的接口数量
-
interfaces(u2):接口表,是一个表结构,成员同this_class,是对常量池中CONSTANT_Class_info类型的一个有效索引值
-
fields_count(u2):字段计数器,当前class文件所有字段的数量
-
fields:字段表,是一个表结构,表中每个成员必须是field_info数据结构,用于表示当前类或者接口的某个字段的完整描述,但它不包含从父类或父接口继承的字段
-
methods_count(u2):方法计数器,表示当前类方法表的成员个数
-
methods:方法表,是一个表结构,表中每个成员必须是method_info数据结构,用于表示当前类或者接口的某个方法的完整描述
-
attributes_count(u2):属性计数器,表示当前class文件attributes属性表的成员个数
-
attributes:属性表,是一个表结构,表中每个成员必须是attribute_info数据结构,这里的属性是对class文件本身,方法或者字段的补充描述,如SourceFile属性用于表示class文件的源代码文件名
-
这里的常量池是之前说的class常量池,类加载后就会把一些参数存入方法区,像code、静态变量、常量,进入方法区后就是动态常量池或者运行时常量池
实例
-
package ex6; /** * @author King老师 * 字节码分析 */ public class ByteCode { public ByteCode(){ } }
-
Methods
- [0]
- [0] Code
- [0] LineNumberTable
- [1] LocalVariableTable
- [0] Code
- [0]
-
其中code里面对应的是字节码指令
2.类似于结构体的伪结构来存储数据
3.只有两种数据类型:无符号数和表
- 无符号数,是确定长度的
- 常量池中的大部分量
- 表,首先是有个计数,
4.无符号数属于基本的数据类型,以u1、u2、u4、u8
- u1表示1个字节
5.表是由多个无符号数或者其他表作为数据项构成的复合数据类型
字节码指令
-
简介和重要性
-
指令和数据类型
-
指令分类,及部分具体指令
- 加载和存储指令
- 加载指令:aload、iload
- 存储指令:istore、astore、dstore、fstore
- 运算指令
- 加法指令:iadd
- 减法指令:isub
- 乘法指令:imul
- 除法指令:idiv
- …
- 类型转换指令
- int转换为long:i2l
- …
- 对象创建与访问指令
- 对象的创建:new、newarray
- …
- 操作数栈管理指令
- 出栈:pop
- 复制栈顶的元素并压入栈:dup
- …
- 控制转移指令
- 当栈顶int类型元素 等于0时:ifeq
- 当栈顶int类型元素 不等于0时:ifne
- 当栈顶int类型元素 小于0时:iflt
- 当栈顶int类型元素 大于0时:ifgt
- …
- 异常指令
- athrow
- 同步指令
- monitorenter+monitorexit
- 加载和存储指令
-
字节码指令的速度是很快的,是纳秒级别的,代码哪怕写了1000行,但是都是一些简单的if-else,速度不会满,JVM不会对代码数量做限制,真正影响速度的是线程的上下文切换
字节码指令——异常处理
实例
-
package ex6; /** * @author King老师 * 在 synchronized 生成的字节码中,其实包含两条 monitorexit 指令,是为了保证所有的异常条件,都能够退出 * 这就涉及到了 Java 字节码的异常处理机制 */ public class SynchronizedDemo { synchronized void m1(){ System.out.println("m1"); } static synchronized void m2(){ System.out.println("m2"); } final Object lock=new Object(); void doLock(){ synchronized (lock){ System.out.println("lock"); } } }
-
doLock()方法的字节码文件
0 aload_0 1 getfield #3 <ex6/SynchronizedDemo.lock> 4 dup 5 astore_1 6 monitorenter 7 getstatic #4 <java/lang/System.out> 10 ldc #8 <lock> 12 invokevirtual #6 <java/io/PrintStream.println> 15 aload_1 16 monitorexit 17 goto 25 (+8) 20 astore_2 21 aload_1 22 monitorexit 23 aload_2 24 athrow 25 return
会发现其中有两个monitorexit
java异常处理
-
Throwable
- Error
- Exception
- RuntimeException
-
Error和RuntimeException属于非检查型异常
-
方法执行的过程中,如果是正常执行,就会有对应的虚拟机栈(栈帧—局部变量表、操作数栈、完成出口),最后从完成出口返回,但是如果代码有异常呢?
异常表
-
SynchronizedDemo.class中doLock方法的异常表
-
stack=2, locals=3, args_size=1 0: aload_0 1: getfield #3 // Field lock:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter 7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #8 // String lock 12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: aload_1 16: monitorexit 17: goto 25 20: astore_2 21: aload_1 22: monitorexit 23: aload_2 24: athrow 25: return
-
Exception table: from to target type 7 17 20 any 20 23 20 any
-
from-to-target 从第7行执行到第17行,如果发生了异常,转跳到20行
-
finally
实例1
-
package ex6; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; /** * @author King老师 * finally字节码的处理 */ public class StreamDemo { public void read(){ InputStream in = null; try { in = new FileInputStream("A.java"); }catch(FileNotFoundException e){ e.printStackTrace(); } finally { if (null != in) { try { in.close(); }catch(IOException e) { e.printStackTrace(); } } } } }
-
方法中的字节码
-
Code: stack=3, locals=5, args_size=1 0: aconst_null 1: astore_1 2: new #2 // class java/io/FileInputStream 5: dup 6: ldc #3 // String A.java 8: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V 11: astore_1 12: aconst_null 13: aload_1 14: if_acmpeq 79 17: aload_1 18: invokevirtual #5 // Method java/io/InputStream.close:()V 21: goto 79 24: astore_2 25: aload_2 26: invokevirtual #7 // Method java/io/IOException.printStackTrace:()V 29: goto 79 32: astore_2 33: aload_2 34: invokevirtual #9 // Method java/io/FileNotFoundException.printStackTrace:()V 37: aconst_null 38: aload_1 39: if_acmpeq 79 42: aload_1 43: invokevirtual #5 // Method java/io/InputStream.close:()V 46: goto 79 49: astore_2 50: aload_2 51: invokevirtual #7 // Method java/io/IOException.printStackTrace:()V 54: goto 79 57: astore_3 58: aconst_null 59: aload_1 60: if_acmpeq 77 63: aload_1 64: invokevirtual #5 // Method java/io/InputStream.close:()V 67: goto 77 70: astore 4 72: aload 4 74: invokevirtual #7 // Method java/io/IOException.printStackTrace:()V 77: aload_3 78: athrow 79: return
-
-
异常表
-
Exception table: from to target type 17 21 24 Class java/io/IOException 2 12 32 Class java/io/FileNotFoundException 42 46 49 Class java/io/IOException 2 12 57 any 32 37 57 any 63 67 70 Class java/io/IOException
-
IOException的catch语句只有一条,为什么在异常表里有3个IOException?
把finally的异常表信息,在try块和catch块中各复制一份
-
实例2
-
package ex6; /** * @author King老师 * 加了finally为啥不会异常 */ public class NoError { public static void main(String[] args) { NoError noError =new NoError(); noError.read(); } volatile int kk =0; public int read(){ try { int a = 13/0; return a; }finally { return 1; } } }
-
Code: stack=2, locals=4, args_size=1 0: bipush 13 2: iconst_0 3: idiv 4: istore_1 5: iload_1 6: istore_2 7: iconst_1 8: ireturn 9: astore_3 10: iconst_1 11: ireturn Exception table: from to target type 0 7 9 any
-
为什么加了finnaly不会抛出异常?
- 根据异常表信息,如果try块抛出了异常,则跳到finally中
字节码指令——装箱拆箱
-
八大基础数据类型
- byte、short、int、long
- char
- boolean
- float、double
-
包装类型
-
比如int的包装类型是Integer
为什么要有包装类?int无法表示null值,因为数据库里面有null值,无法表示
-
装箱拆箱字节码层面分析
实例
-
package ex6; /** * @author King老师 * 装箱拆箱字节码层面分析 */ public class Box { public Integer cal() { Integer a = 1000; int b = a * 10; return b; } }
-
Code: stack=2, locals=3, args_size=1 0: sipush 1000 3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 6: astore_1 7: aload_1 8: invokevirtual #3 // Method java/lang/Integer.intValue:()I 11: bipush 10 13: imul 14: istore_2 15: iload_2 16: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 19: areturn
- Integer a = 1000;这行代码自动加了一条字节码invokestatic,调用了Integer.valueOf方法,自动装箱
- int b = a * 10; 这行代码会调用Integer.intValue方法,自动拆箱
IntegerCache
-
Integer.valueOf方法里面的
-
这个类里面有一个cache[]
- 最小是-128
- 最大是high,默认情况是127
-
package ex6; /** * @author King老师 * IntegerCache及修改 * -XX:AutoBoxCacheMax=256 */ public class BoxCache { public static void main(String[] args) { Integer n1 = 123; //new一东西 Integer n2 = 123; Integer n3 = 128; Integer n4 = 128; System.out.println(n1 == n2);// true System.out.println(n3 == n4);// false } }
-
n2是从缓存中去的,而n3超过了缓存范围,所以没有缓存
-
-XX:AutoBoxCacheMax=256,设置缓存的最大值是256
-
字节码指令——透析其他底层技术
数组访问
- 数组是一种内置的对象类型,继承自Object类
实例
-
package ex6; public class ArrayDemo { int getValue() { int[] arr = new int[]{1111, 2222, 3333, 4444}; return arr[2]; } int getLength(int[] arr) { return arr.length; } }
-
字节码1
-
int getValue(); descriptor: ()I flags: Code: stack=4, locals=2, args_size=1 0: iconst_4 1: newarray int 3: dup 4: iconst_0 5: sipush 1111 8: iastore 9: dup 10: iconst_1 11: sipush 2222 14: iastore 15: dup 16: iconst_2 17: sipush 3333 20: iastore 21: dup 22: iconst_3 23: sipush 4444 26: iastore 27: astore_1 28: aload_1 29: iconst_2 30: iaload 31: ireturn LineNumberTable: line 5: 0 line 6: 28 LocalVariableTable: Start Length Slot Name Signature 0 32 0 this Lex6/ArrayDemo; 28 4 1 arr [I
-
newarray:创建数组的指令
-
iconst_0:把数组下标0压入操作数栈
-
sipush 1111:把常量1111压入操作数栈
-
iastore:把1111塞到创建的数组中,并且下标为0
-
其余的数组元素过程相同
-
aload_1:本地引用的变量推送至栈顶,也就是本地变量表中的第2个变量,也就是生成一个数组
-
iconst_2:再推一个2,因为数组中下标为2的元素
-
iaload:把指定的索引2下面对应的值推送至栈顶
-
-
字节码2
-
int getLength(int[]); descriptor: ([I)I flags: Code: stack=1, locals=2, args_size=2 0: aload_1 1: arraylength 2: ireturn LineNumberTable: line 9: 0 LocalVariableTable: Start Length Slot Name Signature 0 3 0 this Lex6/ArrayDemo; 0 3 1 arr [I
-
arraylength:获取数组长度的指令
-
foreach
- 对数组、list进行遍历,java代码看上去是一样的,实际上生成的字节码是不一样的
实例
-
package ex6; import java.util.List; public class ForDemo { void loop(int[] arr) { for (int i : arr) { System.out.println(i); } } void loop(List<Integer> arr) { for (int i : arr) { System.out.println(i); } } }
-
字节码1
-
void loop(int[]); descriptor: ([I)V flags: Code: stack=2, locals=6, args_size=2 0: aload_1 1: astore_2 2: aload_2 3: arraylength 4: istore_3 5: iconst_0 6: istore 4 8: iload 4 10: iload_3 11: if_icmpge 34 14: aload_2 15: iload 4 17: iaload 18: istore 5 20: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 23: iload 5 25: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 28: iinc 4, 1 31: goto 8 34: return LineNumberTable: line 6: 0 line 7: 20 line 6: 28 line 9: 34 LocalVariableTable: Start Length Slot Name Signature 20 8 5 i I 0 35 0 this Lex6/ForDemo; 0 35 1 arr [I StackMapTable: number_of_entries = 2 frame_type = 254 /* append */ offset_delta = 8 locals = [ class "[I", int, int ] frame_type = 248 /* chop */ offset_delta = 25
-
在数组中,foreach就是标准的for循环的写法
-
-
字节码2
-
void loop(java.util.List<java.lang.Integer>); descriptor: (Ljava/util/List;)V flags: Code: stack=2, locals=4, args_size=2 0: aload_1 1: invokeinterface #4, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 6: astore_2 7: aload_2 8: invokeinterface #5, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z 13: ifeq 39 16: aload_2 17: invokeinterface #6, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 22: checkcast #7 // class java/lang/Integer 25: invokevirtual #8 // Method java/lang/Integer.intValue:()I 28: istore_3 29: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 32: iload_3 33: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 36: goto 7 39: return LineNumberTable: line 11: 0 line 12: 29 line 13: 36 line 14: 39 LocalVariableTable: Start Length Slot Name Signature 29 7 3 i I 0 40 0 this Lex6/ForDemo; 0 40 1 arr Ljava/util/List; LocalVariableTypeTable: Start Length Slot Name Signature 0 40 1 arr Ljava/util/List<Ljava/lang/Integer;>; StackMapTable: number_of_entries = 2 frame_type = 252 /* append */ offset_delta = 7 locals = [ class java/util/Iterator ] frame_type = 250 /* chop */ offset_delta = 31
-
在list中,是使用的迭代器的方法,localIterator.next()是否存在下一个
-
注解
实例
-
package ex6; public @interface KingAnnotation { }
-
package ex6; @KingAnnotation public class AnnotationDemo { @KingAnnotation public void test(@KingAnnotation int a){ } }
-
常量池
-
Constant pool: #1 = Methodref #3.#20 // java/lang/Object."<init>":()V #2 = Class #21 // ex6/AnnotationDemo #3 = Class #22 // java/lang/Object #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = Utf8 LineNumberTable #8 = Utf8 LocalVariableTable #9 = Utf8 this #10 = Utf8 Lex6/AnnotationDemo; #11 = Utf8 test #12 = Utf8 (I)V #13 = Utf8 a #14 = Utf8 I #15 = Utf8 RuntimeInvisibleAnnotations #16 = Utf8 Lex6/KingAnnotation; #17 = Utf8 RuntimeInvisibleParameterAnnotations #18 = Utf8 SourceFile #19 = Utf8 AnnotationDemo.java #20 = NameAndType #4:#5 // "<init>":()V #21 = Utf8 ex6/AnnotationDemo #22 = Utf8 java/lang/Object
-
-
字节码1,加在类上面的注解,在class文件的最后多了两行
-
RuntimeInvisibleAnnotations: 0: #16()
#16对应于常量池
-
-
字节码2,加在方法上面的注解
-
public void test(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=0, locals=2, args_size=2 0: return LineNumberTable: line 7: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Lex6/AnnotationDemo; 0 1 1 a I RuntimeInvisibleAnnotations: 0: #16() RuntimeInvisibleParameterAnnotations: 0: 0: #16()
-
深入JVM即时编译器JIT
- just in time
什么是JIT?
- 1.首先.java文件首先被编译成class文件的字节码,
- 2.判断代码是否是热点代码
- 调用比较频繁的代码才是热点代码
- 而且如果是热点代码,会将机器码缓存下来
- 3.如果是热点代码,是通过JIT Compiler编译执行,然后转换成机器码
- 4.如果不是热点点吗,就是通过Interpreter解释执行,转成成机器码
热点探测
- 方法调用计数器
- 计算方法调用了多少次
- 比如在C1模式下1500次,或者C2模式下10000次就是热点代码
- 回边计数器
- 在一个方法里面可能写for循环,可能for循环很大,每一次都回到一个边,字节码遇到控制指令又会跳回去
- C1模式下:13995,C2模式下:10700
即时编译器
- C1
- C2