元月's blog 元月's blog
首页
  • 基础
  • 并发编程
  • JVM
  • Spring
  • Redis篇
  • Nginx篇
  • Kafka篇
  • Otter篇
  • Shardingsphere篇
  • 设计模式
  • MySQL
  • Oracle
  • 基础
  • 操作系统
  • 网络
  • 数据结构
  • 技术文档
  • Git常用命令
  • GitHub技巧
  • 博客搭建
  • 开发工具
更多

元月

临渊羡鱼,不如退而结网
首页
  • 基础
  • 并发编程
  • JVM
  • Spring
  • Redis篇
  • Nginx篇
  • Kafka篇
  • Otter篇
  • Shardingsphere篇
  • 设计模式
  • MySQL
  • Oracle
  • 基础
  • 操作系统
  • 网络
  • 数据结构
  • 技术文档
  • Git常用命令
  • GitHub技巧
  • 博客搭建
  • 开发工具
更多
  • 基础

  • 并发编程

  • JVM

    • Java的调试体系-JPDA架构
    • JVM整体结构和内存模型
    • 深度剖析JVM类加载机制
      • 一、类加载运行全过程
      • 二、类加载器类型
        • 引导类加载器(BootstrapClassLoader)
        • 扩展类加载器(ExtClassLoader)
        • 应用程序类加载器(AppClassLoader)
        • 自定义类加载器
      • 三、类的加载过程
        • 1、加载
        • 2、验证
        • 3、准备
        • 4、解析
        • 5、初始化
      • 四、类的加载机制
        • 1、双亲委派机制
        • 1.1 双亲委派机制原理
        • 1.2 为什么要设计双亲委派机制?
        • 1.3 tomcat是如何打破双亲委派机制的?
        • 2、全盘负责委托机制
      • 五、Java代码执行顺序
        • 1、静态代码块、静态变量
        • 2、构造代码块
        • 3、构造方法
      • 六、思维导图
    • JVM对象创建与内存分配机制
    • JVM垃圾回收算法
    • JVM垃圾收集器一:Serial和Parallel收集器
    • JVM垃圾收集器二:CMS与三色标记算法详解
    • JVM垃圾收集器三:G1(Garbage First)
    • JVM垃圾收集器四:ZGC与颜色指针详解
    • JVM调优之常用的调优指令
    • JVM调优之常用的调优工具
    • Arthas:一款优秀的Java诊断工具
    • 亿级流量系统JVM实战
  • Java基础
  • JVM
元月
2022-08-22
目录

深度剖析JVM类加载机制

# 深度剖析JVM类加载机制

# 一、类加载运行全过程

当我们执行java com.ruanyou.Math这样一个命令,整体过程大致如下:

1、windows系统下java.exe会调用底层的jvm.dll文件创建Java虚拟机(C++实现)

2、随后创建一个引导类加载器实例(C++实现)

3、C++调用Java代码创建JVM启动器实例sun.misc.Launcher,该类由引导类加载器负责加载,会创建其它类加载器

//Launcher的构造方法
public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        //构造扩展类加载器,在构造的过程中将其父加载器设置为null
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
        //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }

    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    。。。 。。。 //省略一些不需关注代码

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

4、sun.misc.Launcher.getLauncher(),获取运行类自己的加载器ClassLoader,是AppClassLoader的实例launcher.getClassLoader()

5、调用loadClass加载要运行的类Math

classLoader.loadClass("com.ruanyou.Math")
1

6、类加载完成后,JVM会执行Math类的main方法

7、JVM销毁

# 二、类加载器类型

# 引导类加载器(BootstrapClassLoader)

​ 负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等

# 扩展类加载器(ExtClassLoader)

​ 负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录下的类库

# 应用程序类加载器(AppClassLoader)

​ 负责加载ClassPath路径下的类包,主要就是加载自己写的那些类

# 自定义类加载器

​ 如果想自定义类加载器,需要继承ClassLoader,并重写findClass方法,如果想打破双亲委派机制,还要重写loadClass方法

# 三、类的加载过程

# 1、加载

​ 在硬盘上查找并通过IO读入字节码文件,只有使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

Math math = null,这种是不会加载类的

# 2、验证

​ 校验字节码文件的正确性

# 3、准备

​ 给类的静态变量分配内存,并赋予默认值

public class TestMain {
    static {
        System.out.println("1、静态变量a的值:" + TestMain.a);
    }
    private static int a = 10;
    static {
        System.out.println("2、静态变量a的值:" + TestMain.a);
        a = 11;
    }
    static {
        System.out.println("3、静态变量a的值:" + TestMain.a);
    }
    public static void main(String[] args) {
        System.out.println("=========================");
    }
}

输出:
1、静态变量a的值:0
2、静态变量a的值:10
3、静态变量a的值:11
=========================
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 4、解析

​ 这个阶段会将符号引用(一些静态方法,比如main()方法等)替换为被加载到内存区域的代码的直接引用。也就是静态链接过程

# 5、初始化

​ 1、将类的静态变量初始化为指定的值

​ 2、执行静态代码块

1和2,此处不分先后,按照静态的顺序性来执行

# 四、类的加载机制

# 1、双亲委派机制

# 1.1 双亲委派机制原理

注意:并非继承,而是持有父加载器的引用

可以简单理解为:向上委托,向下加载

ClassLoader里面的loadClass方法,实现了双亲委派机制,整体过程大致如下:

1> 首先会检查当前类加载器是否已经加载了这个类,如果加载了,那么直接返回(就是调用findLoadedClass(name)方法)

2> 如果没有加载过,那么再判断一下父加载器是否为空;如果父加载器不为空,那么委托父加载器加载这个类(就是调用parent.loadClass(name, false)

3> 如果父加载器为空,那么委托引导类加载器加载这个类(就是调用findBootstrapClassOrNull(name)方法)

4> 如果父加载器以及引导类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法查找并加载这个类。

//ClassLoader的loadClass方法,里面实现了双亲委派机制
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) {
                // If still not found, then invoke findClass in order to find the class.
                long t1 = System.nanoTime();
                //都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
                c = findClass(name);
            }
        }
        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
28
# 1.2 为什么要设计双亲委派机制?
  • ​ 沙箱安全机制:

    我们自己写的java.lang.String.class类不会被加载,这样可以防止核心API库被随意篡改

  • ​ 避免类的重复加载:

    如果父加载器已经加载了这个类,子加载器就没有必要再加载,保证被加载类的唯一性

# 1.3 tomcat是如何打破双亲委派机制的?

tomcat自定义了类加载器,继承ClassLoader,并且重写了loadClass和findClass方法

# 2、全盘负责委托机制

当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,否则这个类依赖以及引用的类也由这个ClassLoder载入。

# 五、Java代码执行顺序

# 1、静态代码块、静态变量

用static修饰的处于同一优先级,同一优先级就按先后顺序来执行

static {
		System.out.println(" 静态代码块A");
}
1
2
3

# 2、构造代码块

{
		System.out.println("构造代码块A");
}
1
2
3

# 3、构造方法

public Test() {
		System.out.println("构造方法");
}
1
2
3
  • 规则一:静态优先且顺序性执行
  • 规则二:父类优先于子类
  • 规则三:构造代码块优先于构造方法

执行顺序:父类静态代码块 > 子类静态代块 > 父类构造代码块 > 父类构造方法 > 子类构造代码块 > 子类构造方法

代码示例一:

public class ClassA {
    static {
        System.out.println("父类静态代码块    ");
    }
    {
        System.out.println("父类构造代码块      ");
    }
    public ClassA() {
        System.out.println("父类构造函数      ");
    }
}

public class ClassB extends ClassA{
    static {
        System.out.println("子类静态代码块    ");
    }
    {
        System.out.println("子类构造代码块      ");
    }
    public ClassB() {
        System.out.println("子类构造函数     ");
    }
}

public static void main(String[] args) {
        ClassB classB = new ClassB();
}

//执行结果
父类静态代码块    
子类静态代码块    
父类构造代码块      
父类构造函数      
子类构造代码块      
子类构造函数
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
28
29
30
31
32
33
34
35

代码示例二:

在ClassB中增加private static ClassB classB = new ClassB();,此处子类静态代码块在中间执行

public class ClassA {
    static {
        System.out.println("父类静态代码块    ");
    }
    {
        System.out.println("父类构造代码块      ");
    }
    public ClassA() {
        System.out.println("父类构造函数      ");
    }
}

public class ClassB extends ClassA{
    private static ClassB classB = new ClassB();//增加成员属性
    static {
        System.out.println("子类静态代码块    ");
    }
    {
        System.out.println("子类构造代码块      ");
    }
    public ClassB() {
        System.out.println("子类构造函数     ");
    }
}

public static void main(String[] args) {
        ClassB classB = new ClassB();
}
//执行结果
父类静态代码块    
父类构造代码块      
父类构造函数      
子类构造代码块      
子类构造函数     
子类静态代码块    
父类构造代码块      
父类构造函数      
子类构造代码块      
子类构造函数 
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
28
29
30
31
32
33
34
35
36
37
38
39

# 六、思维导图

#JVM
JVM整体结构和内存模型
JVM对象创建与内存分配机制

← JVM整体结构和内存模型 JVM对象创建与内存分配机制→

最近更新
01
otter二次开发-支持按目标端主键索引Load数据
08-03
02
mvnw简介
06-21
03
gor流量复制工具
06-03
更多文章>
Theme by Vdoing | Copyright © 2022-2024 元月 | 粤ICP备2022071877号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式