JVM学习笔记(类加载的三个阶段和类加载器)

发布于:2022-07-17 ⋅ 阅读:(306) ⋅ 点赞:(0)

1、类加载过程

类加载过程分为loading、linking、Initialization

1.1、loading(加载)阶段

从各种渠道获取类,例如

  • 本地系统直接获取
  • 网络获取,例如web app
  • 通过压缩包,例如jar包
  • 运行时计算生成,使用最多的是动态代理技术
  • 有其他文件生成:jsp应用
  • 从专门的数据库中提取,比较少见
  • 从加密文件中获取,典型的房class文件被反编译的保护措施

1.2、linking(链接)阶段

Verity(验证)——> Prepare(准备)——> Resolve(解析)
Verity
要求class文件符合当前虚拟机要求,不会危害虚拟机自身安全,主要包括四种验证(文件格式验证、元数据验证、字节码验证、符号引用验证)
Prepare
为类分配内存,并且为类中的变量设置默认值(零值),不包括final修饰的static,因为编译的时候就会分配。不会为实例变量分配初始化,类变量会分配到方法区,而实例变量是会随着对象一起分配到java中
Resolve
将常量池中的符号引用转换为直接引用的过程

1.3、Initialization(初始化)阶段

1.3.1、初始化阶段就是执行()的过程

javac编译时搜集类中所有的类变量赋值动作和静态态代码块中的语句合并起来。如果编译类时,没有静态变量或者静态代码块的初始化,则不会有()方法的出现。

/*
 0 iconst_1
 1 putstatic #3 <com/luo/java/ClassInitTest.num : I>
 4 iconst_2
 5 putstatic #3 <com/luo/java/ClassInitTest.num : I>
 8 bipush 20
10 putstatic #5 <com/luo/java/ClassInitTest.number : I>
13 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
16 getstatic #3 <com/luo/java/ClassInitTest.num : I>
19 invokevirtual #4 <java/io/PrintStream.println : (I)V>
22 bipush 10
24 putstatic #5 <com/luo/java/ClassInitTest.number : I>
27 return
可以看出初始化顺序
*/
public class ClassInitTest {
    static int num = 1;
    static {
        num = 2;
        number = 20;
        System.out.println(num);//2
        System.out.println(number);//报错,非法的前向调用Illegal forward reference
    }
    static int number = 10;// linking中的prepare:number = 0 --->  initial:20 --> 10
    //意思就是先执行初始化number等于0,然后执行static中的让number等于20,最后让number等于10
    public static void main(String[] args) {
        System.out.println(ClassInitTest.num);//2
        System.out.println(ClassInitTest.number);//10
    }
}

1.3.2、虚拟机必须保证一个类()方法在多线程下被同步加锁

一个类在编译的时候只会加载一次在内存里,放在方法区,当在使用类的时候,都是使用已经加载的类。(通俗的讲,类只会被加载一次)
![在这里插入图片描述](https://img-blog.csdnimg.cn/483d1939b80643c68aabf5efd14b2a03.png![在这里插入图片描述](https://img-blog.csdnimg.cn/409b192584a94359a44d04f8e9a69379.png在这里插入图片描述
假如a线程先于b线程访问方法区中的某类,当a进入到类中出不来时,b也无法进入,因为a仍在初始化状态,类会被加锁。

public class DeadThreadTest {
    public static void main(String[] args) {
        Runnable r = ()->{
            System.out.println(Thread.currentThread().getName() + "开始");
            DeadThread deadThread = new DeadThread();
            System.out.println(Thread.currentThread().getName() + "结束");
        };
        Thread r1 = new Thread(r,"a线程");
        Thread r2 = new Thread(r,"b线程");
        r1.start();
        r2.start();
    }
}
class DeadThread{
    static {
        if(true){
            System.out.println(Thread.currentThread().getName() + "初始化当前类");
            while (true){
            }
        }
    }
}

输出结果

a线程开始
b线程开始
a线程初始化当前类

2、类加载器

注意:如果获取的加载器为null,则其加载器为引导加载器
jvm支持两种类型的类加载器,分别为引导加载器(Bootstrap Class Loader)和自定义类加载器(User-defined Class Loader,所有派生于抽象类ClassLoader的类加载器)

2.1、启动类加载器(BootStrap ClassLoader)

  • 使用c/c++实现,嵌套在JVM内部用于加载java的核心库,并不继承于
  • java.lang.ClassLoader,没有父加载器;
  • 加载拓展类和应用程序类加载器,并指定为他们的父类;
  • 出于安全考虑,BootStrap启动类加载器只加载包名为java,javax,sum等开头的类

2.2、扩展类加载器(Extension ClassLoader)

  • java编写,派生ClassLoader
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从jdk的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的jar放在此目录下,也会被拓展类加载器加载

2.3、应用程序加载器(系统类加载器,AppClassLoader)

  • java编写,派生ClassLoader
  • 父类加载器为拓展类加载器
  • 该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由他来完成加载

2.4、用户自定义类

为什么要自定义类加载器

  • 隔离加载器
  • 修改类加载的方式
  • 拓展加载源
  • 防止源码泄露

关于ClassLoader

他是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器和引导类加载)

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到