本文整理了基于JDK8的ClassLoader核心知识点,包括JVM中ClassLoader的类型、ClassLoader的执行顺序、父加载器的概念、父类委托机制和自定义类加载器。JDK和JRE的作用JDK提供了java编程环境,包括编译和调试环境功能,其中包括JRE(JDK目录下的JRE是专用的JRE,安装后和JDK同目录下的JRE是公共的JRE)。开发时一般运行JDK专用的JRE,运行外部程序时一般运行通用的JRE,这样不同分工的jres负责各自范围内的内容。JRE为JAVA程序运行提供必要的环境平台JAVAHOME,PATH,CLASSPATHJAVAHOME:JDK安装位置路径,如:D:\ProgramFiles\Java\jdk1.8.0_241PATH:配置后运行bin中的命令不需要待补完整路径,如java、javac命令可在任意位置运行,%JAVA_HOME%\bin;CLASSPATH:指向jar包路径%JAVA_HOME%\lib;类加载器的类型由JVM中的三种ClassLoader组成:BootstrapClassLoader启动类(或根类)加载器ExtentionClassLoader扩展类加载器AppclassLoader应用类加载器(一)BootstrapClassLoaderBootstrapClassLoader顶层类加载器,主要加载rt.jar和核心类库%JRE_HOME%\lib.jar下的资源,charsets.jar和类文件等。//执行System.out.println(System.getProperty("sun.boot.class.path"));//输出结果D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\resources.jar;D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\rt.jar;D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\sunrsasign.jar;D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\jsse.jar;D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\jce.jar;D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\charsets.jar;D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\jfr.jar;D:\ProgramFiles\Java\jdk1.8.0_241\jre\classes(2)ExtentionClassLoaderExtentionClassLoader扩展类加载器,主要加载目录%jarJRE_HOME%\lib\ext目录中的包和类文件。//执行System.out.println(System.getProperty("java.ext.dirs"));//输出D:\ProgramFiles\Java\jdk1.8.0_241\jre\lib\ext;C:\Windows\Sun\Java\lib\ext(3)AppclassLoaderAppclassLoader,也称为SystemAppClass,加载当前应用程序类路径下的所有类。类加载器的执行顺序除了引导类加载器(BootstrapClassLoader)外,扩展类加载器和应用类加载器都是通过类sun.misc.Launcher初始化的,Launcher类由引导类加载器加载。Launcher相关代码如下:publicLauncher(){Launcher.ExtClassLoadervar1;try{//初始化扩展类加载器,构造函数无参数,无法获取启动类加载器var1=Launcher.ExtClassLoader.getExtClassLoader();}catch(IOExceptionvar10){thrownewInternalError("Couldnotcreateextensionclassloader",var10);}try{//初始化应用类加载器,入参为扩展类加载器this.loader=Launcher.AppClassLoader.getAppClassLoader(var1);}catch(IOExceptionvar9){thrownewInternalError("Couldnotcreateapplicationclassloader",var9);}//设置上下文类加载器Thread.currentThread().setContextClassLoader(this.loader);//...}加载顺序:BootstrapCLassloder>ExtentionClassLoader>AppClassLoader父加载器概念AppClassLoader和ExtClassLoader都继承了URLClassLoader。每个类加载器都有一个父加载器(注意:父类和父加载器是两个不同的概念),可以通过getParent()获取父类加载器。System.out.println("ClassLoaderis:"+cl.toString());System.out.println("ClassLoader\'sparentis:"+cl.getParent().toString());System.out.println("ClassLoader的祖父是:"+cl.getParent().getParent().toString());AppClassLoader的父加载器是ExtClassLoaderExtClassLoader的父加载器是BootstrapClassLoader(上面代码输出ExtClassLoader为null是因为BootstrapClassLoader本身不是Java类所致)BootstrapClassLoader是用C/C++写的,它是虚拟机本身的一部分,所以它不是JAVA类,即在java代码中不能被引用,JVM启动时使用Bootstrap类加载器加载核心jar包中的类文件如rt.jar,以及前面的int。class和String.class都是由它加载的。然后,正如我们前面分析的那样,JVM初始化sun.misc.Launcher并创建ExtensionClassLoader和AppClassLoader实例。并将ExtClassLoader设置为AppClassLoader的父加载器。Bootstrap没有父加载器,但它可以充当ClassLoader的父加载器。双亲委托双亲委托模型:当一个类加载器收到一个类加载请求时,它会先请求它的父类加载器加载,然后依次递归。当父类加载器找不到该类时(根据类的完全限定名),子类加载器将尝试加载它。为什么时序图要用双亲委派模型呢?双亲委派模型是为了保证Java核心库的类型安全。所有的Java应用程序都至少需要一个java.lang.Object类的引用,这个类需要在运行时加载到Java虚拟机中。如果加载过程是由自定义类加载器完成的,则可能存在多个版本的java.lang.Object类,并且这些类不兼容。通过双亲委派模型,Java核心库中类的加载由启动类加载器统一完成,保证了同版本的Java核心库中的类被Java应用程序使用并相互兼容。无论自定义类加载器是BootstrapClassLoader还是ExtClassLoader,这些类加载器只加载指定目录下的jar包或资源。如果我们需要动态加载,比如从指定目录加载class文件,可以通过自定义类加载器来实现。自定义类加载器只需要继承java.lang.ClassLoader类,然后重写findClass(Stringname)方法,在方法中指定如何获取该类的字节码流。如果要打破双亲委托规范,需要重写loadClass方法(双亲委托的具体逻辑实现)。但不建议这样做。publicclassClassLoaderTestextendsClassLoader{privateStringclassPath;publicClassLoaderTest(StringclassPath){this.classPath=classPath;}/***写findClass方法的逻辑**@paramname*@return*@throwsClassNotFoundException*/@OverrideprotectedClass>findClass(Stringname)throwsClassNotFoundException{//获取类文件字节数组byte[]classData=getClassData(name);if(classData==null){thrownewClassNotFoundException();}else{//生成类对象returndefineClass(name,classData,0,classData.length);}}/***写出获取class文件并转为字节码流的逻辑**@paramclassName*@return*/privatebyte[]getClassData(StringclassName){//读取class文件的字节Stringpath=classNameToPath(className);try{InputStreamis=newFileInputStream(path);ByteArrayOutputStreamstream=newByteArrayOutputStream();byte[]buffer=newbyte[2048];intnum=0;//读取类文件的字节码while((num=is.read(buffer))!=-1){stream.write(buffer,0,num);}returnsstream.toByteArray();}catch(IOExceptione){e.printStackTrace();}returnnull;}/***类文件完整路径**@paramclassName*@return*/privateStringclassNameToPath(StringclassName){returnclassPath+File.separatorChar+className.replace('.',File.separatorChar)+".class";}publicstaticvoidmain(String[]args){StringclassPath="/Users/zzs/my/article/projects/java-stream/src/main/java/";ClassLoaderTestloader=newClassLoaderTest(classPath);try{//加载指定的类文件Class>object1=loader.loadClass("com.secbro2.classload.SubClass");System.out.println(object1.newInstance().toString());}catch(Exceptione){e.printStackTrace();}}}
