java虚拟机6

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
  • 其中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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值