通过一个类的全限定名获取定义此类的二进制字节流
将这个字节流所代表的的静态存储结构转化为方法区的运行时数据区
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
补充:加载 class 文件的方式
1.验证(Verify):
2.准备(Prepare):
3.解析(Resolve):
()
的过程()
不同于类的构造器。(关联:构造器是虚拟机视角下的 ()
)()
执行前,父类的 clinit()
已经执行完毕clinit()
方法在多线程下被同步加载类加载器可以通过getParent获取父加载器,这并不是继承关系,如果直接继承ClassLoader自己实现一个类加载器,且不指定父加载器,他的父加载器就是AppClassLoader
任何parent为null的加载器,其父加载器为 BootstrapClassLoader
Java 虚拟机对 class 文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的 class 文件加载到内存生成 class 对象。而且加载某个类的 class 文件时,Java 虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。
优势
沙箱安全机制
自定义 String 类,但是在加载自定义 String 类的时候会率先使用引导类加载器加载,而引导类加载器在加载过程中会率先加载 JDK 自带的文件(rt.jar 包中 java/lang/String.class),报错信息说没有 main 方法,就是因为加载的是rt.jar 包中的 String 类。这样可以保证对 Java 核心源代码的保护,这就是沙箱安全机制。
在 JVM 中表示两个 class 对象是否为同一个类存在两个必要条件:
换句话说,在 JVM 中,即使这两个类对象(class 对象)来源于同一个 Class 文件,被同一个虚拟机所加载,但只要加载它们的 ClassLoader 实例对象不同,那么这两个类对象也是不相等的。
JVM 必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么 JVM 会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候,JVM 需要保证这两个类型的类加载器是相同的。
如果一个类依赖了其他的类,那么就需要先加载依赖的类。
类加载之后,就把它缓存起来,后续从缓存中获取
ClassLoader 类,它是一个抽象类,其后所有的类加载器都继承自 ClassLoader (不包括启动类加载器)
方法名称 | 描述 |
---|---|
getParent() | 返回该类加载器的超类加载器 |
loadClass(String name) | 加载名称为 name 的类,返回结果为 java.lang.Class 类的实例 |
findClass(String name) | 查找名称为 name 的类,返回结果为 java.lang.Class 类的实例 |
findLoadedClass(String name) | 查找名称为 name 的已经被加载过的类,返回结果为 java.lang.Class 类的实例 |
defineClass(String name, byte[] b, int off, int len) | 把字节数组 b 中的内存转换成为一个 Java 类,返回结果为 java.lang.Class 类的实例 |
resolveClass(Class c) | 连接指定的一个 Java 类 |
方式一:获取当前类的 ClassLoader
clazz.getClassLoader()
方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader()
方式三:获取系统的ClassLoader
ClassLoader.getSystemClassLoader()
方式四:获取调用者的 CLassLoader
DriverManager.getCallerClassLoader()
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
public class JvmClassLoaderPrintPath {
public static void main(String[] args) {
// 启动类加载器
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
System.out.println("启动类加载器");
for (URL url : urls) {
System.out.println(" ===> " + url.toExternalForm());
}
// 拓展类加载器
printClassLoader("拓展类加载器", JvmClassLoaderPrintPath.class.getClassLoader().getParent());
// 应用类加载器
printClassLoader("应用类加载器", JvmClassLoaderPrintPath.class.getClassLoader());
}
public static void printClassLoader(String name, ClassLoader classLoader) {
if (classLoader != null) {
System.out.println(name + " ClassLoader -> " + classLoader.toString());
printUrlForClassLoader(classLoader);
} else {
System.out.println(name + " ClassLoader -> null");
}
}
public static void printUrlForClassLoader(ClassLoader classLoader) {
Object ucp = insightField(classLoader, "ucp");
Object path = insightField(ucp, "path");
ArrayList ps = (ArrayList) path;
for (Object p : ps) {
System.out.println(" ===> " + p.toString());
}
}
private static Object insightField(Object obj, String fName) {
try {
Field f = null;
if (obj instanceof URLClassLoader) {
f = URLClassLoader.class.getDeclaredField(fName);
} else {
f = obj.getClass().getDeclaredField(fName);
}
f.setAccessible(true);
return f.get(obj);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
启动类加载器
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/resources.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/rt.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/sunrsasign.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/jsse.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/jce.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/charsets.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/jfr.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/classes
拓展类加载器 ClassLoader -> sun.misc.Launcher$ExtClassLoader@6d06d69c
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/sunec.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/nashorn.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/cldrdata.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/dnsns.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/localedata.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/sunjce_provider.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/sunpkcs11.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/jaccess.jar
===> file:/Users/yq/.sdkman/candidates/java/8.0.275.hs-adpt/jre/lib/ext/zipfs.jar
===> file:/System/Library/Java/Extensions/MRJToolkit.jar
应用类加载器 ClassLoader -> sun.misc.Launcher$AppClassLoader@659e0bfd
===> file:/Users/yq/code/wangyonghong/code-lab/gtu-java/out/production/gtu-java/
在 Java 的日常应用程序开发中,类的加载几乎是由上述 3 种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式
为什么要自定义类加载器
开发人员可以通过继承抽象类 java.lang.ClassLoader 类的方式,实现自己的类加载器,以满足一些特殊需求
在 JDK 1.2 之前,在自定义类加载器时,总会去继承 ClassLoader 类并重写 loadClass() 方法,从而实现自定义的类加载器类,但是在 JDK 1.2 之后已不再建议用户去覆盖 loadClass() 方法,而是建议把自定义的类加载逻辑写在 findClass() 方法中
在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承 URLClassLoader 类,这样就可以避免自己去编写 findClass() 方法以及获取字节码流的方式,使自定义类加载器编写更加简单
public class Hello {
public void hello() {
System.out.println("Hello, classLoader!");
}
}
通过以下方法拿到 base64
$ javac Hello.java
$ base64 Hello.class
yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVoZWxsbwEAClNvdXJjZUZpbGUBAApIZWxsby5qYXZhDAAHAAgHABYMABcAGAEAE0hlbGxvLCBjbGFzc0xvYWRlciEHABkMABoAGwEABUhlbGxvAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAUABgAAAAAAAgABAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAQAAQALAAgAAQAJAAAAJQACAAEAAAAJsgACEgO2AASxAAAAAQAKAAAACgACAAAABgAIAAcAAQAMAAAAAgAN
通过以下方法可以自定义ClassLoader
import java.util.Base64;
/**
* @author yonghongwang#163.com
* @since 2021/4/2
*/
public class HelloClassLoader extends ClassLoader {
public static void main(String[] args) {
try {
new HelloClassLoader().findClass("Hello").newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String helloBase64 = "yv66vgAAADQAHAoABgAOCQAPABAIABEKABIAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAAVoZWxsbwEAClNvdXJjZUZpbGUBAApIZWxsby5qYXZhDAAHAAgHABYMABcAGAEAE0hlbGxvLCBjbGFzc0xvYWRlciEHABkMABoAGwEABUhlbGxvAQAQamF2YS9sYW5nL09iamVjdAEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgAhAAUABgAAAAAAAgABAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAQAAQALAAgAAQAJAAAAJQACAAEAAAAJsgACEgO2AASxAAAAAQAKAAAACgACAAAABgAIAAcAAQAMAAAAAgAN";
byte[] bytes = decode(helloBase64);
return defineClass(name, bytes, 0, bytes.length);
}
private byte[] decode(String base64) {
return Base64.getDecoder().decode(base64);
}
}
Class.forName("xxx", new URLClassLoader("path"));
)自定义一个 Classloader,加载一个 Hello.xlass 文件,执行 hello 方法, 此文件内容是一个 Hello.class 文件所有字节(x=255-x)处理后的文件。
题解
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
* @author yonghongwang#163.com
*/
public class MyClassloader extends ClassLoader {
public static void main(String[] args) {
Class<?> helloClass = new MyClassloader().findClass("Hello");
Method helloMethod = null;
try {
helloMethod = helloClass.getMethod("hello");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
try {
helloMethod.invoke(helloClass.newInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
@Override
protected Class<?> findClass(String name) {
String path = this.getClass().getResource("Hello.xlass").getPath();
File file;
try {
file = new File(URLDecoder.decode(path, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("failed to find path: " + path);
}
byte[] bytes;
if (file.isFile() && file.exists()) {
try (FileChannel channel = new FileInputStream(file).getChannel()) {
ByteBuffer byteBuffer = ByteBuffer.allocate((int) channel.size());
channel.read(byteBuffer);
bytes = byteBuffer.array();
} catch (IOException e) {
throw new RuntimeException("failed to find path: " + path);
}
} else {
throw new RuntimeException("failed to find path: " + path);
}
return defineClass(name, decode(bytes), 0, bytes.length);
}
/**
* replace each byte with x->255-x
*/
private byte[] decode(byte[] bytes) {
for (int i = 0; i < bytes.length; i++) {
// bytes[i] = (byte) (255 - bytes[i]);
bytes[i] = (byte) ~bytes[i];
}
return bytes;
}
}