前言
上一篇玩命学JVM(一)—认识JVM和字节码文件我们简单认识了 JVM 和字节码文件。那JVM是如何使用字节码文件的呢?从上图清晰地可以看到,JVM 通过类加载器完成了这一过程。
以下是类加载机制的知识框架:
接下来我们对思维导图中重难点部分做补充。
1. 是什么?
类的加载就是将 .class 文件的二进制数据读入到内存中,将其放在 JVM 的运行时数据区的方法区内。然后在堆区内创建一个 java.lang.Class 对象,用于封装类在方法区内的数据结构。
5. 双亲委派模型
对于“双亲委派模型”,首先需要纠正一点,“双亲”并不是说它有“两个亲”。实际上行“双亲委派模型”和“双”毫无关系,只和“亲”有关系。
其实“双亲”是翻译的一个错误,原文出处是“parent”,被翻译成了“双亲”,在计算机领域更常见的说法是“父节点”。所以如果将“双亲委派模型”改为“父委派模型”,应该更好理解。
结合实际的类加载器来说,就是:
- 每个类加载器都会向上找自己父类加载器尝试完成类加载;
- 父加载器加载失败会向下找加载器尝试加载。
如图:
接下来我们从源码上来分析下 双亲委派模型
除Bootstrap ClassLoader
外,其它的类加载器都是ClassLoader
的子类。加载类的方法为loadClass
,查看源码可发现,loadClass
在ClassLoader
中有具体的实现,且在各个子类中都没有被覆盖。
先介绍三个重要的函数,对后续的源码阅读有帮助:
loadClass
:调用父类加载器的loadClass,加载失败则调用自己的findClass方法。
findClass
:根据名称读取文件存入字节数组。
defineClass
:把一个字节数组转为Class对象。
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