image.png

1. 类加载过程

类使用的7个阶段
类从被加载到虚拟机内存中开始,到卸载出内存,它的整个生命周期包括:
加载(Loading)、验证 (Verification)、准备(Preparation、解析(Resolution)、初始化(Initiallization)、使用(Using)、卸载 (Unloading)这7个阶段。其中 验证、准备、解析 3个部分统称为连接(Linking)
image.png

加载

加载是类加载的第一个阶段。有两种时机会触发类加载:

  1. 预加载

虚拟机启动时加载,加载的是JAVA_HOME/lib/下的rt.jar下的.class文件,这个jar包里面的内容是程序运行时非常常
常用到的,像java.lang.*、java.util.、java.io. 等等,因此随着虚拟机一起加载。

  1. 运行时加载

虚拟机在用到一个.class文件的时候,会先去内存中查看一下这个.class文件有没有被加载,如果没有就会按照类的
全限定名来加载这个类。

连接

  1. 验证

连接阶段的第一步,这一阶段的目的是为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

  1. 准备

准备阶段是正式为类变量分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配。这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化 的时候随着对象一起分配在Java堆中
这个阶段赋初始值的变量指的是那些不被final修饰的static变量,比如”public static int value = 123”,value在准 备阶段过后是0而不是123,给value赋值为123的动作将在初始化阶段才进行;比如”public static final int value = 123;”就不一样了,在准备阶段,虚拟机就会给value赋值为123。

  1. 解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

  1. Constant pool:
  2. #2 = Utf8 cn/hzlim/test6/TestMain

符号引用指的是符号 #2 对应的值就是 Utf8 cn/hzlim/test6/TestMain

初始化

类的初始化阶段是类加载过程的最后一个步骤, 之前介绍的几个类加载的动作里, 除了在加载阶段用户应用程序可以通过自定义类加载器的方式局部参与外,其余动作都完全由Java虚拟机来主导控 制。 直到初始化阶段, Java 虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。
初始化阶段就是执行类构造器()方法的过程。 ()并不是程序员在Java代码中直接编写 的方法, 它是Javac编译器的 自动生成物,()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块) 中的 语句合并产 生的, 编译器收集的顺序是由语句在源文件中出现的顺序决定的, 静态语句块中只能访问 到定义在静态语句块之前的变量, 定义在它之后的变量, 在前面的静态语句块可以赋值, 但是不能访 问

2. 双亲委派

双亲委派说明

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。

image.png

如何破坏双亲委派

在JDK1.8的ClassLoader源码中的loadClass方法是实现双亲委派逻辑的地方,修改他会破坏双亲委派机制

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

破坏双亲委派

image.png

  1. 重写loadClass方法 ```java package cn.hzlim.java.basics.jvm;

import java.io.*;

public class MyTestClassLoader extends ClassLoader { private String codePath;

public MyTestClassLoader(ClassLoader parent) {
    super(parent);
}

public MyTestClassLoader(ClassLoader parent, String codePath) {
    super(parent);
    this.codePath = codePath;
}

public MyTestClassLoader(String codePath) {
    this.codePath = codePath;
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    BufferedInputStream bis = null;
    ByteArrayOutputStream baos = null;
    try {
        //1.字节码路径
        String fileName = codePath + name + ".class";
        //2.获取输入流
        bis = new BufferedInputStream(new FileInputStream(fileName));
        //3.获取输出流
        baos = new ByteArrayOutputStream();
        //4.io读写
        int len;
        byte[] data = new byte[1024];
        while ((len = bis.read(data)) != -1) {
            baos.write(data, 0, len);
        }
        //5.获取内存中字节数组
        byte[] byteCode = baos.toByteArray();
        //6.调用defineClass 将字节数组转成Class对象
        Class<?> defineClass = defineClass(null, byteCode, 0, byteCode.length);
        return defineClass;
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            bis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            baos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
    // 1、找到ext classLoader,并首先委派给它加载
    ClassLoader classLoader = getSystemClassLoader();
    while (classLoader.getParent() != null) {
        classLoader = classLoader.getParent();
    }
    Class<?> clazz = null;
    try {
        clazz = classLoader.loadClass(name);
    } catch (ClassNotFoundException e) {
        // Ignore
    }
    if (clazz != null) {
        return clazz;
    }
    // 2、自己加载
    clazz = this.findClass(name);
    if (clazz != null) {
        return clazz;
    }
    // 3、自己加载不了,再调用父类loadClass,保持双亲委派模式
    return super.loadClass(name);
}

}


2. 执行测试代码
```java
public class MyTest {
    public static void main(String[] args) throws Exception {
        MyTestClassLoader testClassLoader = new MyTestClassLoader("/Users/limiao/Desktop/study-notes/code/study-final-code/java-basics/target/classes/cn/hzlim/java/basics/jvm/");
        Class<?> clazz = testClassLoader.loadClass("MyTestClass");
        System.out.println("我是由"+clazz.getClassLoader().getClass().getName()+"类加载器加载的");
    }
}
  1. 测试结果

image.png

类加载器

双亲委派模型

自定义类加载器

ClassLoader源码

sun.misc.Launcher类是java的入口,在启动java应用的时候会首先创建Launcher类,创建Launcher类的时候回准备应用程序运行中需要的类加载器。

Launcher作为JAVA应用的入口,根据双亲委派模型,Laucher是由JVM创建的,它类加载器应该是BootStrapClassLoader, 这是一个C++编写的类加载器,是java应用体系中最顶层的类加载器,负责加载JVM需要的一些类库(/lib)。可以通过一个简单的代码验证一下我们的想法。