JVM学习笔记之五

13 篇文章 0 订阅

4. 类加载阶段

4.1 加载

  • 将类的字节码载入方法区中,内部采用C++instanceKlass描述java类,它的重要field有:
  • _java_mirror即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
  • _super 即父类
  • _methods 即方法
  • constants 即常量池
  • class_loader 即类加载器
  • vtable 虚方法表
  • _itable 接口放发表
  • 如果这个类还有父类没有加载,先加载父类
  • 加载和链接可能是交替运行的

注意

  • instanceKlass 这样的【元数据】是存储在方法区(1.8后的元空间内)但 _java_mirror 是存储在堆中
  • 可以通过HSDB工具查看

在这里插入图片描述

4.2 链接

  • 验证:验证类是否符合 JVM 规范,安全性检查
  • 用 UE 等支持二进制的编译器修改 HelloWorld.class 的魔数,在控制台运行
PS D:\view\out\production\view> java HelloWorld
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.UnsupportedClassVersionError: HelloWorld has been compiled by a more recent version of the Java Runtime (class file version 8244.8224), this version of the Java Runtime only recognizes class file versions up to 52.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
  • **准备:**为 static 变量分配空间,设置默认值
  • static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
  • static 变量分配空间和赋值时两个步骤,分配空间准备阶段完成,赋值在初始化阶段完成
  • 如果 static 变量时 final基本类型以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
  • 如果 static 变量时 final 的,但属于引用类型,那么赋值也会在初始化阶段完成
  • 解析:将常量池中的符号引用解析为直接引用
/**
 * 解析的含义
 */
public class Load1 {

    public static void main(String[] s) throws ClassNotFoundException {
        ClassLoader classLoader = Load1.class.getClassLoader();
        // loadClass 方法不会导致类的解析和初始化
        // 只会进行对类 C 的加载,并不会导致类 C 的解析以及初始化,从而类D也不会被加载、解析、初始化
        Class<?> c = classLoader.loadClass("C");
        
        // 这个就会C D 进行加载、解析、初始化
        //new C();
    }
}

class C {
    D d = new D();
}

class D {

}

4.3 初始化

<cinit>()v 方法
  • 初始化调用 <cinit>()v ,虚拟机会保证这个类的【构造方法】的线程安全
发生时机
  • 概括得说,类初始化时【懒惰的】
  • mian 方法所在的类,总会被首先初始化
  • 首次访问这个类的静态变量静态方法
  • 子类初始化,如果父类还没有初始化,会引发
  • 子类访问父类的静态方法,只会触发父类的初始化
  • Class.forName
  • new 会导致初始化
  • 不会导致初始化的情况
  • 访问类的 static final 静态常量(基本类型和字符串)不会触发初始化
  • 类对象 .class不会触发初始化
  • 创建该类的数组不会触发初始化
    类加载器的 loadClass 方法
  • Class.forName 的参数2false
  • 代码例子(一下代码需要自己一行一行运行)

public class Load2 {

    static {
        System.out.println("main init");
    }

    public static void main(String[] args) throws ClassNotFoundException {

        //================不会初始化=======================//
        
        // 1.静态常量不会触发初始化
        //System.out.println(B.b);
        // 2.类对象 .class 不会触发初始化
        //System.out.println(B.class);
        // 3.创建该类的数组不会触发初始化
        //System.out.println(new B[0]);
        // 4.不会初始化类B,但是会加载B、A
        /*ClassLoader c1 = Thread.currentThread().getContextClassLoader();
        c1.loadClass("B");*/
        // 5. 不会初始化类B,但是会加载B、A
        /*ClassLoader c2 = Thread.currentThread().getContextClassLoader();
        Class.forName("B", false, c2);*/

        //================会初始化=======================//
        
        // 1.首次访问这个类的静态变量或静态方法
        //System.out.println(A.a);
        // 2.子类初始化,如果父类还没有初始化,会引发
        //System.out.println(B.c);
        // 3.子类访问父类静态变量,只会触发父类初始化
        //System.out.println(B.a);
        // 4.会初始化类B,并先初始化类A
        //Class.forName("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");
    }
}

4.4 练习

  1. 从字节码角度分析,使用 a,b,c 三个常量是否会导致 E 的初始化
public class Load3 {

    public static void main(String[] args) {
        // 不会
        System.out.println(E.a);
        // 不会
        System.out.println(E.b);
        // 会
        System.out.println(E.c);
    }
}

class E {
    static {
        System.out.println("init E");
    }
    public static final int a = 10;
    public static final String b = "hello";
    public static final Integer c = 20;
}

===============E.class字节码=========================
static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String init E
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: bipush        20
        10: invokestatic  #5                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: putstatic     #6                  // Field c:Ljava/lang/Integer;
        16: return

  1. 典型应用 - 完成懒惰初始化单例模式
public class Singleton {
    
    private Singleton(){};
    
    // 内部类中保存单例
    private static class LazyHolder{
        static final Singleton INSTANCE = new Singleton();
    }
    
    // 第一次调用 getInstance 方法,才会导致内部类加载和初始化其静态变量
    public static Singleton getInstance(){
        return LazyHolder.INSTANCE;
    }
}
================特点===================================
1. 懒惰实例化
2. 初始时的线程安全是有保障的
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值