类加载相关知识
1、类加载过程
类加载的过程是将.class文件加载到JVM内存当中,生成对应的class对象
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 (parent != null) {
c = parent.loadClass(name, false);
} else {
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();
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(c);
}
return c;
}
}
流程:
- 查看类有无被当前类加载器加载过,如果加载过,直接返回;
- 如果没有加载过,查看有无父类加载器,有父类加载器,则查看父类加载器有无加载过(调用parent.loadClass从而又进行同样的流程);无父类加载器,查看启动类加载器有无加载过;
- 如果父类加载器和启动类加载器都没有加载过,则调用findClass(通常由子类来实现)来加载类;
可以看出类加载的过程是一个递归的过程,也叫作双亲委派模式。
2、类加载器的分类
2.1、应用程序类加载器(AppClassLoader)
应用程序类加载器可通过下面的方式获取:
ClassLoader classLoader = Launcher.getLauncher().getClassLoader();
System.out.println(classLoader);
或者这种方式
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println(contextClassLoader);
在创建Launcher的时候会创建应用程序类加载器,如在Launcher的构造方法中的第九行this.loader = Launcher.AppClassLoader.getAppClassLoader(var1)
创建应用程序类加载器,并将其设置为上下文类加载器Thread.currentThread().setContextClassLoader(this.loader)
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
走进getAppClassLoader
方法,可以看到应用程序类加载器的创建过程,主要是获取加载路径,然后构建对象:
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);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
应用程序类加载器主要用来加载java.class.path
下的类,即我们自定义的类。
String property = System.getProperty("java.class.path");
StringTokenizer stringTokenizer = new StringTokenizer(property, ";");
while (stringTokenizer.hasMoreTokens()) {
String s = stringTokenizer.nextToken();
System.out.println(s);
}
2.2、拓展类加载器(ExtClassLoader)
在上面Launcher的构造方法中,我们还看到了var1 = Launcher.ExtClassLoader.getExtClassLoader()
,没错,这就是在创建拓展类加载器,下面我们看一下getExtClassLoader
方法:
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
final File[] var0 = getExtDirs();
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
MetaIndex.registerDirectory(var0[var2]);
}
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException)var2.getException();
}
}
也是获取加载路径,然后创建对象,只不过路径变为了getExtDirs
方法的返回值:
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
可以看到拓展类加载器主要加载java.ext.dirs
目录下的类。
另外,var1 = Launcher.ExtClassLoader.getExtClassLoader()
执行之后,var1被作为参数传递给了应用程序类加载器,追踪代码之后,可以看出拓展类加载被作为了应用程序类加载器的父加载器。
2.3、启动类加载器(BootstrapClassLoader)
启动类加载器由C++实现,它不继承ClassLoader类。
URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
for (URL url : bootstrapClassPath.getURLs()) {
System.out.println(url.getPath());
}
打印结果:
/D:/jdk8/jre/lib/resources.jar
/D:/jdk8/jre/lib/rt.jar
/D:/jdk8/jre/lib/sunrsasign.jar
/D:/jdk8/jre/lib/jsse.jar
/D:/jdk8/jre/lib/jce.jar
/D:/jdk8/jre/lib/charsets.jar
/D:/jdk8/jre/lib/jfr.jar
/D:/jdk8/jre/classes
可以看出启动类加载器主要加载$JAVA_HOME$/jre/lib
下面的类。比如String类位于rt.jar,获取String类的类加载器则为null,因为启动类加载器不是java实现的,所以为null。
ClassLoader classLoader = String.class.getClassLoader();
System.out.println(classLoader); //null
3、自定义类加载器
流程:
- 创建一个类继承自ClassLoader;
- 重写findClass;
- 读取字节码文件的字节流,调用defineClass,得到Class对象;
public class CustomClassLoader extends ClassLoader {
private final String filePath;
public CustomClassLoader(String filePath) {
this.filePath = filePath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;
try {
bis = new BufferedInputStream(new FileInputStream(filePath));
baos = new ByteArrayOutputStream();
byte[] arr = new byte[1024];
int len;
while ((len = bis.read(arr, 0, arr.length)) > 0) {
baos.write(arr, 0, len);
}
byte[] b = baos.toByteArray();
return defineClass(name, b, 0, b.length);
} catch (Exception e) {
throw new ClassNotFoundException();
} finally {
if (bis != null) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
CustomClassLoader customClassLoader = new CustomClassLoader("C://Users//86157//Desktop//com//oamha//test//Test.class");
Class<?> aClass = customClassLoader.loadClass("com.oamha.test.Test");
Class<?> aClass1 = customClassLoader.loadClass("com.oamha.test.Test");
System.out.println(aClass == aClass1); //true
CustomClassLoader customClassLoader1 = new CustomClassLoader("C://Users//86157//Desktop//com//oamha//test//Test.class");
Class<?> aClass2 = customClassLoader1.loadClass("com.oamha.test.Test");
System.out.println(aClass == aClass2); //false
}
}
不同类加载器加载同一字节码文件,得到的字节码对象是不同的,因为一个类是由其二进制名和其定义类加载器共同确定的。
本文来自博客园,作者:世间很大,转载请注明原文链接:https://www.cnblogs.com/oamha/p/15885255.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】