Java 类加载机制详解
本文参考网址:https://mp.weixin.qq.com/s/Pmq73a4MAb5BWTsvX2XVcA
何为 Java 类加载机制?
- Java 虚拟机使用 Java 类的流程为:首先将开发者编写的 Java 源代码(.java文件)编译成 Java 字节码(.class文件),然后类加载器会读取 .class 文件,并转换成 java.lang.Class 的实例。有了该 Class 实例后,Java 虚拟机可以利用 newInstance 之类的方法创建其真正对象了。
- ClassLoader 是 Java 提供的类加载器,绝大多数的类加载器都继承自 ClassLoader,它们被用来加载不同来源的 Class 文件。
- public abstract class ClassLoaderextends Object
Class 文件来源?
- 既然是类加载器(ClassLoader)读取字节码文件(.class文件),那么.class文件有哪些来源呢?
- 最常见的是开发者在应用程序中编写的类,这些类位于项目目录下,会由.java文件自动编译成.class文件;
- Java 内部自带的核心类如 java.lang、java.math、java.io 等包内部的类,位于 $JAVA_HOME/jre/lib/ 目录下,如 java.lang.String 类就是定义在 $JAVA_HOME/jre/lib/rt.jar 文件里(jar文件就是打包编译好的.class文件集合)
- Java 核心扩展类,位于 $JAVA_HOME/jre/lib/ext 目录下,开发者也可以把自己编写的类打包成 jar 文件放入该目录下
- 最后一种是动态加载远程的 .class 文件。
对应的类加载器加载对应Class
- 针对上面四种来源的类文件(.class文件),分别有不同的加载器负责加载。
- 级别最高的 Java 核心类,即$JAVA_HOME/jre/lib 里的核心 jar 文件。这些类是 Java 运行的基础类,由一个名为 BootstrapClassLoader 加载器负责加载,它也被称作 根加载器/引导加载器。注意 BootstrapClassLoader 比较特殊,它不继承 ClassLoader,而是由 JVM 内部实现;
- Java 核心扩展类,即 $JAVA_HOME/jre/lib/ext 目录下的 jar 文件。这些文件由 ExtensionClassLoader 负责加载,它也被称作 扩展类加载器。用户如果把自己开发的 jar 文件放在这个目录,也会被 ExtClassLoader 加载;
- 开发者在项目中编写的类,这些文件将由 AppClassLoader 加载器进行加载,它也被称作 系统类加载器 System ClassLoader;
- 如果想远程加载(如本地文件/网络下载)的方式,则必须要自己自定义一个 ClassLoader,复写其中的 findClass() 方法才能得以实现。
- 因此能看出,Java 里提供了至少四类 ClassLoader 来分别加载不同来源的 Class。
类加载流程
- 不同的ClassLoader加载不同的类,那么运行时是如何协作来加载一个类呢?
- 当查找一个类时,优先BootstrapClassLoader遍历最高级别的 Java 核心类,然后ExtensionClassLoader再去遍历 Java 核心扩展类,最后AppClassLoader 再遍历用户自定义类,而且这个遍历过程是一旦找到就立即停止遍历。在 Java 中,这种实现方式也称作 双亲委托。
- 可以将 BootstrapClassLoader 想象为核心高层领导人, ExtClassLoader 想象为中层干部, AppClassLoader 想象为普通公务员。每次需要加载一个类,先获取一个系统加载器 AppClassLoader 的实例(ClassLoader.getSystemClassLoader()),然后向上级层层请求,由最上级优先去加载,如果上级觉得这些类不属于核心类,就可以下放到各子级负责人去自行加载。
累加器 验证
验证 AppClassLoader
- 下面可以验证,MusicPlayer 是由 AppClassLoader 进行加载的。
package test.classLoadTest; /** * Created by Administrator on 2018/6/21 0021. * 音乐播放器 */ public class MusicPlayer { /**模拟播放音乐*/ public void palyer() { System.out.println("music is player ..."); } public static void main(String[] args) { try { /**获取Class实例,forName方法参数是类的全路径*/ Class aClass = Class.forName("test.classLoadTest.MusicPlayer"); /**获取累加器对象*/ ClassLoader classLoader = aClass.getClassLoader(); /**获取累加器的名称 * 输出:sun.misc.Launcher$AppClassLoader*/ System.out.println(classLoader.getClass().getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
验证双亲委托
- 可以发现ExtClassLoader 确实是 AppClassLoader 的双亲,不过却没有看到 BootstrapClassLoader。
- 因为 BootstrapClassLoader 比较特殊,它是由 JVM 内部实现的,所以 ExtClassLoader.getParent() = null。
/** * Created by Administrator on 2018/6/21 0021. * 音乐播放器 */ public class MusicPlayer { /**模拟播放音乐*/ public void palyer() { System.out.println("music is player ..."); } public static void main(String[] args) { try { /**获取Class实例,forName方法参数是类的全路径*/ Class aClass = Class.forName("test.classLoadTest.MusicPlayer"); /**获取累加器对象*/ ClassLoader classLoader = aClass.getClassLoader(); /**获取累加器的名称 * 输出:class sun.misc.Launcher$ExtClassLoader*/ ClassLoader extClassLoader = classLoader.getParent(); System.out.println(extClassLoader.getClass().toString()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
自定义类加器
- 自定义类加载器,允许JVM在运行时可以从本地磁盘或网络上动态加载自定义类(.class文件)。这使得开发者可以动态修复某些有问题的类,热更新代码。
- 下面来实现一个网络类加载器,这个加载器可以从网络上动态下载 .class 文件并加载到虚拟机中使用。
- 自定义流程如下:
- 自定义 NetworkClassLoader 类继承 ClassLoader
- 然后实现 ClassLoader 类的 findClass() 方法
- ClassLoader 自己提供了 loadClass(),它会基于双亲委托机制去搜索某个 class,当搜索不到时会调用自身的findClass(),如果直接复写loadClass(),那还要实现双亲委托机制;
- 在 findClass() 方法里,要从网络上下载一个 .class 文件,然后转化成 Class 对象供虚拟机使用。
class文件准备
package org.xyjt.filter; /** * Created by Administrator on 2018/6/21 0021. * 音乐播放器 */ public class MusicPlayer { /** * 模拟播放音乐 */ public void player() { System.out.println("music is player ..."); } /** * 音乐直播 * * @param musicName:歌曲名称 * @return */ public String musicLivePlayer(String musicName) { return "music " + musicName + " player is success"; } }
NetworkClassLoader
package com.lct.utils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; /** * Created by Administrator on 2018/6/21 0021. * 自定义网络类加载器---继承ClassLoader */ public class NetworkClassLoader extends ClassLoader { /** * 必须重写findClass方法 * * @param name * @return * @throws ClassNotFoundException */ @Override protected Class<?> findClass(String name) throws ClassNotFoundException { if (name == null || "".equals(name.trim())) { return null; } /**下载网络上的Class文件,返回字节数组*/ byte[] classData = downloadClassData(name); if (classData == null || classData.length <= 0) { super.findClass(name); } else { /**调用ClassLoader重载的defineClass方法 * 将一个 byte 数组转换为 Class 类的实例。必须分析 Class,然后才能使用它。 * name:所需要的类的二进制名称,如果不知道此名称,则该参数为 null * classData已经包含了class信息,name设置为null也是可以的 * 当name不为null时,必须是目标类的全路径,如:org.xyjt.filter.MusicPlayer */ return defineClass(null, classData, 0, classData.length); } return null; } /** * 下载网络上的Class文件 * * @param name :.class文件名,也是目标类的名字,如:MusicPlayer * @return 返回字节数组 */ public byte[] downloadClassData(String name) { try { /** * path拼接完成后如:http://localhost:8080/webProject/javaClass/MusicPlayer.class * 这个路径只有确保网络上能访问到此.class文件即可,为了方便立即才写死在这 * 实际中路径前缀应该放到配置文件中 */ String path = "http://localhost:8080/webProject/javaClass/" + name + ".class"; URL url = new URL(path); InputStream inputStream = url.openStream(); /**用一个字节数组输出流来接收 * 边读边存,都是常用的IO操作*/ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int buffSize = 2048; byte[] buffer = new byte[buffSize]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { byteArrayOutputStream.write(buffer, 0, bytesRead); } /**最后返回字节数组*/ return byteArrayOutputStream.toByteArray(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } public static void main(String[] args) { try { /**调用的目标.class文件的名称*/ String className = "MusicPlayer"; /**用自定义类加载器类加载此类 * loadClass方法会使用双亲委托,当找不到时,它默认就是调用上面的findClass方法 * 当然也可以直接networkClassLoader.findClass("className)*/ NetworkClassLoader networkClassLoader = new NetworkClassLoader(); /**aClass就是网络上的动态加载的org.xyjt.filter.MusicPlayer类*/ Class aClass = networkClassLoader.loadClass(className); /** 这里肯定输出的是自定义累加器而不是AppClassLoader、ExtClassLoader、BootstrapClassLoader*/ System.out.println("1、"+aClass.getClassLoader().getClass().getName()); System.out.println("2、"+aClass.getName()); /**调用MusicPlayer方法*/ Method playerMethod = aClass.getMethod("player", null); playerMethod.invoke(aClass.newInstance(),null); Method musicLivePlayerMethod = aClass.getMethod("musicLivePlayer", String.class); Object result = musicLivePlayerMethod.invoke(aClass.newInstance(), "千年等一回"); System.out.println("3、"+result); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }