梁恒嘉的技术专栏 全栈之路

JVM中的类加载系统

2022-09-28
NanKe
JVM

记录JVM里面的类加载系统的相关知识,包括类加载系统,类加载过程,类加载器,双亲委派机制,打破双亲委派机制

知识图谱: 类加载系统


一、类加载系统

类加载系统负责从文件系统或者网络中加载class文件,classLoader只负责class文件的加载,class文件是否可以运行,则由Execution Engine决定。加载的类信息存放在一块称为方法区的内存空间。并且ClassLoader会为加载进来的class文件创建Class类的对象

二、类加载过程

1.加载

通过类名进行加载

Class.forName("具体的类名称");

使用IO读取字节码文件到内存(运行时方法区),生成此类的Class对象

2.链接

  1. 验证:验证文件的格式是否一致;元数据验证,相当于Java语法验证,比如类是否继承其他类
  2. 准备:为类的不含final修饰的静态属性分配内存,并设置默认初始值;不为静态的常量(final修饰的属性)设置默认初始值;比如class文件中存在如下代码:static int num = 1,在这个阶段下的num就会被设置初始值为0
  3. 解析:将类中的二进制数据的符号引用替换成直接引用(符号引用是class中的逻辑符号,直接引用是指内存中的实际地址)

    3.初始化

    对类中的静态变量进行赋值

类什么时候初始化?

  1. 创建类的实例的时候,也就是new一个对象
  2. 访问某个类或接口的静态变量,或者对该静态变量赋值
  3. 调用类的静态方法
  4. 反射(Class.forNmae(“”))
  5. 初始化一个类的子类(会首先初始化子类的父类)
  6. 执行该类的main方法

类什么时候不会初始化?

  1. 当我们访问一个类的静态常量的时候,我们访问的这个类是不会被加载的,只是将这个静态常量加载到常量池;静态代码块的加载与类的加载是同时进行的,可以根据静态代码中的代码是否执行判断类是否已经加载,如下面代码:
    public class ClassInit{
    	
     final static int num = 10;
     static{
         System.out.println("static静态代码块执行了!");
     }	
    }
    
    class Test{
     public static void main(String[] args){
         System.out.println(num);//10
     }
    }
    

    运行结果如下图所示:

在这里插入图片描述

  1. 当类被作为数组中的数据类型时,这个类是不会被初始化的

如以下代码:int[ClassInit] demo = new int[5]

类的初始化顺序 对static修饰的变量或语句块进行赋值

如果同时包含多个静态代码块和静态变量,则按照从上到下的顺序依次初始化

如果初始化的时候,一个父类没有初始化,则优先初始化父类

顺序是:父类static => 子类static => 父类构造器方法 => 子类构造方法

public class ClassInit{
	static{
		num = 20;
	}
	static int num = 10;
	public static void main(String[] args){
		//num从准备到初始化值的变化过程 num=0 => num=20 => num=10
		System.out.println(num);//10
	}
}

三、类加载器

指的是负责加载类的类

站在JVM的角度对类加载器进行分类,类加载器分为两类:

  1. 启动类加载器(引导类加载器),这部分不是Java语言写的,负责加载Java核心类
  2. 其他所有类加载器,这些类加载器独立于JVM,存在于JVM外部,并且全部继承于抽象类java.lang.ClassLoader

站在Java开发人员的角度来看,类加载器就应当划分的更加细致,自JDK1.2以来Java一直保持着三层加载器

  1. 启动类加载器
  2. 扩展类加载器
  3. 应用程序类加载器

启动类加载器

这个加载器是使用C/C++语言实现的,嵌套在JVM内部

启动类加载器加载位于jdk/lib的类,具体位置如下图所示:

在这里插入图片描述

扩展类加载器

Java体系类加载器是ClassLoader,扩展类加载器派生于ClassLoader,包含应用程序类加载器

扩展类加载器加载位于jdk/jre/lib/ext的类,具体位置如下图所示:

在这里插入图片描述

应用程序类加载器

负责加载环境变量classpath或path路径下的类库

加载我们自己定义的类,用于加载用户类路径上所有的类,应用程序类加载器由扩展类加载器加载

该类加载器是程序中默认的加载器

应用程序类加载还包括自定义类加载器

双亲委派机制

Java虚拟机对class文件的加载是按需加载,只会加载需要使用的class类,在查找我们需要的class类的时候,Java会采用双亲委派机制去查找。默认是使用的应用程序类加载器,但是此时应用程序类加载器会委托扩展类加载器去查找,扩展类加载器又会委托启动类加载器,启动类加载器此时会在自己的范围类查找class类,如果没有找到则会让扩展类加载器去查找,如果扩展类加载器在其范围类没有找到需要的class类,此时就会让启动类加载器去查找,如果在这个期间找到则返回找到的class类,如果最终找不到则出现ClassNotFound异常。

确保类加载的正确性,安全性

双亲委派机制如何打破

Java类加载器的超类是ClassLoader类,通过继承ClassLoader类实现自定义类加载器,重写loadClass和findClass方法会打破双亲委派机制,代码如下所示:

public class MyClassLoader extends ClassLoader{

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        //该方法默认使用双亲委派机制
        return super.loadClass(name);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //该方法默认不会使用双亲委派机制
        return super.findClass(name);
    }
}

类的主动使用与被动使用,主要区别为类是否初始化

主动使用:当类被使用的时候,类会被初始化

  1. 创建类对象,通过new创建对象
  2. 访问类中静态变量/方法
  3. 通过反射机制,动态加载类
  4. 当子类被加载,会先初始化父类
  5. 调用类中的main方法

被动使用:当类被使用的时候,不会被初始化

  1. 访问类中的静态常量
  2. 当被作为类型存在时,如:MyClass[] c = new MyClass[10];

上一篇 JVM整体结构

 

Content