JVM基础2 - java类加载机制文章目录1. java类的加载过程2. 类加载器2.1. 类加载器2.2. 双亲委派机制3. 自定义类加载器1. java类的加载过程JVM 中类的装载是由类加载器ClassLoader 实现的类加载器负责在运行时查找和装入类文件中的类。当Java程序需要使用某个类时JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。加载指把.class文件中读入到内存中然后产生与所加载类对应的Class对象。加载完成后Class对象还不完整所以此时的类还不可用。连接包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。类初始化如果类存在父类且这个类没有被初始化那么就先初始化父类如果类中存在初始化语句就依次执行这些初始化语句。符号引用和直接引用 当java类编译成class文件时java类并不知道其引用类的实际内存地址因此只能使用符号引用来代替。类装载器装载类时可以通过虚拟机获取实际的内存地址然后将符号引用替换为直接引用。当程序执行结束或者程序遇到异常、操作系统导致JVM进程终止、执行了 System.exit() 都会结束类的生命。2. 类加载器2.1. 类加载器类加载器包括根加载器BootStrap、扩展加载器Extension、系统加载器System和用户自定义类加载器java.lang.ClassLoader的子类。类加载器Bootstrap一般用本地代码实现负责加载JVM基础核心类库rt.jarExtension从java.ext.dirs系统属性所指定的目录中加载类库它的父加载器是BootstrapSystem应用类加载器其父类是Extension。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类是用户自定义加载器的默认父加载器。2.2. 双亲委派机制如果一个类收到了类加载的请求它首先会将请求发送给父类去加载以此向上直到所有的类加载请求传递到顶层的启动类加载器中。只有当父类加载器在自己的搜索范围找不到所需的类时子加载器才会尝试自己去加载。双亲委派过程当AppClassLoader加载一个class时它首先不会自己去尝试加载这个类而是把类加载请求委派给父类加载器ExtClassLoader去完成。当ExtClassLoader加载一个class时它首先也不会自己去尝试加载这个类而是把类加载请求委派给BootStrapClassLoader去完成。如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class)会使用ExtClassLoader来尝试加载若ExtClassLoader也加载失败则会使用AppClassLoader来加载如果AppClassLoader也加载失败则会报出异常ClassNotFoundException双亲委派源码publicClass?loadClass(String name)throws ClassNotFoundException{returnloadClass(name,false);}protectedsynchronized Class?loadClass(String name,boolean resolve)throws ClassNotFoundException{// 首先判断该类型是否已经被加载Class cfindLoadedClass(name);if(cnull){//如果没有被加载就委托给父类加载或者委派给启动类加载器加载try{if(parent!null){//如果存在父类加载器就委派给父类加载器加载cparent.loadClass(name,false);}else{//如果不存在父类加载器就检查是否是由启动类加载器加载的类通过调用本地方法native Class findBootstrapClass(String name)cfindBootstrapClass0(name);}}catch(ClassNotFoundException e){// 如果父类加载器和启动类加载器都不能完成加载任务才调用自身的加载功能cfindClass(name);}}if(resolve){resolveClass(c);}returnc;}双亲委派优势系统类防止内存中出现多份同样的字节码保证Java程序安全稳定运行3. 自定义类加载器场景当通过网络来传输 Java 类的字节码需为保证安全性这些字节码经过了加密处理这时系统类加载器就无法对其进行加载这样则需要自定义类加载器来实现。自定义类加载器一般都是继承自 ClassLoader 类从上面对 loadClass 方法来分析来看我们只需要重写 findClass方法即可。dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-compress/artifactIdversion1.21/version/dependencypackage com.gao.jvm.classloader;importorg.apache.commons.compress.utils.IOUtils;importjava.io.ByteArrayOutputStream;importjava.io.File;importjava.io.FileInputStream;importjava.nio.ByteBuffer;importjava.nio.channels.Channels;importjava.nio.channels.FileChannel;importjava.nio.channels.WritableByteChannel;publicclassMyClassLoaderextends ClassLoader{publicstaticvoidmain(String[]args)throws Exception{MyClassLoader mclnewMyClassLoader();Class?clazzClass.forName(com.gao.algorithm.integer.IntegerOne,true,mcl);Object objclazz.newInstance();System.out.println(obj.getClass().getClassLoader());}publicMyClassLoader(){}OverrideprotectedClass?findClass(String name)throws ClassNotFoundException{File filenewFile(/Users/lianggao/Downloads/com/gao/algorithm/integer/IntegerOne.class);try{byte[]bytesgetClassBytes(file);//将二进制流字节组成的文件转换为一个java.lang.Classreturnthis.defineClass(name,bytes,0,bytes.length);}catch(Exception e){e.printStackTrace();}returnsuper.findClass(name);}// 这里要读入.class的字节因此要使用字节流privatebyte[]getClassBytes(File file)throws Exception{try(FileInputStream fisnewFileInputStream(file);ByteArrayOutputStream baosnewByteArrayOutputStream();){//我们这里可以读到之后可以对文件进行解密。。。 IOUtils.copy(fis,baos);returnbaos.toByteArray();}catch(Exception e){e.printStackTrace();}returnnewbyte[0];}}控制台输出//com.gao.jvm.classloader.MyClassLoader610455d6需要注意 :最好不要重写loadClass方法因为这样容易破坏双亲委托模式。这类Test 类本身可以被 AppClassLoader 类加载因此我们不能把com/gao/algorithm/integer/IntegerOne.class 放在类路径下。否则由于双亲委托机制的存在会直接导致该类由 AppClassLoader 加载而不会通过我们自定义类加载器来加载。https://pdai.tech/md/java/jvm/java-jvm-classload.html