前言
上一篇我们了解了字节码的结构,这些字节码文件都是什么时候被加载到内存中的,今天就来聊聊类加载。
类的加载时机
众所周知,电脑的内存是有限的,在使用的过程有些类没有被使用就不应该把它加载到内存中,加载了不用的类,会导致可用的内存越来越少。
Java
中会在下面这两种情况下把 .class
文件加载到内存中
- 调用类的构造方法
- 调用类的静态变量或静态方法
Java 类加载器
在 Java
中有 AppClassLoader
、 ExtClassLoader
、 BootstrapClassLoader
,我们依次看下
AppClassLoader
1
2
3
4
5
6
7
8
9
10
11
12
13
|
static class AppClassLoader extends URLClassLoader {
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);
}
});
}
}
|
从上面的代码中可以发现, AppClassLoader
会加载 java.class.path
路径下的文件。也就是 CLASS_PATH
环境变量的值。
我们自己写的代码以及第三方库都是由 AppClassLoader
加载的。
通过如下代码查看 java.class.path
的具体路径
1
|
System.out.println(System.getProperty("java.class.path"));
|
最后只会输出一个 .
也就是当前文件路径。
ExtClassLoader
ExtClassLoader
在 1.9 之后改名为 PlatformClassLoader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
static class ExtClassLoader extends URLClassLoader {
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();
}
}
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;
}
}
|
ExtClassLoader
和 AppClassLoader
类似,不同的地方是加载的路径不一样。 ExtClassLoader
加载的是 java.ext.dirs
环境变量的值。
同样我们原来查看一下 java.ext.dirs
的路径
1
|
System.out.println(System.getProperty("java.ext.dirs"));
|
输出如下内容
1
2
3
4
5
6
|
/Users/test/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
|
BootstrapClassLoader
BootstrapClassLoader
和 ExtClassLoader
、 AppClassLoader
不一样,它是 C/C++
编写的,是虚拟机的一部分。
BootstrapClassLoader
加载的是 sun.boot.class.path
下的文件。
同样我们原来查看一下 sun.boot.class.path
的路径
1
|
System.out.println(System.getProperty("sun.boot.class.path"));
|
输出如下内容
1
2
3
4
5
6
7
8
|
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre/classes
|
双亲委派
经过上一小节的学习,我们知道在 Java
中有三种类加载器,这三个类加载器加载的都是不同路径下的。那么在具体加载类的时候是怎么决定用哪个类加载器加载的呢?
Java
中通过 双亲委派机制 来加载类的。如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
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.
c = findClass(name);
}
}
return c;
}
|
从 loadClass
中可以看出,先判断该类是否已经被加载了,如果加载了就直接返回。
如果该类没有加载,就会看看有没有 parent
,如果有就委托给 parent
,最后 parent
中没有找到该类,则调用自己的 findClass
去加载。
如果 parent
为 null
则会委托给 BootstrapClassLoader
去加载,如果在 BootstrapClassLoader
中没有加载成功,则也是调用自己的 findClass
去加载。
AppClassLoader 中的 parent
我们来看看 AppClassLoader
中的 parent
是怎么来的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
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);
}
// 省略代码
}
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);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
// URLClassLoader.java
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
// 省略代码
}
// SecureClassLoader.java
protected SecureClassLoader(ClassLoader parent) {
super(parent);
// 省略代码
}
// ClassLoader.java
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
// 省略代码
}
|
一路跟踪发现 parent
是 ClassLoader
中的一个成员变量,是在构造方法中传入的,在 AppClassLoader
的构造方法中传入一个 ClassLoader
,经过层层传递最后赋值给了 parent
。
这个 parent
的来源是在 getAppClassLoader
传进去的,而这个 ClassLoader
就是 Launcher.ExtClassLoader.getExtClassLoader();
也就是 AppClassLoader
中的 parent
就是 ExtClassLoader
。
ExtClassLoader 中的 parent
1
2
3
4
|
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
|
ExtClassLoader
中的 parent
赋值为 null
了。
这也就解释了为什么为什么叫 双亲委派 了。也就是 AppClassLoader
会先委派给 ExtClassLoader
,而 ExtClassLoader
会委派给 BootstrapClassLoader
,如果委托方都没能成功加载,那就自己去加载。
Android 中的 ClassLoader
Android
中的 ClassLoader
和 JVM
类似,也是双亲委派机制。但是还是有些不同的,比如: JVM
加载的是 .class
文件,而 Android
中的 .class
文件会通过 dx
工具转换成 dex
。所以 Android
中加载的是 .dex
文件。
Android
中用来加载的 ClassLoader
是 PathClassLoader
和 DexClassLoader
。
PathClassLoader
PathClassLoader
一般用来加载系统 APK
和已经被安装到手机上的 APK
中的 dex
文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath,ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
@libcore.api.CorePlatformApi
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders) {
super(dexPath, librarySearchPath, parent, sharedLibraryLoaders);
}
}
|
其中 dexPath
就是加载的路径,可以包含多个,使用 :
分隔。 librarySearchPath
是 C/C++ native 库的路径
DexClassLoader
DexClassLoader
可以加载特定路径下的 dex
文件,这就给热修复创造了条件。
1
2
3
4
5
|
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
|
dexPath
和 PathClassLoader
中一样,也是加载路径,可以包含多个,用 :
分隔。
optimizedDirectory
用来缓存优化后的 dex
路径,这个从 API 26 就被废弃了。
总结
Java
中有三种类加载器,分别是 BootstrapClassLoader
、 ExtClassLoader
、 AppClassLoader
。他们分别用来加载 sun.boot.class.path
、 java.ext.dirs
、 java.class.path
下的文件。
Android
中有两种类加载器,分别是 PathClassLoader
和 DexClassLoader
。 PathClassLoader
用来加载已经安装到手机上的 APK
。 DexClassLoader
可以加载任意位置的 dex
文件,是热修复的基础。
类加载器通过 双亲委派 机制来决定使用哪个类加载器。
参考