java类加载机制

什么是类的加载?

类的加载是:

  • 将类的.class文件中的二进制数据读入到内存中
  • 将其放在运行时数据区的方法区内
  • 然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构

加载时机:

  • 并不是“首次主动使用”时加载
  • JVM允许类加载器在预料某个类将要被使用的时候 就预先加载它
  • 如果在加载过程中遇到错误,类加载器必须在首次主动使用该类时才报告错误(LinkageError错误)

生命周期

java类加载机制_第1张图片
image
  • 过程:类加载过程包括加载、验证、准备、解析和初始化五个阶段。

  • 顺序:其中除了解析,其他是个阶段是按照顺序==开始的==,解析阶段不确定,是为了支持语言的运行时绑定,所以可能在初始化之后进行。

  • 注意:是按顺序开始的,不是一个结束另一个才开始,通常是混合进行的,一个阶段执行过程中激活另一个阶段。

加载

  • 获取一个类的二进制字节流
  • 讲这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

相对其他阶段,加载阶段是可控性最强的,既可以使用系统提供的类加载器,还可以自定义加载器来完成加载。

连接(验证、准备、解析)

验证

确保被加载的类的正确性

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

验证是重要的,但不是必须的。

准备

为类的静态变量分配内存,并初始化为数据类型默认值

正式为类变量分配内存并设置类变量初始值的阶段,这些内存都在方法区中分配。注意:

  1. 进行内存分配的仅包括类变量(static),不包括实例变量
    • 实例变量在对象实例化的时候分配在java堆中
  2. 这里设置的初始值通常是数据类型默认的值,而不是被显示赋予的值。
    • 假设 类变量定义
public static int value=3
在准备阶段之后,value的值为0 而不是3
  1. 类字段属性表存在ConstantValue属性,即同时被static final修饰,在准备阶段就会被初始化属性所指定的值
public final static int value=3
在准备阶段之后,value的值为3

解析

把常量池内的符号引用转换为直接引用

  • 符号引用:简单说就是字符串
  • 直接引用:直接指向目标的指针或者地址偏移量,引用对象一定在内存里。

初始化

为类的静态变量赋予正确的初始值。
步骤

  • 执行类/接口初始化方法
  • 初始化静态变量,静态块中的数据(一个类加载器只会初始化一次)
  • 之类的调用前,保证父类的被调用

类初始化时机:

只有对类进行主动使用的时候才会初始化,包括

  • 创建实例 ,new的方式
  • 访问类的静态变量,或者对变量赋值
  • 调用类的静态方法
  • 反射
  • 初始化子类,父类也会被初始化
  • 虚拟机启动时,被标明为启动类的类(Java Test)

结束生命周期

  • 执行了System.exit()
  • 程序执行结束
  • 程序遇到错误或异常
  • 虚拟机进程终止

类加载器

什么是类装载器 ClassLoader

  • 是一个抽象类
  • 它的实例将读入java字节码 装载到jvm中
  • 可以定制,满足不同的字节码流获取方式
  • 负责类装载过程中的加载阶段

JVM的类加载器

  1. 启动类加载器(BootStrap)
  • 负责将jdk中jre/lib下面的核心库
  • 或Xbootclasspath指定的jar包加载到内存中
  • 不允许通过引用进行操作
  1. 扩展类加载器(Extension)
  • 负责将jre/lib/ext下面的核心库
  • 或者由变量-Djava.ext.dir指定位置的类库加载到内存中.
  • 开发者可直接使用
  1. 系统类加载(System)
  • 负责将 java -classpath
  • 或-Djava.class.path变量所指的类库加载到内存
  • 开发者可以直接使用

类加载的方式

  • 命令行启动应用时,由JVM初始化加载
  • 通过Class.forName()方法动态加载
  • 通过ClassLoader.loadClass()方法动态加载

双亲委派模型

看一个例子

public class loader {
    public static void main(String[] args) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        System.out.println(loader);
        System.out.println(loader.getParent());
        System.out.println(loader.getParent().getParent());
    }
}

Result:
sun.misc.Launcher$AppClassLoader@51521cc1
sun.misc.Launcher$ExtClassLoader@593634ad
null 
//可以看出,并没有获取到ExtClassLoader的父Loader
//原因是 Bootstrap Loader是用C实现的,java无法引用,所以返回null

加载器层次图


image

工作流程是:

  • 如果一个类加载器收到了加载请求,它不会尝试加载这个类,而是把请求委托给父加载器去完成,依次向上
  • 因此,所有的加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需类

意义:

  • 系统类防止内存中出现多份同样的字节码
  • 保证java程序安全稳定运行

自定义加载器

继承ClassLoader类,重写findClass方法

public class MyClassLoader extends ClassLoader {

     protected Class findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }


 private byte[] loadClassData(String className) {
     ****
 }
}

或者 继承URLClassLoader类,然后设置自定义路径的URL来加载URL下的类。

有什么用?

资源隔离 热部署 代码保护

比如tomcat,就用到了类加载器的知识处理 web应用加载。

参考文章 类加载机制
java类加载机制

你可能感兴趣的:(java类加载机制)