本文是笔者在学习Java 类加载的过程中,整理的心得体会,共勉!
类加载的意义
简单概念:
将Java类的.class文件中的二进制数据读入到内存中,放置在运行时数据区的方法区内。
什么时候会进行类加载?
本地系统加载;
代理加载,比如Spring的AOP动态代理;
从jar包中加载;
等等等等
ClassLoader类结构介绍
ClassLoader是一个抽象类,核心方法如下:
defineClass():目的是将byte字节流解析成JVM能够识别的Class对象。
findClass():此方法支持重载,与defineClass()配合使用,目的是获取Class对象的字节码。这个方法的意义在于,我们不仅仅可以通过class文件实例化对象,也可以通过其他方式,比如从网络上获取的字节码文件,可能会有对应的加密规则。
loadClass():此方法支持重载,目的是获取加载类的类对象。
resolveClass():实现让JVM链接这个类,此方法调用的是本地方法,不能重载。
Class类的生命周期
此生命周期对应到ClassLoader类的具体方法:
加载:loadClass()、findClass()、defineClass()
连接:包括验证、准备、解析:resolveClass() 本地native方法
初始化:JVM负责
类加载机制(双亲委托)
概念介绍:
大致的ClassLoader分类:
- Bootstrap ClassLoader:负责加载存放在 JDKjrelib(JDK代表JDK的安装目录,下同)下,或被 -Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库
- ExtClassLoader:负责加载 JDKjrelibext目录中,或者由 java.ext.dirs系统变量指定的路径中的所有类库(如javax.开头的类),开发者可以直接使用扩展类加载器
- AppClassLoader:负责加载用户类路径(ClassPath)所指定的类,其父类是ExtClassLoader
心得体会:
这里的父加载器与子加载器 在java类关系上并不是继承关系;
实现自己的类加载器,不管是直接实现抽象类ClassLoader,还是继承其他子类,父加载器都是AppClassLoader;
类加载的方式
1、命令行启动应用时候由JVM初始化加载
2、通过Class.forName()方法动态加载
3、通过ClassLoader.loadClass()方法动态加载
Class.forName()和ClassLoader.loadClass()区别
Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
Class.forName(name,initialize,loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象
应用实践
实现自定义的ClassLoader
基于上面对于类加载器的分析,自定义ClassLoader的情况如下:
- 需要在自定义路径下查找自定义的class文件
- 对我们想要加载的类做特殊处理,比如通过网络传输的类的字节码可能进行了加密,获取这种类的字节码需要在我们自定义的ClassLoader中实现
- 实现类的热部署
从上面的分析我们还可以得到,自定义ClassLoader的实现思路可以通过继承ClassLoader类,重写findClass()来实现,当然你也可以重写loadClass(),但是默认的loadClass()是实现类加载双亲委托模式的,除非你想要破坏这种双亲委托模式,不然就别重写了。
下面是我在本机写的测试自定义ClassLoader的Demo:
/** * 自定义路径下的class文件加载 * @Auther: trey_stao@163.com * @Date: 2018/5/31 19:34 * @Description: */ public class PathClassLoader extends ClassLoader { private String classPath; public PathClassLoader(String classPath) { this.classPath = classPath; } private String packageName = "com.trey.classloader.Test"; protected Class<?> findClass(String name) throws ClassNotFoundException { if(packageName.startsWith(name)) { byte[] classData = getData(name); if(classData == null) { throw new ClassNotFoundException(); } else { System.out.println("当前类加载器:"+this.getClass().getClassLoader().toString()); return defineClass(name, classData, 0, classData.length); } } else { return super.loadClass(name); } } private byte[] getData(String name) { String path = classPath + File.separatorChar + name.replace('.',File.separatorChar) + ".class"; try { InputStream is = new FileInputStream(path); ByteArrayOutputStream stream = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int num = 0; while ((num = is.read(buffer)) != -1) { stream.write(buffer, 0, num); } return stream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return null; } }
实现类的热部署
这是给自己布置的对类加载学习的总结练习。下面说下自己准备实现的思路:
- 实现一个server服务,监听具体目录下的所有class文件的更新情况
- 当出现class文件更新时,使用自定义的classLoader进行加载
参考文献
整理这篇学习心得时,参考了一些文章,特在此注明,表示感谢!
周志明:《深入理解Java虚拟机:JVM高级特性与最佳实践》
纯洁的微笑:java类的加载机制
许令波:《深入分析Java Web 技术内幕》