类加载机制深度解析

Posted qishanmozi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载机制深度解析相关的知识,希望对你有一定的参考价值。

一、类加载过程

多个java文件经过编译打包生成可运行jar包,最终由java命令运行某个主类的main启动程序,这里需要先通过类加载器把主类加载到JVM
主类在运行过程中如果使用到其他类,会逐步加载这些类。
注意:jar包里的类不是一次性全部加载的,是使用到时才加载,不过类似于java.lang.Object这种支持JVM运行的类会在启动时便被加载。
类加载过程
加载>>验证>>准备>>解析>>初始化>>使用>>卸载
  • 加载:在硬盘上查找并通过IO将字节码文件读入到内存中,使用到类时才会被加载,例如调用类的main()方法,new对象等
  • 验证:验证字节码文件的正确性
  • 准备:为类的静态变量分配内存空间,并给静态变量赋予初始值
  • 解析:将符号引用替换为这直接引用,该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链 接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用 
  • 初始化:对类的静态变量初始化为指定的值,执行静态代码块
技术图片
二、类加载器和双亲委派机制
类的加载主要通过类加载器来实现,java中的类加载器如下:
  • 启动类加载器(BootstrapClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等
  • 扩展类加载器(ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包
  • 应用程序加载器(AppClassLoader):负责加载ClassPath路径下的类包,主要是java开发人员自己写的类生成的字节码文件
  • 自定义加载器:开发人员可以通过继承ClassLoader这个类来自定义自己的类加载器,只需要实现findClass()这个方法即可,如果要打破双亲委派机制则需要额外自己实现loadClass()这个方法修改代码使其不使用双亲委派的方式。
类加载器示例:
package jvm;

public class TestJDKClassLoader {
    public static void main(String[] args) {
        System.out.println(String.class.getClassLoader());
        System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
        System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
        System.out.println(ClassLoader.getSystemClassLoader().getClass().getName());
    }
  
结果:
null//启动类加载器是使用C++语言实现,所以无法打印
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$AppClassLoader
 
双亲委派机制的逻辑大致如下:
1.首先加载指定名称的类是否已被加载过,如果加载过就不需要重复加载,直接返回。
2.如果此类没有被加载,那么判断是否有父类加载器,如果有,则委派给父加载器加载,如果没有则直接委派给启动类加载器加载。
3.如果父加载器及bootstrapClassLoader均没有找到目标类则有当前类加载器的findClass完成加载。
总结:加载器加载时将加载动作逐级向上委托直到最高级的启动类加载器,再从最高级向下逐级进行目标类加载,如果在某一级加载到了目标类则不再向下继续。
设计双亲委派机制的目的:
  • 沙盒安全机制:java开发人员自己写的java.lang.class不会被加载,防止核心API库被篡改。
  • 避免类的重复加载:当父亲类加载器已经加载到目标类时,字加载器便不会在进行加载,保证的被加载类的唯一性。
在自定义类加载器示例:
自定义类加载器主要是重写findclass()方法:
package jvm;

import java.io.FileInputStream;
import java.io.IOException;

//自定义类加载器
public class MyClassloaderTest extends ClassLoader{
    private String classPath;

    //初始化时指定字节码目录所在目录
    public MyClassloaderTest(String classPath) {
        this.classPath = classPath;
    }
	//将字节码文件加载到内存中
    private byte[] loadByte(String name) throws IOException {
        name = name.replaceAll(".","/");
        FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
        int len = fis.available();
        byte[] bt = new byte[len];
        fis.read(bt);
        fis.close();
        return bt;
    }

    //通过该类返回Class对象
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name,data,0,data.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}
 
 
打破双亲委派机制
如果要打破双亲委派机制只需要重写loadClass()这个方法
package jvm;

import java.io.FileInputStream;
import java.io.IOException;

//自定义类加载器
public class MyClassloaderTest extends ClassLoader{
    private String classPath;

    public MyClassloaderTest(String classPath) {
        this.classPath = classPath;
    }

    private byte[] loadByte(String name) throws IOException {
        name = name.replaceAll(".","/");
        FileInputStream fis = new FileInputStream(classPath+"/"+name+".class");
        int len = fis.available();
        byte[] bt = new byte[len];
        fis.read(bt);
        fis.close();
        return bt;
    }

    @Override
    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) {
                // 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;
        }
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = loadByte(name);
            return defineClass(name,data,0,data.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}
 
测试类:
 package jvm;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
        MyClassloaderTest classloader = new MyClassloaderTest("F:	est");
        Class clazz = classloader.loadClass("java.lang.String");
        Object o = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sout", null);
        method.invoke(o,null);
        System.out.println(clazz.getClassLoader().getClass().getName());
    }
}
 



以上是关于类加载机制深度解析的主要内容,如果未能解决你的问题,请参考以下文章

深度学习Java的类加载机制

深度分析:Java虚拟机类加载机制过程与类加载器

深度分析:Java虚拟机类加载机制过程与类加载器

深度分析:Java虚拟机类加载机制过程与类加载器

深度分析:Java虚拟机类加载机制过程与类加载器

JVM:Java类加载原理深度解析