我们通过ide写的java代码,毫无疑问是最终需要加载到JVM来运行的。试想JVM作为跨语言的平台,能同时支持多种编程语言(js、groory、scala…等)的字节码文件运行,那么在字节码文件和JVM之间,必须有一套完备的流程,来将字节码文件转化为JVM内存中的变量信息。
下图,是坊间盛传的JVM类加载过程,今天我们来探一探水深。
sun.misc.Launcher的方法很简单,静态代码实例了launcher,供getLauncher()方法获取launcher对象。
但是,当给我们他通过debug打点到launcher()构造方法获取ExtClassLoader、AppClassLoader时,却发现断点进不到46、52行。是思路错了还是断点无效?
于是我又翻了翻 IBM 关于 Java 中 Debug 实现原理的介绍,文章地址如下:https://www.ibm.com/developerworks/cn/java/j-lo-jpda1/
JDI(Java Debug Interface)是三个模块中最高层的接口,在多数的 JDK 中,它是由 Java 语言实现的。
参考 Oracle 的官方文档:https://docs.oracle.com/javase/9/docs/api/jdk.jdi-summary.html
可以知道 jdi 是一个位于 tools.jar 包下的子包,而 tools.jar 也是由 BootStrap 类加载器负责加载的
可以明确,Debug时调用的是lib/tools.jar的包,而此时启动类加载器势必已经完成启动,所以debug无效。
类加载器定义:
class_path = SecuritySupport.getSystemProperty("java.class.path");
boot_path = SecuritySupport.getSystemProperty("sun.boot.class.path");
ext_path = SecuritySupport.getSystemProperty("java.ext.dirs");
上节了解到,启动类加载器比较神秘,多是由C++来实现的,那我们就先从Luncher着手看看扩展类加载器和应用程序类加载器的结构。
1、首先查看Launcher的结构,内部类有AppClassLoader、ExtClassLoader ,并且这2个内部类都继承了URLClassLoader。下图看URLClassLoader的组织结构,最终继承了ClassLoader类。所以我们需要明确,类加载器之间并没有继承的父子关系,而是ClassLoader中维护了private final ClassLoader parent;字段,用这种方式来标记当前加载器的父加载器。后面在看到双亲委委派机制时,不能搞混了。
2、Luncher使用的ClassLoader ,是由getAppClassLoader()生成的,所以JVM的加载的类,首先都需要从App加载器开始加载。
Luncher的构造方法,生成 ExtClassLoader 、AppClassLoader。
private ClassLoader loader;
public ClassLoader getClassLoader() {
return this.loader;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//创建ExtClassLoader扩展类加载器对象
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//将扩展类加载器作为参数,创建AppClassLoader对象
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
}
2、AppClassLoader的getAppClassLoader方法,将ExtClassLoader对象 var0传递给超类ClassLoader,并标记ExtClassLoader 扩展类加载器是AppClassLoader应用程序加载器的父加载器。同时ExtClassLoader 在构造时,传入的父加载器参数是null,但实际其父类加载器是启动类加载器,C++实现的所以在java代码中只出现了BootClassPathHolder。
//var0是前面生成的ExtClassLoader对象
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
//创建AppClassLoader应用类加载器对象
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
private ClassLoader(Void unused, ClassLoader parent) {
//可见在AppClassLoader中,设置了parent为ExtClassLoader
this.parent = parent;
}
类加载的过程中,依次向上委托父类加载器进行加载。这就形成了双亲委派机制。结合loadClass(name)方法和下图看类的加载顺序,就不难理解了。
双亲委派机制简单的说,就是app加载器先向上交由父类加载器进行加载,父类中找不到,再由子类加载器自行加载。
为什么要设计成双亲委派机制?
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//第一步,在已加载的类中查找是否存在
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//第二步,通过父加载器去加载,如果当前是app加载器,则去ext扩展类加载器中查找,如果是扩展类加载器,则去查启动类加载器
if (parent != null) {
//扩展类加载器没有重写loadClass方法,会再次进入本方法
c = parent.loadClass(name, false);
} else {
//启动类加载器加载某个类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
//第三步,如果还是没有找到类,那就要去磁盘查找文件,将类加载进来
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
//具体实施查找的方法
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;
}
从磁盘中读取字节码文件 —> 转为二进制文件 —> 验证 —> 准备 —> 解析 —> 初始化
这其中多数核心代码,仍然是C++实现,Java代码中看不到太多有效代码,无奈略过。
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:
} 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);
}
}
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
//前期检查
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
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) {
}
}
}
}, acc);
}
return result;
}
自定义加载器的功能在1.5中实现。
public class MyClassLoader extends ClassLoader {
//需要加载的自定义加载路径
private String classPath;
//指定路径的构造方法
public MyClassLoader(String path){
this.classPath = path;
}
//修改路径的自定义加载器
private byte[] loadByte(String name) throws Exception {
//保持和原代码大体一致
String path = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + path + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException{
try {
byte[] data = loadByte(name);
//找到文件后,继续沿用原有的加载类的方法
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
@Override
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(name.startsWith("com.asky")){
c = findClass(name);
}else {
c = this.getParent().loadClass(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/study");
Class clazz = classLoader.loadClass("com.asky.test");
Object obj = clazz.newInstance();
System.out.println(clazz.getClassLoader());
}
}
参考文章:https://cloud.tencent.com/developer/article/1590312