源于蚂蚁课堂的学习,点击这里查看(老余很给力)
Java底层是由C++去编写的,而C/C++由是封装汇编指令,进而转为二进制被计算机识别。所以当我们作为Java开发者,写好Java代码后,是需要进过JavaComplier进行编译生成class字节码文件,然后由类加载器将字节码文件加载至Java虚拟机内存中进行使用,JVM通过调用C的指令转汇编,从而使得代码生效。
本文将简单聊聊博主对Java类加载机制相关知识的理解。
这对于大多数开发者而已,应该耳熟能详,.java文件经过Java编译器的编译后生成.class文件,交由JVM运行。Java开发者引以为豪的地方就是Java的代码,“一次编程,导出使用”。其缘由,在不同的操作系统上,有着不同的JVM,所以我们代码只需要编译一次即可,不同JVM加载相同的class字节码文件,通过硬件层面屏蔽操作系统的一些指令,从而使得代码产生相同的功能。
Java文件编译为class文件后,需要通过类加载器加载至虚拟机内存中。那么,哪些情况下,类加载器才会去加载class文件呢?
如new对象,反射生成,反序列化,克隆。都会先加载类的信息。
访问类的静态方法、静态变量等类特有信息时,会把类加载至内存 。
通过反射的方式主动加载类。
此类中包含Java应用程序运行的入口,则一定会被加载。
子类加载时一定会先加载父类。
类加载器就是将class字节码加载至JVM的工具。通常分为四种:
最上层的类加载器,主要职责为加载$JAVA_HOME/jre/lib下的class。
第二层类加载器,主要职责为加载$JAVA_HOME/jre/lib/ext下的class。
我们自己创建的工程中的类加载器,加载自己项目中定义的那些class。
自定义的类加载器,可以加载指定范围下的class
当类被触发加载时,会先找到当前线程的类加载器,向上查找至最顶层,也就是启动类加载器,然后自上而下判断当前类加载器中是否有此类的class,没有就向下查找,否则,就行class的加载并停止向下查找。 我们可以通过源码得出此规律。
ClassLoader通过loadClass()方法加载类,此方法中有一处核心代码。 if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); }这里parent指的是当前线程类加载器的上级类加载器。通过此代码可以看出,从当前线程类加载器不断向上查找,直到parent == null,即查找到当前类加载器为扩展类加载器(注意,启动类加载器是C++编写,非Java对象,故扩展类加载器的parent为null,也代表启动类加载器)。开始执行findBootstrapClassOrNull方法。
private Class<?> findBootstrapClassOrNull(String name) { if (!checkName(name)) return null; return findBootstrapClass(name); } private native Class<?> findBootstrapClass(String name);源码可得,最终通过本地方法栈调用findBootstrapClass去启动类加载器的职责中查找当前类的信息。
如果启动类加载器查找不到,则进入下面代码 if (c == null) { 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(); }而这个findClass方法是非常重要的方法,自定义类加载器都会重写findClass而非loadClass(类似于模板方法设计模式的原则)。
默认采用URLClassLoader,在此类中重写的findClass方法根据参数className进行文件地址的拼接,然后去扩展类加载器中查找此类,扩展类加载器没有的话,再向下查找。
双亲委派机制最大好处是当我们自己定义的类全路径名称和JDK定义的冲突时,不会冲突,而是按照优先级加载。
双亲委派机制核心点在于classLoader的loadClass方法中循环查找。所以想要打破双亲委派原则,我们只需饶佐loadClass或者重新findClass即可。
可以将指定接口的实现类加载至内存中,其约定了在/resources/services/META-INF/下定义以接口全类名为文件名的文件,内容是接口的实现类。如果通过ServiceLoad.load(接口.class)的方式返回接口的实现类集合。在其遍历使用时,进行加载。
public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } 获取当前线程类加载器后,调用load方法。 public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }创建ServiceLoader对象。值的一提的是,此类的属性中private static final String PREFIX = "META-INF/services/";此处约定了文件地址。
构造方法中初始化了一个迭代器,且设置类加载器(如果没有就为当前的appClassLoader)
private ServiceLoader(Class<S> svc, ClassLoader cl) { service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; reload(); } public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }当我们遍历这个迭代器时,其netx方法中进行配置文件的读取,直接通过类加载器将类加载
private boolean hasNextService() { if (nextName != null) { return true; } if (configs == null) { try { String fullName = PREFIX + service.getName(); if (loader == null) configs = ClassLoader.getSystemResources(fullName); else configs = loader.getResources(fullName); } catch (IOException x) { fail(service, "Error locating configuration files", x); } } while ((pending == null) || !pending.hasNext()) { if (!configs.hasMoreElements()) { return false; } pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
通过继承的方式我们可以自己定义一个类加载器,然后重写其findClass方法(官方推荐),也可以直接重写loadClass方法(但是如果这么做了,相当于此类加载器想要加载一个类,类的最上层Object需要它手动加载才行,太繁琐)。
原理即绕过这个class加载时触发双亲委派原则的逻辑即可。附一个自定义的类加载器
package live.yanxiaohui.classLoader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; /** * @Description TODO * @ https://blog.csdn.net/yxh13521338301 * @Author yanxh<br> * @Date 2020/10/22 9:46<br> * @Version 1.0<br> */ public class YXHClassLoader extends ClassLoader { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { try { byte[] data = getClassFileBytes(getFileObject()); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); return null; } } private File fileObject; public void setFileObject(File fileObject) { this.fileObject = fileObject; } public File getFileObject() { return fileObject; } /** * 官方不建议重写该loadClass * * @param name * @return * @throws ClassNotFoundException */ // @Override // protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // return super.loadClass(name, resolve); // } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { // 读取我们硬盘上的class文件 try { byte[] data = getClassFileBytes(getFileObject()); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 从文件中读取去class文件 * * @throws Exception */ private byte[] getClassFileBytes(File file) throws Exception { //采用NIO读取 FileInputStream fis = new FileInputStream(file); FileChannel fileC = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = Channels.newChannel(baos); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (true) { int i = fileC.read(buffer); if (i == 0 || i == -1) { break; } buffer.flip(); outC.write(buffer); buffer.clear(); } fis.close(); return baos.toByteArray(); } }
欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!
公众号:帝都的雁