java ClassLoader机制
2019-10-27 本文已影响0人
spraysss
java
编译后的class
文件在需要使用时会由ClassLoader
加载到内存中,每一类的所有实例都只有一个唯一的Class
对象
关于Class
对象应该放在jvm的哪个内存空间中JVM规范并没有明确的规定,HotSpot虚拟机将其放在方法区中
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
System.out.println(a1.getClass() == a2.getClass());//true
}
static class A {
}
}
ClassLoader
- 每一个Class对象都有一个引用指向定义它的ClassLoader
- jvm类加载机制为父委托机制,即需要加载一个类时,会先看其父加载器是否可以加载该类,如果可以加载则由父加载器加载
JVM
自带3种ClassLoader
,用于加载class
:
-
Bootstrap ClassLoader
用于加载java
核心类库,比如java.lang.*
,它处于最上层,没有父加载器 -
Extension ClassLoader
的父加载器为Bootstrap ClassLoader
-
App ClassLoader
的父加载器为Extension ClassLoader
,用于加载我们自己写的类

类加载的过程
loading
使用ClassLoader加载类到JVM内存
linking
- verification 合法性验证
- preparation 为类的静态变量分配内存空间并将其初始化为默认值
- resolution 把类中的符号引用转为直接引用
initialization
为类的静态变量赋正确的初始值
主动使用VS被动使用
JVM只有在首次主动使用类或者接口时才会去初始化该类或接口的Class对象
被动使用只会做加载动作,不会做初始化动作
jvm参数 -XX:+TraceClassLoading
可以查看类加载详细信息
几个典型被动使用的例子
- 子类调用父类的静态字段
- 调用final 修饰的静态字段(这些字段会存放在调用类的常量池中)
- new 类的数组
查看Bootstrap ClassLoader 加载的内容
public class Test {
public static void main(String[] args) {
Arrays.stream(System.getProperty("sun.boot.class.path").split(":")).forEach(System.out::println);
}
}
output
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/classes
查看Extension ClassLoader 加载的内容
public class Test {
public static void main(String[] args) {
Arrays.stream(System.getProperty("java.ext.dirs").split(":")).forEach(System.out::println);
}
}
输出
/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
App Classloader
public class Test {
public static void main(String[] args) {
System.out.println(new A().getClass().getClassLoader());
//sun.misc.Launcher$AppClassLoader@18b4aac2
}
static class A{
}
}
输出
sun.misc.Launcher$AppClassLoader@18b4aac2
自定义类加载器
自定义类加载器需要继承抽象类ClassLoader
并重写findClass
方法
-
findClass
用于将指定路径下的class文件转化为Class对象,内部调用defineClass
-
loadClass
ClassLoader用于加载类的外部接口
具体步骤:
- 首先创建存放class文件的目录
mkdir -p /tmp/test/com/yz/clz
cd /tmp/test/com/yz/clz
- 编写A类
package com.yz.clz;
public class A {
public void helloWorld() {
System.out.println("Hello World");
}
}
- javac 编译生产class文件
javac A.java
- ls 可以看的A.java生成的A.class文件
A.class A.java
- 自定义ClassLoader加载A.class
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
public class MyClassLoader extends ClassLoader {
private String dir;
public MyClassLoader(ClassLoader parent, String dir) {
super(parent);
this.dir = dir;
}
public MyClassLoader(String dir) {
this.dir = dir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String cp = name.replace(".", "/");
File classFile = new File(dir, cp + ".class");
if (!classFile.exists()) {
throw new ClassNotFoundException("class " + name + "not find");
}
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(classFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
byte[] classBytes = baos.toByteArray();
return defineClass(name, classBytes, 0, classBytes.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws Exception {
ClassLoader classLoader = new MyClassLoader("/tmp/test");
Class<?> aClz = classLoader.loadClass("com.yz.clz.A");
System.out.println(aClz.getClassLoader());
Method method = aClz.getMethod("helloWorld");
Object obj = aClz.newInstance();
method.invoke(obj);
}
}
- 测试输出如下结果
com.yz.clz.MyClassLoader@1376c05c
Hello World