Jvm专讲之类加载全过程探索
类加载全过程探索
作为一个Java程序员,我们一定很好奇编译好的class文件是如何加载到内存中运行起来的呢?那么本篇就围绕这个主线来探讨一下Jvm加载类过程以及类加载器的原理吧!
一 类加载过程初探
1.我们先声明一个Math类
public class Math {
public static final int initData = 10;
public static User user = new User();
private int compute(){
int a = 1;
int b = 2;
int c = (a + b) *10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
2.我们的math类是如何加载到jvm内存中然后再执行main方法呢?它需要经历以下的过程
这里以windows系统为例
1.首先会通过java.exe调用底层的jvm.dll创建Java虚拟机
2.创建一个引导类加载器实例(C++实现的,也就是bootstapClassLoader)
3.创建JVM启动器实例Launcher类
4.创建类加载器实例
5.调用类加载的loadClass方法加载我们的类
6.Jvm找到Main函数入口进行调用
其实我们最关心的就是loadClass这个类加载的过程,它主要有以下步骤:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
- 加载:在硬盘上查找并通过IO读入字节码文件,使用到类时才会加载,例如调用类的main()方法,new对象等等,在加载阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区访问这个类的各种数据的入口
- 验证:校验字节码文件的正确性
- 准备:给类的静态变量分配内存,并赋予默认值
- 解析:将符号引用替换为直接引用,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所在内存的指针或句柄等(直接引用),这是所谓的静态链接过程(类加载期间完成),动态链接是在程序运行期间完成的将符号引用替换为直接引用。
什么是静态链接和动态链接?
当一个字节码文件被装载进 JVM 内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接
如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接
- 初始化:对类的静态变量初始化为指定的值,执行静态代码块
类被加载到方法区中后主要包含 运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。
类加载器的引用:这个类到类加载器实例的引用
对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。(这里记住类信息存储在方法区,类实例存储在堆中)
注意: 主类在运行过程中如果使用到其它类,会逐步加载这些类。jar包或war包里的类不是一次性全部加载的,是使用到时才加载。
我们可以做一个小测试:
/**
* 测试动态加载
*/
public class TestDynamicLoad {
static {
System.out.println("*************load TestDynamicLoad************");
}
public static void main(String[] args) {
A a = new A();
System.out.println("*************load test************");
B b = null; //B不会加载,除非这里执行 new B()
}
}
class A{
static {
System.out.println("*************load A************");
}
public A(){
System.out.println("*************initial A************");
}
}
class B{
static {
System.out.println("*************load B************");
}
public B(){
System.out.println("*************initial B************");
}
}
运行结果:
这一地对象b为null,所以不会调用B的静态代码块和构造方法
****** ****** *load TestDynamicLoad ****** ****** ****** ****** *load A ****** ****** ****** ****** *initial A ****** ****** ****** ****** *load test ****** ******
当我们把B b = null 改为 B b = new B()时,运行结果:
****** ****** *load TestDynamicLoad ****** ****** ****** ****** *load A ****** ****** ****** ****** *initial A ****** ****** ****** ****** *load test ****** ****** ****** ****** *load B ****** ****** ****** ****** *initial B ****** ******
进一步说明我们的静态代码块是要比我们实例化对象之前加载,而且只会随着类的加载而加载。
二 类加载器详解
2.1 类加载类型
我们知道,类是由类加载器负责将class文件加载到我们的Jvm虚拟机中的,但是java中有哪几种类加载器呢?每个类加载分别会加载哪些类呢?接下来我们继续探讨一下java中的类加载器。
首先Java中主要有以下类加载器:
- 引导类加载器:负责加载支撑JVM运行的位于Jre的lib目录下的核心类库,比如 rt.jar、charsets.jar等
- 扩展类加载器:负责加载支撑JVM运行的位于Jre的lib目录下的ext扩展目录中的Jar类包
- 应用程序类加载器:负责加载ClassPath路径下的类包,主要就是加载我们自己写的那些类
- 自定义加载器:负责加载用户自定义路径下的类
我们可以通过一下代码,进一步查看Java中的类加载器
/**
* 测试Jdk类加载器
*/
public class TestJdkClassLoader {
public static void main(String[] args) {
/*String 位于jre的lib下*/
System.out.println(String.class.getClassLoader());
/*DESKeyFactory 位于jre的lib下的ext目录*/
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
/*classPath路径下*/
System.out.println(TestJdkClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
//获取应用程序类加载器
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
//扩展类加载器
ClassLoader extClassloader = appClassLoader.getParent();
//获取引来类加载器
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
运行结果:
null sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null the extClassloader : sun.misc.Launcher$ExtClassLoader@4b67cf4d
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrapLoader加载以下文件: file:/D:/environment/jdk1.8/jre/lib/resources.jar file:/D:/environment/jdk1.8/jre/lib/rt.jar file:/D:/environment/jdk1.8/jre/lib/sunrsasign.jar file:/D:/environment/jdk1.8/jre/lib/jsse.jar file:/D:/environment/jdk1.8/jre/lib/jce.jar file:/D:/environment/jdk1.8/jre/lib/charsets.jar file:/D:/environment/jdk1.8/jre/lib/jfr.jar file:/D:/environment/jdk1.8/jre/classes
extClassloader加载以下文件: D:\environment\jdk1.8\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
appClassLoader加载以下文件: D:\environment\jdk1.8\jre\lib\charsets.jar;D:\environment\jdk1.8\jre\lib\deploy.jar;D:\environment\jdk1.8\jre\lib\ext\access-bridge-64.jar;D:\environment\jdk1.8\jre\lib\ext\cldrdata.jar;D:\environment\jdk1.8\jre\lib\ext\dnsns.jar;D:\environment\jdk1.8\jre\lib\ext\jaccess.jar;D:\environment\jdk1.8\jre\lib\ext\jfxrt.jar;D:\environment\jdk1.8\jre\lib\ext\localedata.jar;D:\environment\jdk1.8\jre\lib\ext\nashorn.jar;D:\environment\jdk1.8\jre\lib\ext\sunec.jar;D:\environment\jdk1.8\jre\lib\ext\sunjce_provider.jar;D:\environment\jdk1.8\jre\lib\ext\sunmscapi.jar;D:\environment\jdk1.8\jre\lib\ext\sunpkcs11.jar;D:\environment\jdk1.8\jre\lib\ext\zipfs.jar;D:\environment\jdk1.8\jre\lib\javaws.jar;D:\environment\jdk1.8\jre\lib\jce.jar;D:\environment\jdk1.8\jre\lib\jfr.jar;D:\environment\jdk1.8\jre\lib\jfxswt.jar;D:\environment\jdk1.8\jre\lib\jsse.jar;D:\environment\jdk1.8\jre\lib\management-agent.jar;D:\environment\jdk1.8\jre\lib\plugin.jar;D:\environment\jdk1.8\jre\lib\resources.jar;D:\environment\jdk1.8\jre\lib\rt.jar;D:\devTools\idea\workspace\jvm_study\target\classes;D:\devTools\idea\IntelliJ IDEA 2019.2.3\lib\idea_rt.jar
注意:
1.bootstrapLoader是由c++语言实现的,所以会打印为null
2.虽然应用程序类加载器打印了jre/lib下的核心类库,但是它其实只加载/target/class目录下的class类
2.2类加载器的创建过程
当我们Java程序运行的时候,会创建一个引导类加载器
,再由这个引导类加载器
创建JVM启动器实例sun.misc.Launcher,在Launcher构造方法内部,其创建了两个类加载器,分别是sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应用程序类加载器)。JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们的应用程序。
源码剖析:
我们查看Launcher类,在声明静态变量的时候创建Launcher实例,在构造方法中创建扩展类加载器和应用类加载器
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
//静态new出来
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
public static Launcher getLauncher() {
return launcher;
}
//构造方法中创建类加载器
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//创建扩展类加载器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//创建应用类加载器,注意这里把应用类加载赋值给loader属性,并将扩展类加载器作为参数
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);
}
}
}
...
在Launcher.ExtClassLoader.getExtClassLoader()中创建扩展类加载器,这里会调用到顶层ClassLoader类的构造方法,只不过这里扩展类加载器传的parent为null
public ExtClassLoader(File[] var1) throws IOException {
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
在Launcher.AppClassLoader.getAppClassLoader(var1)中创建应用类加载器,这里会把父类加载器ExtClassLoader作为参数传入进来,注意,这里的两个类加载器不是类上的继承关系,只是AppClassLoader的parent属性指向了ExtClassLoader实例
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
父类构造方法
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
acc = AccessController.getContext();
ucp = new URLClassPath(urls, factory, acc);
}
父类的父类的构造方法
protected SecureClassLoader(ClassLoader parent) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
initialized = true;
}
父类的父类的父类的构造方法,为parent属性赋值
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}
2.3 双亲委派机制
了解了类加载器之间的关系,我们接下来可以了解下Java的双亲委派机制是怎么回事?
先解释一下什么是双亲委派机制: 加载某个类时,如果自己加载过的类中没有找到,会先委托父加载器寻找目标类,如果还是找不到则继续再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
比如我们的Math类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托引导类加载器,顶层引导类加载器在自己的类加载路径里如果没找到Math类,则向下退回加载Math类的请求,扩展类加载器收到回复就自己尝试加载,在自己的类加载路径里也没找到对应的Math类,则又向下退回Math类的加载请求给应用程序类加载器,应用程序类加载器在自己的类加载路径里找Math类,找到后自己加载Math类。
思考一下,这里从下往上,再从上往下走了一圈,何不直接从引导类加载器开始加载呢?
那是因为我们很多的业务都是自己写的类实现的,这些类基本都是通过应用程序类加载器加载的,如果我们这些类多次使用,我们都要从引导类加载器来判断,那无疑对于我们的性能损耗会更大,而我们从应该程序类加载器开始加载,当加载我们自己写的类的时候,第一次走过一遍双亲委派机制后,还是由我们的应用程序类加载器进行加载,当下次再次使用这个类的时候,会先判断当前类加载器是否加载过这个类,如果加载过直接就返回了,这样性能损耗更低一点,
源码分析:
- 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
- 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);)或者是调用bootstrap类加载器来加载。
- 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
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) {
//parent属性不为空则调用父加载器加载类
c = parent.loadClass(name, false);
} else {
//如果parent为空,则调用引导类加载器加载
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();
//调用findClass方法加载指定名称的类
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;
}
}
思考一下双亲委派机制的好处有哪些?
- 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
- 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
这里做一个demo,我们自定义一个和String类相同路径相同名称的类,看看结果会怎么样
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("******自定义String的Main方法");
}
}
运行结果:
错误: 在类 java.lang.String 中找不到 main 方法, 请将 main 方法定义为: public static void main(String[] args) 否则 JavaFX 应用程序类必须扩展javafx.application.Application
这是因为双亲委派机制的存在,当我们要加载java.lang.String的时候,应用程序类加载会向上委托,而我们的jre的lib下也有一个相同类路径的String类,此时会返回这个String类信息,但是这个String类是没有main方法的,就会出现以上错误。
2.4 全盘委托机制
“全盘委托”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类所依赖及引用的类也由这个ClassLoder载入。当然这里的类是类路径下的类.
2.5 自定义类加载器
自定义类加载器只需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
1.创建People类
2.复制一份放在D盘的/test/com/lx目录下
/**
* 自定义类加载器测试
* @author wcy
*/
public class MyClassLoaderTest {
//自定义类加载器
static class MyClassLoader extends ClassLoader{
private String classPath;
//加载路径
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
//D盘创建 test/com/lv 目录,将People.class丢入该目录
Class clazz = classLoader.loadClass("com.lx.People");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("printf", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
运行结果:
自定义类加载器加载类成功 sun.misc.Launcher$AppClassLoader
对于结果打印的是类加载器是AppClassLoader,因为我们的项目中也有People这个类,而自定义类加载器的parent属性是应用程序类加载,这里是通过ClassLoader的构造方法实现的。
当我们把People这个类从工程中删除掉重新执行代码,运行结果如下:
自定义类加载器加载类成功 com.lx.MyClassLoaderTest$MyClassLoader
2.6 打破双亲委派机制
可以回想,我们双亲委派机制的实现机制代码是在ClassLoader的loadClass方法中,那我们可以重写这个方法来改变双亲委派的逻辑
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;
}
}
现在我们对代码进行改造,我们把People类还原到项目中去,但是仍然让自定义类加载器加载我们D盘的People类
/**
* 重写类加载方法,实现自己的加载逻辑,不委派给双亲加载
* @param name
* @param resolve
* @return
* @throws 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) {
// 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.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
运行结果:
java.io.FileNotFoundException: D:\test\java\lang\Object.class (系统找不到指定的路径。) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at java.io.FileInputStream.(FileInputStream.java:93) at com.lx.MyClassLoaderTestMyClassLoader.findClass(MyClassLoaderTest.java:30) at com.lx.MyClassLoaderTestMyClassLoader.findClass(MyClassLoaderTest.java:32) at com.lx.MyClassLoaderTestMyClassLoader.findClass(MyClassLoaderTest.java:32) at com.lx.MyClassLoaderTestMyClassLoader.findClass(MyClassLoaderTest.java:35) at com.lx.MyClassLoaderTest$MyClassLoader.loadClass(MyClassLoaderTest.java:58) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 7 more
很显然,提示的找不到Object类,这是因为我们所有的类都继承于Object类,而我们自定义类加载器加载People类的时候找不到Object类,所以就会出现这个错误,这里我们可以怎么解决呢?有两个思路:
1.思路一:我们也自定义一个Object类,放在D盘的test/java/lang包下
再看运行结果:
java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662) at java.lang.ClassLoader.defineClass(ClassLoader.java:761) at java.lang.ClassLoader.defineClass(ClassLoader.java:642) at com.lx.MyClassLoaderTestMyClassLoader.loadClass(MyClassLoaderTest.java:58) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.lang.ClassLoader.defineClass(ClassLoader.java:642) at com.lx.MyClassLoaderTestMyClassLoader.loadClass(MyClassLoaderTest.java:58) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at com.lx.MyClassLoaderTest.main(MyClassLoaderTest.java:82) Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) at java.lang.ClassLoader.defineClass(ClassLoader.java:642) at com.lx.MyClassLoaderTestMyClassLoader.loadClass(MyClassLoaderTest.java:58) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at com.lx.MyClassLoaderTest.main(MyClassLoaderTest.java:82) Caused by: java.lang.ClassNotFoundException at com.lx.MyClassLoaderTestMyClassLoader.loadClass(MyClassLoaderTest.java:58) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 7 more
这个错误是因为Java的沙箱机制,不会加载我们篡改jdk的核心类库的类的!
2.思路2:改变类加载的逻辑,自己写的类打破双亲委派机制,而其他的类依然使用双亲委派机制,我们对代码做一下改动
@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) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//自定义包下的类由自定义类加载器加载
if(name.startsWith("com.lx")){
c = findClass(name);
}else {
c = this.getParent().loadClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
运行结果:
自定义类加载器加载类成功 com.lx.MyClassLoaderTest$MyClassLoader
转载自:https://juejin.cn/post/7252252174998241341