Java程序运行图:
html
上一篇玩命学JVM(一)—认识JVM和字节码文件咱们简单认识了 JVM 和字节码文件。那JVM是如何使用字节码文件的呢?从上图清晰地能够看到,JVM 经过类加载器完成了这一过程。java
如下是类加载机制的知识框架:数组
接下来咱们对思惟导图中重难点部分作补充。数据结构
类的加载就是将 .class 文件的二进制数据读入到内存中,将其放在 JVM 的运行时数据区的方法区内。而后在堆区内建立一个 java.lang.Class 对象,用于封装类在方法区内的数据结构。框架
双亲委派模型图以下:
ide
对于“双亲委派模型”,首先须要纠正一点,“双亲”并非说它有“两个亲”。实际上行“双亲委派模型”和“双”毫无关系,只和“亲”有关系。
其实“双亲”是翻译的一个错误,原文出处是“parent”,被翻译成了“双亲”,在计算机领域更常见的说法是“父节点”。因此若是将“双亲委派模型”改成“父委派模型”,应该更好理解。函数
结合实际的类加载器来讲,就是:测试
接下来咱们从源码上来分析下 双亲委派模型
除Bootstrap ClassLoader
外,其它的类加载器都是ClassLoader
的子类。加载类的方法为loadClass
,查看源码可发现,loadClass
在ClassLoader
中有具体的实现,且在各个子类中都没有被覆盖。this
先介绍三个重要的函数,对后续的源码阅读有帮助:
loadClass
:调用父类加载器的loadClass,加载失败则调用本身的findClass方法。
findClass
:根据名称读取文件存入字节数组。
defineClass
:把一个字节数组转为Class对象。.net
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 在JVM中查看类是否已经被加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { // 调用父类加载器的 loadClass方法,parent是该类加载器的父类,parent的值可能为 Application ClassLoader、Extension ClassLoader,当想要继续往上找 Extension ClassLoader时,因为Bootstrap ClassLoader是C/C++实现的,因此在java中是Null c = parent.loadClass(name, false); } else { // 寻找 Bootstrap ClassLoader 加载 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(); // 父加载器开始尝试加载.class文件,加载成功就返回一个java.lang.Class,加载不成功就抛出一个ClassNotFoundException,给子加载器去加载 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) { // 若是要解析这个.class文件的话,就解析一下,解析的做用主要就是将符号引用替换为直接引用的过程 resolveClass(c); } return c; } }
所谓的双亲委派模型,就是利用了loadClass
只在父类中实现了这一点。
自定义类加载主要有两种方式:
遵照双亲委派模型:继承ClassLoader,重写findClass()方法。
破坏双亲委派模型:继承ClassLoader,重写loadClass()方法。 一般咱们推荐采用第一种方法自定义类加载器,最大程度上的遵照双亲委派模型。
咱们看一下实现步骤
(1)建立一个类继承ClassLoader抽象类
(2)重写findClass()方法
(3)在findClass()方法中调用defineClass()
第一步,自定义一个实体类Person.java,我把它编译后的Person.class放在D盘根目录下:
package com.xrq.classloader; public class Person { private String name; public Person() { } public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return "I am a person, my name is " + name; } }
第二步,自定义一个类加载器,里面主要是一些IO和NIO的内容,另外注意一下 defineClass方法能够把二进制流字节组成的文件转换为一个java.lang.Class----只要二进制字节流的内容符合Class文件规 范。咱们自定义的MyClassLoader继承自java.lang.ClassLoader,就像上面说的,只实现findClass方法:
public class MyClassLoader extends ClassLoader { public MyClassLoader() { } public MyClassLoader(ClassLoader parent) { super(parent); } protected Class<?> findClass(String name) throws ClassNotFoundException { File file = getClassFile(name); try { byte[] bytes = getClassBytes(file); Class<?> c = this.defineClass(name, bytes, 0, bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private File getClassFile(String name) { File file = new File("D:/Person.class"); return file; } private byte[] getClassBytes(File file) throws Exception { // 这里要读入.class的字节,所以要使用字节流 FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel wbc = Channels.newChannel(baos); ByteBuffer by = ByteBuffer.allocate(1024); while (true) { int i = fc.read(by); if (i == 0 || i == -1) break; by.flip(); wbc.write(by); by.clear(); } fis.close(); return baos.toByteArray(); } }
第三步,Class.forName有一个三个参数的重载方法,能够指定类加载器,平时咱们使用的Class.forName("XX.XX.XXX")都是使用的系统类加载器Application ClassLoader。写一个测试类:
public class TestMyClassLoader { public static void main(String[] args) throws Exception { MyClassLoader mcl = new MyClassLoader(); Class<?> c1 = Class.forName("com.xrq.classloader.Person", true, mcl); Object obj = c1.newInstance(); System.out.println(obj); System.out.println(obj.getClass().getClassLoader()); } }
运行结果:
I am a person, my name is null
com.xrq.classloader.MyClassLoader@5d888759
https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc
https://blog.csdn.net/qq_44836294/article/details/105439753