JVM----③类加载与字节码技术2

3. 编译期处理
4. 类加载阶段
5. 类加载器
6. 运行期优化

3. 编译期处理

所谓的 语法糖 ,其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利(给糖吃嘛)

注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外,编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记。

3.1 默认构造器
在这里插入图片描述
3.2 自动拆装箱
在这里插入图片描述
3.3 泛型集合取值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ParameterizedType 泛型的参数类型
在这里插入图片描述
输出:
在这里插入图片描述
3.4 可变参数
在这里插入图片描述
注意 如果调用了 foo() 则等价代码为 foo(new String[]{}) ,创建了一个空的数组,而不会传递 null 进去

3.5 foreach 循环
在这里插入图片描述
在这里插入图片描述
注意 foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用,其中 Iterable 用来获取集合的迭代器( Iterator )

3.6 switch 字符串
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.7 switch 枚举
在这里插入图片描述
转换后代码:
在这里插入图片描述
3.8 枚举类
在这里插入图片描述
在这里插入图片描述
3.9 try-with-resources
在这里插入图片描述
会被转换为:
在这里插入图片描述
为什么要设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常):
在这里插入图片描述
3.10 方法重写时的桥接方法
在这里插入图片描述
在这里插入图片描述
3.11 匿名内部类
在这里插入图片描述
在这里插入图片描述
匿名内部类接收了一个final变量:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4. 类加载阶段

4.1 加载

  • 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
    • _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
    • _super 即父类
    • _fields 即成员变量
    • _methods 即方法
    • _constants 即常量池
    • _class_loader 即类加载器
    • _vtable 虚方法表 -介绍多态介绍过
    • _itable 接口方法表
  • 如果这个类还有父类没有加载,先加载父类
  • 加载和链接可能是交替运行的
    在这里插入图片描述
    Class对象在堆中,每个对象的对象头有16个字节,其中8个字节对应绑定Class内存地址,如果你想访问Class信息,它通过对象头找到堆中的Class对象,进而找到元空间的 instanceKlass 的内存地址,当我们调用一些方法,属性时,它都是从元空间获取到这些Filed,Method 具体信息的。

4.2 链接
验证
在这里插入图片描述
准备

  • 为 static 变量分配空间,设置默认值 jdk8中static变量存储在堆中
    • static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
    • static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
    • 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
    • 如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成

解析
在这里插入图片描述

//loadClass方法不会导致类的解析和初始化
        ClassLoader classloader = Load2.class.getClassLoader();
        Class<?> c = classloader.loadClass("cn.jvm.t3.load.C");

在这里插入图片描述

new C();

在这里插入图片描述
将常量池中的符号引用解析为直接引用:就是将常量池代表的某个符号经过解析阶段可以直接找到堆内存中引用

4.3 初始化
<cinit>()V 方法
初始化即调用 <cinit>()V ,虚拟机会保证这个类的『构造方法』的线程安全
在这里插入图片描述
实验:

public class Load3 {
    static {
        System.out.println("main init");
    }
    public static void main(String[] args) throws ClassNotFoundException, IOException {
//        // 1. 静态常量不会触发初始化
        System.out.println(B.b);
//        // 2. 类对象.class 不会触发初始化  访问_java_mirror对象
        System.out.println(B.class);
//        // 3. 创建该类的数组不会触发初始化
        System.out.println(new B[0]);
        // 4. 不会初始化类 B,但会加载 B、A
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        cl.loadClass("cn.jvm.t3.load.B");
//        // 5. 不会初始化类 B,但会加载 B、A
        ClassLoader c2 = Thread.currentThread().getContextClassLoader();
        Class.forName("cn.jvm.t3.load.B", false, c2); //true时会
        System.in.read();


//        // 1. 首次访问这个类的静态变量或静态方法时
        System.out.println(A.a);
//        // 2. 子类初始化,如果父类还没初始化,会引发
        System.out.println(B.c);
//        // 3. 子类访问父类静态变量,只触发父类初始化
        System.out.println(B.a);
//        // 4. 会初始化类 B,并先初始化类 A
        Class.forName("cn.jvm.t3.load.B");
    }
}

class A {
    static int a = 0;
    static {
        System.out.println("a init");
    }
}

class B extends A {
    final static double b = 5.0;
    static boolean c = false;
    static {
        System.out.println("b init");
    }
}

练习:

public class Load4 {
    public static void main(String[] args) {
        System.out.println(E.a);//no
        System.out.println(E.b);//no
        System.out.println(E.c);//yes

    }
}
class E {
    public static final int a = 10;
    public static final String b = "hello";
    public static final Integer c = 20;  // Integer.valueOf(20)
    static {
        System.out.println("init E");
    }
}
  Last modified 2020-3-15; size 702 bytes
  MD5 checksum f0e9582c3e40e2360d0da9962a669f05
  Compiled from "Load4.java"
class cn.jvm.t3.load.E
  minor version: 0
  major version: 52
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#28         // java/lang/Object."<init>":()V
   #2 = Methodref          #29.#30        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Fieldref           #7.#31         // cn/itcast/jvm/t3/load/E.c:Ljava/lang/Integer;
   ......
{
  public static final int a;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 10

  public static final java.lang.String b;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String hello

  public static final java.lang.Integer c;
    descriptor: Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  cn.itcast.jvm.t3.load.E();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/itcast/jvm/t3/load/E;

  static {}; //初始化
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: bipush        20
         2: invokestatic  #2        // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: putstatic     #3        // Field c:Ljava/lang/Integer;
         8: getstatic     #4        // Field java/lang/System.out:Ljava/io/PrintStream;
        11: ldc           #5        // String init E
        13: invokevirtual #6        // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        16: return
      LineNumberTable:
        line 14: 0
        line 16: 8
        line 17: 16
}
SourceFile: "Load4.java"

典型应用 - 完成懒惰初始化单例模式

public class Load9 {
    public static void main(String[] args) {
        Singleton.test(); //不打印  lazy holder init
//        Singleton.getInstance(); // 打印 lazy holder init
    }

}
class Singleton {
    public static void test() {
        System.out.println("test");
    }
    private Singleton() {}

    private static class LazyHolder{
        private static final Singleton SINGLETON = new Singleton();
        static {
            System.out.println("lazy holder init");
        }
    }

    public static Singleton getInstance() {
        return LazyHolder.SINGLETON;
    }
}

5. 类加载器

以 JDK 8 为例:
在这里插入图片描述
5.1 启动类加载器
在这里插入图片描述

在这里插入图片描述
5.2 扩展类加载器

public class G {
    static {
//        System.out.println("ext G init");
        System.out.println("classpath G init");
    }
}
/**
 * 演示 扩展类加载器
 * 在 C:\Program Files\Java\jdk1.8.0_91 下有一个 my.jar
 * 里面也有一个 G 的类,观察到底是哪个类被加载了
 * jar -cvf my.jar cn/itcast/jvm/t3/load/G.class
 */
public class Load5_2 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.G");
        System.out.println(aClass.getClassLoader());
    }
}

在这里插入图片描述
5.3 双亲委派模式
在这里插入图片描述
5.4 线程上下文类加载器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
先看 2)发现它最后是使用 Class.forName 完成类的加载和初始化,关联的是应用程序类加载器,因此可以顺利完成类加载
再看 1)它就是大名鼎鼎的 Service Provider Interface (SPI)

在这里插入图片描述
在这里插入图片描述
5.5 自定义类加载器
在这里插入图片描述

public class Load7 {
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader();
        Class<?> c1 = classLoader.loadClass("MapImpl1");
        Class<?> c2 = classLoader.loadClass("MapImpl1");
        System.out.println(c1 == c2);//true

        MyClassLoader classLoader2 = new MyClassLoader();
        Class<?> c3 = classLoader2.loadClass("MapImpl1");
        System.out.println(c1 == c3);//false 类加载对象不是同一个

        c1.newInstance();
    }
}

class MyClassLoader extends ClassLoader {

    @Override // name 就是类名称
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String path = "e:\\myclasspath\\" + name + ".class";

        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            Files.copy(Paths.get(path), os);

            // 得到字节数组
            byte[] bytes = os.toByteArray();

            // byte[] -> *.class
            return defineClass(name, bytes, 0, bytes.length);

        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException("类文件未找到", e);
        }
    }
}

6. 运行期优化

6.1 即时编译分层编译

public class JIT1 {

    // -XX:+PrintCompilation -XX:-DoEscapeAnalysis
    public static void main(String[] args) {
        for (int i = 0; i < 200; i++) {
            long start = System.nanoTime();
            for (int j = 0; j < 1000; j++) {
                new Object();
            }
            long end = System.nanoTime();
            System.out.printf("%d\t%d\n",i,(end - start));
        }
    }
}

0	41714
1	42045
2	39397
...
109	10594
110	8607
111	8277
...
160	20194
161	331
162	331
...

原因是什么呢?
在这里插入图片描述
刚才的一种优化手段称之为【逃逸分析】,发现新建的对象是否逃逸。可以使用 -XX:-DoEscapeAnalysis 关闭逃逸分析,再运行刚才的示例观察结果

参考资料:https://docs.oracle.com/en/java/javase/12/vm/java-hotspot-virtual-machine-performance-enhancements.html#GUID-D2E3DC58-D18B-4A6C-8167-4A1DFB4888E4

方法内联
在这里插入图片描述

public class JIT2 {
    // -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:CompileCommand=dontinline,*JIT2.square(禁止内联)
    // -XX:+PrintCompilation

    public static void main(String[] args) {

        int x = 0;
        for (int i = 0; i < 500; i++) {
            long start = System.nanoTime();
            for (int j = 0; j < 1000; j++) {
                x = square(9);

            }
            long end = System.nanoTime();
            System.out.printf("%d\t%d\t%d\n",i,x,(end - start));
        }
    }

    private static int square(final int i) {
        return i * i;
    }
}
0	81	44694
1	81	65219
2	81	29464
...
294	81	15891
295	81	0
296	81	0

在这里插入图片描述
字段优化
JMH 基准测试请参考: http://openjdk.java.net/projects/code-tools/jmh/
在这里插入图片描述

@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Benchmark)
public class Benchmark1 {

    int[] elements = randomInts(1_000);

    private static int[] randomInts(int size) {
        Random random = ThreadLocalRandom.current();
        int[] values = new int[size];
        for (int i = 0; i < size; i++) {
            values[i] = random.nextInt();
        }
        return values;
    }

    @Benchmark
    public void test1() {
        for (int i = 0; i < elements.length; i++) {//运行
            doSum(elements[i]);
        }
    }

    @Benchmark
    public void test2() {
        int[] local = this.elements;//手动
        for (int i = 0; i < local.length; i++) {
            doSum(local[i]);
        }
    }

    @Benchmark
    public void test3() {
        for (int element : elements) {//编译
            doSum(element);
        }
    }

    static int sum = 0;

    @CompilerControl(CompilerControl.Mode.DONT_INLINE)
    static void doSum(int x) {
        sum += x;
    }


    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Benchmark1.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

在这里插入图片描述
在这里插入图片描述
6.2 反射优化

public class Reflect1 {

    public static void foo() {
        System.out.println("foo...");
    }

    public static void main(String[] args) throws Exception {
        Method foo = Reflect1.class.getMethod("foo");
        for (int i = 0; i <= 16; i++) {
            System.out.printf("%d\t", i);
            foo.invoke(null);
        }
        System.in.read();
    }
}

在这里插入图片描述
在这里插入图片描述
当执行到15次以上就会在本地生成方法访问器 MethodAccessorImpl,不会调用native方法了;将反射方法调用变成了直接方法调用。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

发布了138 篇原创文章 · 获赞 3 · 访问量 7247

猜你喜欢

转载自blog.csdn.net/weixin_43719015/article/details/104879925