JVM底层类加载

JVM底层认知


前言

学习JVM专题,写文章记录笔记,以便于之后的复习与巩固

一、Klass模型

Klass模型类的继承结构
klass模型
由图可以得出,通过继承关系可以得出,类的元信息存储在元空间中,Klass分为InstanceKlass与ArrayKlass。
普通的java类在JVM中对应的是InstanceKlass类的实例。InstanceKlass的三个子类如下:

  1. InstanceMirrorKlass(镜像类):用于表示java.lang.class,java代码中获取到的Class对象,实际上就是这个C++类的实例,存储在堆区中
  2. InstanceRefKlass:是用于java.lang.ref.Reference的子类
  3. InstanceClassLoaderKlass:用于遍历某个加载器加载的类

java中的数组不是静态数据类型,而是动态的数据类型,即在运行期才会生成对象。java数组的元信息用ArrayKlass的子类来表示。

  1. TypeArrayKlass:表示java中基本类型数据组的数据结构。newarray 创建一个指定原始类型(八大基本数据类型)的数组,并将其引用压入栈顶。
  2. ObjArrayKlass:表示java中引用类型数组的数据结构。anewarray创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶

二、类加载过程

类加载生命周期分为七个步骤类加载生命周期
类加载过程为前五个阶段

加载

  • 通过类的全限定名(包+类名)获取存储该类的Class文件(不指名从哪里获取)
  • 解析成运行时数据,即InstanceKlass实例,存放于方法区
  • 在堆区生成该类的Class对象,即InstanceMirrorKlass实例

    什么时候加载?

在主动使用时就会加载:

  • new、getstatic、putstatic、invokestatic
  • 反射
  • 初始化一个类的子类会去加载其父类
  • 启动类(main函数所在类)

预加载:包装类、String、Thread

加载时因为没有指明必须从哪获取class文件,便有了多种加载的方式

  • 从压缩包中读取,如jar、war
  • 从网络中获取,如Web Applet
  • 动态生成,如动态代理、CGLIB
  • 由其他文件生成,如JSP
  • 从数据库读取
  • 从加密文件中读取

验证

  • 文件格式验证
  • 元数据验证
  • 字节码验证
  • 符号引用验证

准备

这一步为静态变量分配内存,赋值初值。
对于实例变量没有赋值初值这一说法,产生实例后给以赋值。
基本数据类型初值
如果被final修饰,在编译时会给属性添加ConstantValue属性,准备阶段直接完成赋值,即无赋初值这一说。

解析

将常量池中的符号引用转为直接引用(间接引用 -> 直接引用),解析后的信息存放于ConstantPoolCache类实例中。
1、类或接口的解析
2、字段解析
3、方法解析
4、接口方法解析

初始化

执行静态代码块,完成静态变量的赋值。
静态字段、静态代码段,字节码层面会生成clinit方法
方法中语句的先后顺序与代码的编写顺序一至


三、类加载案例

案例一:

public class JvmClassTest1 {
    public static void main(String[] args) {
        System.out.println(Jvm_1_Test_B.Str);
    }
}

class Jvm_1_Test_A{
    public static String Str = "Str A";
    static {
        System.out.println("A Static Block");
    }
}

class Jvm_1_Test_B extends Jvm_1_Test_A{
    static {
        System.out.println("B Static Block");
    }
}
输出结果:  
A Static Block
Str A

懒加载,根本就没有使用到Jvm_1_Test_B 。
str是类Jvm_1_Test_A的静态属性,可以看到不会存储到子类Jvm_1_Test_B的镜像类(InstanceMirrorKlass)中
可以猜得到,通过子类Jvm_1_Test_B访问父类Jvm_1_Test_A的静态字段有两种实现方式:
1、先去Jvm_1_Test_B的镜像类中去取,如果有直接返回;如果没有,会沿着继承链将请求往上抛。很明显,这种算法的性能随继承链的death而上升,算法复杂度为O(n)
2、借助另外的数据结构实现,使用K-V的格式存储,查询性能为O(1)

案例二:

public class JvmClassTest1 {
    public static void main(String[] args) {
        System.out.println(new Jvm_1_Test_B().Str);
    }
}

class Jvm_1_Test_A{
    public static String Str = "Str A";
    static {
        System.out.println("A Static Block");
    }
 }

class Jvm_1_Test_B extends Jvm_1_Test_A{
    static {
        System.out.println("B Static Block");
    }
}
输出结果:  
A Static Block
B Static Block
Str A

案例三:

public class JvmClassTest1 {
    public static void main(String[] args) {
        System.out.println(Jvm_1_Test_B.Str);
    }
}

class Jvm_1_Test_A{
    public static String Str = "Str A";
    static {
        System.out.println("A Static Block");
    }
}

class Jvm_1_Test_B extends Jvm_1_Test_A{
    public static String Str = "Str B";
    static {
        System.out.println("B Static Block");
    }
}
输出结果:  
A Static Block
B Static Block
Str B

案例四:

public class JvmClassTest2 {
    public static void main(String[] args) {
        Jvm_2_Test_A testA = Jvm_2_Test_A.getInstance_A();
        System.out.println(Jvm_2_Test_A.intvalue1);
        System.out.println(Jvm_2_Test_A.intvalue2);
    }
}

class Jvm_2_Test_A{
    public static int intvalue1;
    public static int intvalue2 = 1;
    public static Jvm_2_Test_A instance_A = new Jvm_2_Test_A();

    Jvm_2_Test_A() {
        intvalue1++;
        intvalue2++;
    }

    public static Jvm_2_Test_A getInstance_A(){
        return instance_A;
    }
}
输出结果:  
1
2

案例五:

public class JvmClassTest2 {
    public static void main(String[] args) {
        Jvm_2_Test_A testA = Jvm_2_Test_A.getInstance_A();
        System.out.println(Jvm_2_Test_A.intvalue1);
        System.out.println(Jvm_2_Test_A.intvalue2);
    }
}

class Jvm_2_Test_A{
    public static int intvalue1;
    public static int intvalue2 = 1;
    public static Jvm_2_Test_A instance_A = new Jvm_2_Test_A();

    Jvm_2_Test_A() {
        intvalue1++;
        intvalue2++;
    }

    public static Jvm_2_Test_A getInstance_A(){
        return instance_A;
    }
}
输出结果:  
1
1

案例六:

public class JvmClassTest3 {
    public static void main(String[] args) {
        Jvm_3_Test_A[] arr = new Jvm_3_Test_A[1];
    }
}

class Jvm_3_Test_A{
   static {
       System.out.println("A Static Block");
   }
}
输出结果:  
无输出

这只是定义了一个数据类型而已,故不会有输出。

案例七:

public class JvmClassTest4 {
    public static void main(String[] args) {
        System.out.println(Jvm_4_Test_A.Str);
    }
}

class Jvm_4_Test_A{
    public static final String Str = "Str A";
    static {
        System.out.println("A Static Block");
    }
}
输出结果:  
Str A

这个是将Str写入了Jvm_4_Test_A的常量池中

案例八:

public class JvmClassTest5 {
    public static void main(String[] args) {
        System.out.println(Jvm_5_Test_A.Str);
    }
}

class Jvm_5_Test_A{
    public static final String Str = UUID.randomUUID().toString();
    static {
        System.out.println("A Static Block");
    }
}
输出结果:  
A Static Block
d536f6be-f66b-45e4-97be-2b8c479ccb4d

这里的str虽然是final修饰,但是uuid是动态生成的,没办法写到常量池中。

案例九:

public class JvmClassTest6 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> clazz = forName("JVM.ClassLoadTest.Jvm_6_Test_A");
    }
}

class Jvm_6_Test_A{
    static {
        System.out.println("A Static Block");
    }
}
输出结果:  
A Static Block

总结

深入学习后,通过八个案例发现,对jvm类加载掌握的并不扎实,需要深入学习与探索
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

luckjump

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值