JVM 类加载机制之 ClassLoader
原创...大约 3 分钟
简述
JVM 有三种类加载器:
- BootStrap ClassLoader 启动类加载器,加载核心类库,主要加载核心类库,%JRE_HOME%\lib 下的 rt.jar、resources.jar、charsets.jar 和 class 等。
- Extention ClassLoader 扩展类加载器,加载目录%JRE_HOME%\lib\ext 目录下的 jar 包和 class 文件。
- App ClassLoader 应用程序加载器,加载当前应用的 classpath 的所有类。
除以上三种外,还有用户自定义的类加载器。
每个类由加载它的类加载器和类本身确定其唯一性。也就是说,类加载器不同,类肯定不同。
加载过程
在加载类时,通过**“双亲委托”机制,依次从1** -> 3向上查询,再从3->1依次返回结果:
- 调用
findLoadedClass(className)
查询是否已经加载该类 - 调用父加载器的
loadClass(className,false)
,若父加载器为空,则调用BootStrap ClassLoader
- 如果还是没有加载到该类,调用
findClass(className)
这样子保证了每个类都是先经过最顶端的类加载器BootStrap ClassLoader
,如果没有加载到再依次经过Extention ClassLoader
、App ClassLoader
加载,确保如 String 等关键类不会被自定义的 ClassLoader 加载而导致异常。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检测是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空则调用父加载器的 loadClass
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();
//父加载器没有找到,则调用 findclass
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()
resolveClass(c);
}
return c;
}
}
AppClassLoader
和ExtClassLoader
都继承自URLClassLoader
AppClassLoader
的父加载器是ExtClassLoader
,ExtClassLoader
的父加载器为null
,故而会调用BootStrap ClassLoader
ClassLoader 如果没有指定父加载器,则默认的父加载器为AppClassLoader
,自定义 ClassLoader 也是如此。
自定义 ClassLoader
自定义 ClassLoader 一般步骤:
- 继承自
ClassLoader
- 重写
findClass()
- 在
findClass()
方法中调用并返回defineClass()
class MClasLoader:ClassLoader(){
override fun findClass(name: String?): Class<*> {
var sysDir = System.getProperty("user.dir")
var classPath = "$sysDir/src/main/res/$name.class"
var classFile = File(classPath)
//【注意】这里一次只读取一个字节,否则会报错 java.lang.ClassFormatError:
// Extra bytes at the end of class file TestClass
var bytes = ByteArray(1)
var fileInputStream = FileInputStream(classFile)
var len = -1
var byteBuffer = ByteOutputStream()
while (true) {
len = fileInputStream.read(bytes)
if (len > 0) {
byteBuffer.write(bytes)
} else {
break
}
}
var byteArr = byteBuffer.toByteArray()
return defineClass(name,byteArr,0,byteArr.size)
}
}
fun main(args: Array<String>) {
var clazz = MClasLoader().loadClass("TestClass")
var say = clazz.getDeclaredMethod("say",String::class.java)
say.invoke(clazz.newInstance(),"hello world")
print(MClasLoader().parent)
}
而defineClass()
则将一个字节数组转化为一个类的实例(Converts an array of bytes into an instance of class with an optional ProtectionDomain)
contextClassLoader
每个线程都有一个 ClassLoader:contextClassLoader
,通过将其设置为自定义的 ClassLoader 可以在加载类的时候做一些特殊的事情。
Thread.currentThread().contextClassLoader = MClasLoader()
var clazz = Class.forName("TestClass",
true, Thread.currentThread().contextClassLoader)
var say = clazz.getDeclaredMethod("say",String::class.java)
say.invoke(clazz.newInstance(),"hello world")
println(Thread.currentThread().contextClassLoader )
println(Thread.currentThread().contextClassLoader.parent )
结果为:
hello world
MClasLoader@1d44bcfa
sun.misc.Launcher$AppClassLoader@18b4aac2
文章标题:《JVM 类加载机制之 ClassLoader》
本文地址: https://jixiaoyong.github.io/blog/posts/b3994218.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!