一,类加载器基础巩固
类加载器是JVM的重要核心组件之一,也是字节码执行的发源地,只有准确加载了类,JVM才能够创建对象。
一般地有三种类加载器,其名称、对应的对象类以及作用分别是(以JDK8为例):
启动类加载器——加载JRE库文件
用于加载rt.jar等11份文件,如下图所示,
扩展类加载器——加载JRE扩展文件
用于加载dnsns.jar等12份文件,如下图所示,
系统类加载器——加载类路径(classPath)下的所有文件
后两种类加载器均是sun.misc.Launcherd的内部嵌套静态类且继承了java.net包中的URLClassLoader类,启动类使用的加载器为null。
二,使用场景
1.对类进行动态地加载、使用和卸载
常用在Web系统中对编解码器的开发,将编解码器按照一定规则编写好后打包成外部jar包,上传到系统平台中,通过类加载器对jar包的加载完成编解码对象的生成,进而使用其编解码方法。不需要重新编译整个系统程序,完成代码的局部更新;
2.自定义类加载器
通过自定义类加载器,
三,打包一个jar
3.1 功能
把字符串解码为新格式的字符串
3.2 解码接口
public interface DecodeToPOJO { Object decode(String hexContent); }
3.3 解码接口实现类
public class Decoder implements DecodeToPOJO { @Override public Object decode(String hexContent) { return "say Hi"; } public static void main(String[] args) { Decoder decoder = new Decoder(); System.out.println(decoder.decode("")); } }
3.4 目的
在HTTP GET接口中通过加载jar包的形式调用Decoder类的decode方法。
3.5 生成jar包
四,编写一个接口加载类
@RequestMapping("/doClassLoad") public String doClassLoad() throws IOException { long start = System.currentTimeMillis(); ClassLoader classLoader = JarClassLoader.loadJarToSystemClassLoader(new File("F:\\respository\\MessageTransformer\\out")); Class aClass = null; try { aClass = classLoader.loadClass("org.leobit.codec.Decoder"); System.out.println(aClass.hashCode()); } catch (Exception e) { e.printStackTrace(); } long end = System.currentTimeMillis(); return Long.toString(end - start); }
JarClassLoader
类在cn.hutool.hutool-core
包中,省的自己编写了。
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> <version>4.4.0</version> </dependency>
五,源码分析
我们主要关注加载类方法。
aClass = classLoader.loadClass("org.leobit.codec.Decoder");
进入该方法,可以看到该方法是线程安全的,首选检查类是否被加载,未加载的选择合适的类加载器进行加载,这就是双亲委派机制,
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; } }
重点看findClass
方法,实现类是URLClassLoader
,findClass
方法后的sun.misc.PerfCounter
表示性能计数器,可以看到生成类对象后,对象类计数加1。
protected Class<?> findClass(final String name) throws ClassNotFoundException { final Class<?> result; try { result = AccessController.doPrivileged( new PrivilegedExceptionAction<Class<?>>() { public Class<?> run() throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class"); Resource res = ucp.getResource(path, false); if (res != null) { try { return defineClass(name, res); } catch (IOException e) { throw new ClassNotFoundException(name, e); } } else { return null; } } }, acc); } catch (java.security.PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } if (result == null) { throw new ClassNotFoundException(name); } return result; }
在来看defineClass
方法,就是最终的生成对象的方法。
private Class<?> defineClass(String name, Resource res) throws IOException { long t0 = System.nanoTime(); int i = name.lastIndexOf('.'); URL url = res.getCodeSourceURL(); if (i != -1) { String pkgname = name.substring(0, i); // Check if package already loaded. Manifest man = res.getManifest(); definePackageInternal(pkgname, man, url); } // Now read the class bytes and define the class java.nio.ByteBuffer bb = res.getByteBuffer(); if (bb != null) { // Use (direct) ByteBuffer: CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); return defineClass(name, bb, cs); } else { byte[] b = res.getBytes(); // must read certificates AFTER reading bytes. CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); return defineClass(name, b, 0, b.length, cs); } }
一直重载定义类的方法,最后终于在defineClass1Native方法中创建出对象。
可以看到就是将文件流读入到虚拟机中进行对象类的创建,该过程又包含class文件的校验、类变量的准备、方法的解析和初始化的阶段。
六,加载jar包结果展示
我们请求第4节中编写的接口,打印每次加载类的哈希码。
可以看到通过同一类加载器加载的对象的哈希码是相同的。