JVM自定义类加载器加载指定classPath下的所有class及jar
2021-05-19 09:30
标签:控制台 imp 线程 bsp sep object master new not 从Java虚拟机的角度讲,只有两种不同的类加载器:启动类加载器和其他类加载器。 要实现自定义类加载器需要先继承ClassLoader,ClassLoader类是一个抽象类,负责加载classes的对象。自定义ClassLoader中至少需要了解其中的三个的方法: loadClass,findClass,defineClass。 loadClass:JVM在加载类的时候,都是通过ClassLoader的loadClass()方法来加载class的,loadClass使用双亲委派模式。如果要改变双亲委派模式,可以修改loadClass来改变class的加载方式。双亲委派模式这里就不赘述了。 贴上一段ClassLoader中loadClass源码,见见真面目... 源码说明... Invoke { Invoke the { Invoke the { If the class was found using the above steps, and the
* resolve flag is true, this method will then invoke the { Subclasses of ClassLoader are encouraged to override { Unless overridden, this method synchronizes on the result of
* { 翻译过来大概是:使用指定的二进制名称来加载类,这个方法的默认实现按照以下顺序查找类: 调用findLoadedClass(String)方法检查这个类是否被加载过 使用父加载器调用loadClass(String)方法,如果父加载器为Null,类加载器装载虚拟机内置的加载器调用findClass(String)方法装载类, 如果,按照以上的步骤成功的找到对应的类,并且该方法接收的resolve参数的值为true,那么就调用resolveClass(Class)方法来处理类。 ClassLoader的子类最好覆盖findClass(String)而不是这个方法。 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。 resolveClass:Class载入必须链接(link),链接指的是把单一的Class加入到有继承关系的类树中。这个方法给Classloader用来链接一个类,如果这个类已经被链接过了,那么这个方法只做一个简单的返回。否则,这个类将被按照 Java?规范中的Execution描述进行链接。 按照3.1的说明,继承ClassLoader后重写了findClass方法加载指定路径上的class。先贴上自定义类加载器。 以上就是自定义的类加载器了,实现的功能是加载指定路径的class。再看看如何使用。 首先通过构造方法创建MyClassLoader对象myClassLoader,指定加载src/test/resources/bean/Hello.class路径的Hello.class(当然这里只是个例子,直接指定一个class的路径了)。然后通过myClassLoader方法loadClass加载Hello的Class对象,最后实例化对象。以下是输出结果,看得出来实例化成功了,并且类加载器使用的是MyClassLoader。 JVM中class和Meta信息存放在PermGen space区域(JDK1.8之后存放在MateSpace中)。如果加载的class文件很多,那么可能导致元数据空间溢出。引起java.lang.OutOfMemory异常。对于有些Class我们可能只需要使用一次,就不再需要了,也可能我们修改了class文件,我们需要重新加载 newclass,那么oldclass就不再需要了。所以需要在JVM中卸载(unload)类Class。 很容易理解,就是要被卸载的类的ClassLoader实例已经被GC并且本身不存在任何相关的引用就可以被卸载了,也就是JVM清除了类在方法区内的二进制数据。 经过以上几个点的说明,现在可以实现JVM自定义类加载器加载指定classPath下的所有class及jar了。这里没有限制class和jar的位置,只要是classPath路径下的都会被加载进JVM,而一些web应用服务器加载是有限定的,比如tomcat加载的是每个应用classPath+“/classes”加载class,classPath+“/lib”加载jar。以下就是代码啦... 如何使用的代码就不贴了,和3.2节自定义类加载器的使用方式一样。只是构造方法的参数变成classPath了,篇末有代码。当创建MyClassLoader对象时,会自动添加指定classPath下面的所有class和jar里面的class到classMap中,classMap维护className和classCode字节码的关系,只是个缓冲作用,避免每次都从文件中读取。自定义类加载器每次loadClass都会首先在JVM中找是否已经加载className的类,如果不存在就会到classMap中取,如果取不到就是加载错误了。 至此,JVM自定义类加载器加载指定classPath下的所有class及jar已经完成了。这篇博文花了两天才写完,在写的过程中有意识地去了解了许多代码的细节,收获也很多。本来最近仅仅是想实现Quartz控制台页面任务添加支持动态class,结果不知不觉跑到类加载器的坑了,在此也趁这个机会总结一遍。当然以上内容并不能保证正确,所以希望大家看到错误能够指出,帮助我更正已有的认知,共同进步。。。 本文的代码已经上传github:https://github.com/chenerzhu/learning/tree/master/classloader 欢迎下载和指正。 参考文章: 深度分析Java的ClassLoader机制(源码级别) 自定义类加载器-从.class和.jar中读取 Class热替换与卸载 JVM自定义类加载器加载指定classPath下的所有class及jar 标签:控制台 imp 线程 bsp sep object master new not 原文地址:https://www.cnblogs.com/chenerzhu/p/9741883.html一、JVM中的类加载器类型
1.启动类加载器(Boostrap ClassLoader):这个是由c++实现的,主要负责JAVA_HOME/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作。
2.其他类加载器:由java实现,可以在方法区找到其Class对象。这里又细分为几个加载器
a).扩展类加载器(Extension ClassLoader):负责用于加载JAVA_HOME/lib/ext目录中的,或者被-Djava.ext.dirs系统变量指定所指定的路径中所有类库(jar),开发者可以直接使用扩展类加载器。java.ext.dirs系统变量所指定的路径的可以通过System.getProperty("java.ext.dirs")来查看。
b).应用程序类加载器(Application ClassLoader):负责java -classpath或-Djava.class.path所指的目录下的类与jar包装入工作。开发者可以直接使用这个类加载器。在没有指定自定义类加载器的情况下,这就是程序的默认加载器。
c).自定义类加载器(User ClassLoader):在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性。
这四个类加载器的层级关系,如下图所示。
二、为什么要自定义类加载器
三、自定义类加载器
3.1 ClassLoader实现自定义类加载器相关方法说明
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
protected final Class> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
findClass:ClassLoader通过findClass()方法来加载类。自定义类加载器实现这个方法来加载需要的类,比如指定路径下的文件,字节流等。
definedClass:definedClass在findClass中使用,通过调用传进去一个Class文件的字节数组,就可以方法区生成一个Class对象,也就是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) {
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;
}
}
/**
* Loads the class with the specified binary name. The
* default implementation of this method searches for classes in the
* following order:
*
*
*
*
@link #findLoadedClass(String)} to check if the class
* has already been loaded.
*
* 3.2 自定义类加载器实现
package com.chenerzhu.learning.classloader;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* @author chenerzhu
* @create 2018-10-04 10:47
**/
public class MyClassLoader extends ClassLoader {
private String path;
public MyClassLoader(String path) {
this.path = path;
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] result = getClass(name);
if (result == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, result, 0, result.length);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private byte[] getClass(String name) {
try {
return Files.readAllBytes(Paths.get(path));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.chenerzhu.learning.classloader;
import org.junit.Test;
/**
* Created by chenerzhu on 2018/10/4.
*/
public class MyClassLoaderTest {
@Test
public void testClassLoader() throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("src/test/resources/bean/Hello.class");
Class clazz = myClassLoader.loadClass("com.chenerzhu.learning.classloader.bean.Hello");
Object obj = clazz.newInstance();
System.out.println(obj);
System.out.println(obj.getClass().getClassLoader());
}
}
com.chenerzhu.learning.classloader.bean.Hello@2b2948e2
com.chenerzhu.learning.classloader.MyClassLoader@335eadca
四、类Class卸载
JVM中的Class只有满足以下三个条件,才能被GC回收,也就是该Class被卸载(unload):
JVM自带的类加载器所加载的类,在虚拟机的生命周期中,会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象。因此这些Class对象始终是可触及的,不会被卸载。而用户自定义的类加载器加载的类是可以被卸载的。虽然满足以上三个条件Class可以被卸载,但是GC的时机我们是不可控的,那么同样的我们对于Class的卸载也是不可控的。
五、JVM自定义类加载器加载指定classPath下的所有class及jar
package com.chenerzhu.learning.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author chenerzhu
* @create 2018-10-04 12:24
**/
public class ClassPathClassLoader extends ClassLoader{
private static Map
六、最后
上一篇:python--tkinter
下一篇:Java基础(运算符)
文章标题:JVM自定义类加载器加载指定classPath下的所有class及jar
文章链接:http://soscw.com/index.php/essay/87597.html