classLoader:类加载器
类加载器的作用:将class加载进jvm;审查类是由哪个类加载器加载的;将类字节码重新解析成JVM统一的对象格式
ClassLoader是个抽象类,一般实现自己的ClassLoader,会继承URLClassLoader
ClassLoader的等级加载机制:
类加载器 | 加载目录 | 继承自 |
---|---|---|
启动类加载器(BootstrapClassLoader) | %JRE_HOME%\lib | 本身是*加载器 |
扩展类加载器(ExtensionClassLoader) | %JRE_HOME%\lib\ext | URLClassLoader |
应用程序类加载器(ApplicationClassLoader) | CLASSPATH指定的jar或目录 | URLClassLoader |
自定义类加载器(MyClassLoader) | 编写人员指定目录 | ClassLoader |
注意:
BootstrapClassLoader 没有子类,不遵守ClassLoader的加载规则
ExtensionClassLoader 的父类也不是BootstrapClassLoader,且没有父类
ExtensionClassLoader 和ApplicationClassLoader 都继承了URLClassLoader ,URLClassLoader又继承了ClassLoader;ExtensionClassLoader 和ApplicationClassLoader 在Launcher对应,构造函数中首先创建ExtensionClassLoader ,然后以他为父加载器构建 ApplicationClassLoader
JVM加载class文件到内存的两种方式:
隐式加载:不用代码调用类加载器加载需要的类,而是通过jvm自动加载
显示加载:调用类加载器加载类
ClassLoader加载class文件到JVM时需要的步骤:
1.找到class文件,并把这个文件包含 的字节码加载进内存
URLClassLoader通过findClass具体实现:
通过URLClassPath获取要加载的class字节流,URLClassPath是在URLClassLoader的构造函数中创建,并指定搜索路径,找到class文件以后,读取他的byte字节流,通过调用define方法生成类对象。
ClassLoader类型 | 参数选项 | 说明 |
BootstrapClassLoader | -Xbootclasspath | 设置搜索路径 |
ExtensionClassLoader | -Djava.ext.dirs | 同上 |
ApplicationClassLoader | -Djava.class.path=或者-cp 或-classpath | 同上 |
2.字节码验证,准备,解析
3.类的静态属性和初始化赋值,静态代码块等
常见加载类错误分析:
显示加载类的方式:
类Class中的forName() 方法;
类ClassLoader中的loadClass()方法;
类ClassLoader中findSystemClass()方法
ClassNotFoundExpection:
jvm加载类到内存,未找到这个类,需要检查当前目录下有没有指定文件存在
NoClassDefFoundError:
可能是加载类时未使用完整包名+类名,导致未加载成功
UnsatisfiedLinkError:通常在解析native标识的方法时JVM找不到对应的本机库文件
ClassCastException:强制类型转换异常
常用类加载器分析:
tomcat中使用的类加载器是StandardClassLoader 和 WebappClassLoader
StandardClassLoader是在bootstrap类中创建,若未指定父类加载器,默认使用AppClassLoader,StandardClassLoader创建成功将会设置为Tomcat的根ClassLoader。
获取tomcat类中的类加载器不是StandardClassLoader?
StandardClassLoader未覆盖loadClass()方法,所以会由父类加载器AppClassLoader加载。
加载Servlet使用的类加载器是WebappClassLoader
实现自己的ClassLoader:
import java.io.*;
import java.lang.reflect.Method;
/***
* 获取当前类工程路径
*this.getClass().getResource("/").getPath()
* 获取当前类的绝对路径
*this.getClass().getResource("").getPath()
* 获取当前工程路径
*System.getProperty("user.dir")
*/
public class PathClassLoader extends ClassLoader{
private java.lang.String packageName = "com.yangjie.test";
private String classPath;
public PathClassLoader(String classPath){
this.classPath = classPath;
}
@Override
protected Class<?> findClass(java.lang.String name) throws ClassNotFoundException {
if(name.startsWith(packageName)){
byte[] classData = getData(name);
if(classData == null){
throw new ClassNotFoundException();
}else {
return defineClass(name,classData,0,classData.length);
}
}else {
return super.findClass(name);
}
}
private byte[] getData(java.lang.String name) {
//转义问题,最好将路径转成/,java和linux都可以识别
String path = classPath + name.replace('.', '/') + ".class";
InputStream in = null;
try {
in = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024*2];
int num = 0;
while((num =in.read(buffer))!=-1){
stream.write(buffer,0,num);
}
return stream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
///D:/ajavaDemo/lianxi/target/classes/
System.out.println( PathClassLoader.class.getResource("/").getPath());
///D:/ajavaDemo/lianxi/target/classes/
System.out.println( PathClassLoader.class.getResource("").getPath());
//D:\ajavaDemo\lianxi
System.out.println( System.getProperty("user.dir"));
PathClassLoader pathClassLoader = new PathClassLoader(PathClassLoader.class.getResource("").getPath());
try {
//Class<?> person = pathClassLoader.loadClass("Person");
Class<?> person = pathClassLoader.findClass("com.yangjie.test.Person");
System.out.println("字节码名称:"+person.getName());
System.out.println("类加载器名称:"+person.getClassLoader());
Method[] methods = person.getMethods();
for (Method method : methods) {
System.out.println("方法名称:"+method.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
ClassLoader主要方法:
findClass:可以自己定义类的加载规则,从而取得类的字节码
defineClass:根据字节码生成Class对象
resolveClass:类在什么时候被JVM链接
loadClass:运行时获取一个类对象,this.getClass().getClassLoader().loadClass("class")
实现类的热部署:
JVM表示一个类是否是同一个类两个条件:
1、两个类的完整类名是否一样,包括包名+类名
2、看类加载是不是同一个实例,即使是同一个类加载器,不是一个实例,加载的类也不一样,所以热部署就可以通过创建不同的ClassLoader实例对象,然后通过不同的实例对象加载同名的类。
package com;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class ClassReloader extends ClassLoader{
private String classpath;
public ClassReloader(String classpath){
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getData(name);
if(classData == null){
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0 ,classData.length);
}
}
private byte[] getData(String name) {
String path = classpath + name + ".class";
try {
InputStream in = new FileInputStream(path);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while((num=in.read(buffer))!=-1){
stream.write(buffer,0,num);
}
return stream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String path = "D:/ajavaDemo/lianxi/target/classes/";
try {
ClassReloader reloader1 = new ClassReloader(path);
Class<?> aClass = reloader1.findClass("Student");
ClassReloader reloader2 = new ClassReloader(path);
Class<?> bClass = reloader2.findClass("Student");
System.out.println(aClass.newInstance());
System.out.println(bClass.newInstance());
System.out.println(aClass.newInstance().equals(bClass.newInstance()));
}catch (Exception e){
e.printStackTrace();
}
}
}