铁匠 铁匠
首页
收藏
java
架构之路
常用算法
  • Java
  • nginx
  • 系统运维
  • 系统安全
  • mysql
  • redis
参考文档
关于
链接
  • 分类
  • 标签
  • 归档

专注、不予评判地关注当下
首页
收藏
java
架构之路
常用算法
  • Java
  • nginx
  • 系统运维
  • 系统安全
  • mysql
  • redis
参考文档
关于
链接
  • 分类
  • 标签
  • 归档
  • java api 文档
  • 集合

  • 版本特性

  • jvm

    • JVM 结构体系
    • JVM 内存区域划分
    • JVM 垃圾收集
    • JVM 参数配置
    • JVM 监控分析工具
    • JVM 类加载器过程
      • 类加载过程
      • 双亲委派
      • 自定义 Classloader
      • 应用
    • 自己动手写一个 java 热加载插件
  • 网络编程

  • 并发编程

  • java
  • jvm
FengJianxin
2021-11-07
目录

JVM 类加载器过程

# 类加载过程

Java 类加载过程大体上可以分为三个阶段:加载、链接、初始化。详细定义可以查看java虚拟机规范 (opens new window)。

其中链接(Linking)阶段又可以分为:验证、准备、解析

  • 加载(Loading):从任意数据源(例如:文件、jar包、数据库,甚至是网络)中获得字节流并加载到 jvm 中,然后映射为一个 Class 对象
  • 链接(Linking)
    1. 验证(Verification):验证字节码是否符合 jvm 规范,是 jvm 安全运行的报障
    2. 准备(Preparation):创建类(包括接口)中的静态变量,并为静态变量赋初始值,分配所需要的内存空间
    3. 解析(Resolution):将常量池中的符号引用替换为直接引用
  • 初始化(Initialization):执行编译器自动生成的 <clinit>()方法(如果没有静态变量或者静态代码块,则不会生成<clinit>()方法),为静态变量赋值,执行静态代码块逻辑(static),父类初始化优先于子类

<clinit>()方法是为静态变量赋值和执行静态代码块,在编译期间自动收集所有静态变量和所有静态代码块,合并成一个方法

如果是final修饰的静态变量,在编译期就已经赋值,不会包含在<clinit>()方法里

# 双亲委派

双亲委派机制是一种规范,在类加载过程中,优先使用父级已经加载的 Class(与父级类加载器是组合关系,不是继承),如果父类也没有定义对应的 Class,则继续向父级的父级查找,直到父级 Classloader 为 null(启动类加载器 - BootstrapClassLoader),委派模型的目的是为了避免重复加载 Class。

委派关系图如下

双亲委派的意义:

  1. 避免类被重复加载
  2. 保护核心类库被篡改

打破双亲委派的意义

  1. 资源隔离,如 tomcat 支持部署多个项目,并且互不干扰
  2. 解决 jar 包冲突,如老项目迭代,存在两个不同版本jar包,解决版本冲突

可以通过启动参数,指定对应类加载器加载的 jar \ Class 文件

  • 启动类加载器 - BootstrapClassLoader

    # 指定新的bootclasspath,替换java.*包的内部实现
    java -Xbootclasspath:<your_boot_classpath>
    # a意味着append,将指定目录添加到bootclasspath后面
    java -Xbootclasspath/a:<your_dir>
    # p意味着prepend,将指定目录添加到bootclasspath前面
    java -Xbootclasspath/p:<your_dir>
    
    1
    2
    3
    4
    5
    6
    // 打印加载路径
    URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
    System.out.println("================启动类加载器================");
    for (URL urL : urLs) {
        System.out.println(urL);
    }
    
    1
    2
    3
    4
    5
    6
  • 扩展类加载器 - ExtClassLoader

    # 覆盖 jre/lib/ext/ 目录的 jar 包
    java -Djava.ext.dirs=path_to_ext_dir
    
    1
    2
    System.out.println("================扩展类加载器================");
    String extDirs = System.getProperty("java.ext.dirs");
    // String[] dirs = extDirs.split(";"); // for windows
    String[] dirs = extDirs.split(":");
    for (String dir : dirs) {
        System.out.println(dir);
    }
    
    1
    2
    3
    4
    5
    6
    7
  • 应用程序类加载器 App(System)ClassLoader

    # -classpath 或者 -cp,两者等价,多个文件或路径用`:`分隔(windows用`;`)
    java -cp path_to_jar:path_to_dir
    # 指定应用程序类加载器,设置这个参数后 App(System)ClassLoader 将成为自定义类加载器的父亲
    java -Djava.system.class.loader=com.pkg.MyClassLoader
    
    1
    2
    3
    4

提示

详细可查看《深入理解Java虚拟机-第3版》- 7.4.2 双亲委派模型

实际上,并不是所有的类加载器都符合这个规范,例如 Tomcat 通过自定义的类加载器,实现了应用隔离和动态编译 jsp。

tomcat 类加载器架构

# 自定义 Classloader

java 支持用户实现自定义 Classloader来实现不修改 jdk 代码的同时来扩展 java 功能,这一点在 c 和 c++ 就无法实现。

要自定义 ClassLoader 只需要继承 java.lang.ClassLoader 或其子类,通常我们可以重写findClass(String name)或者loadClass(String name, boolean resolve)方法实现自定义类加载逻辑。

通过查看loadClass(String name, boolean resolve)方法源码可以知道,方法内部会首先判断parent是否已经加载过对应类,如果没有加载才会执行自身的加载逻辑,即:保留了双亲委派模型。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }
            if (c == null) {
                long t1 = System.nanoTime();
                c = findClass(name);
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

由此得出:

  • 保留双亲委派模型则重写findClass方法
  • 不保留双亲委派模型则重写loadClass方法

# 应用

自定义类加载器的常见应用

  1. 实现模块化机制,可以隔离运行环境,解决jar包冲突等
  2. 实现代码热加载(如 tomcat 实现 jsp 热加载)
  3. 字节码加密,防止反编译
  4. 扩展加载源
#jvm
JVM 监控分析工具
自己动手写一个 java 热加载插件

← JVM 监控分析工具 自己动手写一个 java 热加载插件→

最近更新
01
策略模式
01-09
02
模板方法
01-06
03
观察者模式
01-06
更多文章>
Theme by Vdoing | Copyright © 2016-2023 铁匠 | 粤ICP备15021633号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式