1). 字节码和.class文件区别
新建Java对象时,JVM将这个对象对应的字节码加载到内存中,这个字节码的原始信息存放在classpath目录下(及Java工程的bin目录下)的.class文件中,类加载需要将.class文件导入硬盘,经过处理变成字节码加载到内存。
示例:
public class ClassLoaderTest {
public static void main(String[] args) {
// 输出ClassLoaderTest的类加载器名称
System.out.println("ClassLoaderTest类的类加载器名称:" + ClassLoaderTest.class.getClassLoader().getClass().getName());
System.out.println("System类的类加载器名称:" + System.class.getClassLoader());
System.out.println("List类的类加载器名称:" + List.class.getClassLoader());
// 获取ClassLoaderTest类的类加载器
ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
while (null != classLoader) {
System.out.println(classLoader.getClass().getName() + "->");
// 获取类加载器的父类
classLoader = classLoader.getParent();
}
System.out.println(classLoader);
}
}
打印结果:
文章图片
图1.png
从打印结果可以看出ClassLoaderTest类是有AppClassLoader加载的。
因为System类,List,Map等这样的系统提供jar类都在rt.jar中,所以由BootStrap类加载器加载,因为BootStrap是祖先类,不是Java编写的,所以打印出class为null。 详情请看第三点:类加载器的委托机制
2). Java虚拟中的类加载器 Java虚拟机中可以安装多个类加载器,系统默认的有三个,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader
由于类加载器本身是Java类,也需要被类加载器加载,因此必须有一个为类加载器不是Java类,因此Bootstrap是由C/C++代码编写,被封装到JVM内核中,ExtClassLoader和AppClassLoader是Java类。
类加载器属性结构图:
文章图片
图2.png
3). 类加载器的委托机制 问题:Java虚拟机要加载第一个类的时候,使用哪一个类加载器加载?
(1). 当前线程的类加载器加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader); 方法,可以获取/指定本线程中的类加载器)
(2). 如果类A引用了类B,Java虚拟机将使用加载类A的加载器来加载类B
(3). 可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类
每个类加载器加载类时,先委托其上级类加载器来加载,当所有父加载器没有加载到类时,回到发起者类加载器,如果还是加载不了,则会抛出ClassNotFoundException。
好处:
能够很好的统一管理类加载,首先交给上级,如果上级有,就加载,这样如果之前已经加载过的类,这时候在来加载它的时候只要拿过来用就可以了,无需二次加载
4). 测试ExtClassLoader
- 将ClassLoaderTest类打包为Jar文件,拷贝至
jdk\jre\lib\ext\下
路径
文章图片
图3.png - 接下来在eclipse中运行ClassLoaderTest文件,其打印结果为
文章图片
图4.png
打印结果验证了类加载器的委托机制.
测试完成后记得将test.jar删除
- 默认的类加载器
System.out.println("默认的类加载器:"+ClassLoader.getSystemClassLoader());
文章图片
图5.png
- loadClass(String name) 源码
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
//加锁,进行同步处理,可能多个线程加载类
synchronized (getClassLoadingLock(name)) {
// 检查是否已经加载过
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果自定义的类加载器的parent不为null,就调用parent的loadClass进行类加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果自定义的类加载器的parent为null,就调用findBootstrapClassOrNull方法查找类,就是BootStrap类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 类未发现异常
}if (c == null) {
// 如果仍然没有发现,就调用自己的findClass方法进行进行类加载
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;
}
}
- defineClass,将class文件的字节数组编译成一个class对象,不能重写,内部实现为C/C++代码
文章图片
图6.png
- 定义一个被加载的类
/**
* 加载类
* @author mazaiting
*/
public class ClassLoaderAttachment extends Date{
private static final long serialVersionUID = -1892974244318329380L;
@Override
public String toString() {
return "Hello ClassLoader";
}
}
- 自定义ClassLoader
/**
* 自定义类加载器
* 步骤:
*1. 继承ClasssLoader
*2. 重写findClass(String name) 方法
* @author mazaiting
*/
public class MyClassLoader extends ClassLoader{
/**需要加载类.class文件的目录*/
private String classDir;
/**无参构造*/
public MyClassLoader() {}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}// 测试,先将ClassLoaderAttachment.class文件加密到工程的class_temp目录下
public static void main(String[] args) throws Exception {
// 配置运行参数
// ClassLoaderAttachment.class原路径--E:\test\java_advanced\ClassLoader\bin\com\mazaiting\ClassLoaderAttachment.class
String srcPath = args[0];
// ClassLoaderAttachment.class输出路径--E:\test\java_advanced\ClassLoader\class_temp\
String desPath = args[1];
// 获取文件名
String desFileName = srcPath.substring(srcPath.lastIndexOf("\\") +1);
// 配置目标文件路径
String desPathFile = desPath + "/" + desFileName;
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(desPathFile);
// 将class进行加密
encodeAndDecode(fis, fos);
fis.close();
fos.close();
}@Override
protected Class> findClass(String name) throws ClassNotFoundException {
// class文件的路径
String classPathFile = classDir + "/" + name + ".class";
try {
// 将class文件进行解密
FileInputStream fis = new FileInputStream(classPathFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
encodeAndDecode(fis, baos);
byte[] classByte = baos.toByteArray();
// 将字节流变成一个class
return defineClass(null, classByte, 0, classByte.length);
} catch (Exception e) {
e.printStackTrace();
}return super.findClass(name);
}/**
* 加密解密
* @param is 输入流
* @param os 输出流
* @throws IOException
*/
private static void encodeAndDecode(InputStream is, OutputStream os) throws IOException {
int len;
while ((len = is.read()) != -1) {
len ^= 0xFF;
os.write(len);
}
}
}
- 运行:在MyClassLoader的main方法右键 -> Run As -> Run Configurations...,进行如下配置(配置前请确保ClassLoader工程目录下存在class_temp文件夹,其中第一个参数是ClassLoaderAttachment.class文件的源路径,第二个参数是加密后存放的目录),点击Run运行,此时在
E:\test\java_advanced\ClassLoader\class_temp
将会生成加密后的ClassLoaderAttachment.class
文件
文章图片
图7.png - 使用,在
ClassLoaderTest
的main函数中应用
public class ClassLoaderTest {
public static void main(String[] args) {
try {
// 加载类
Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
// 创建实例
Date date = (Date) classDate.newInstance();
// 输出ClassLoaderAttachment类的加载器名称
System.out.println("ClassLoader: " + date.getClass().getClassLoader().getClass().getName());
// 调用方法
System.out.println(date.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印结果:
文章图片
图8.png
- 如果loadClass方法的参数为类的全路径名
// 加载类
Class classDate = new MyClassLoader("class_temp").loadClass("com.mazaiting.ClassLoaderAttachment");
文章图片
图9.png
- 将
\class_temp\
路径下的ClassLoaderAttachment.class复制到\bin\com\mazaiting\
路径下,然后运行
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 889275713 in class file com/mazaiting/ClassLoaderAttachment
文章图片
图10.png
不合适的魔数错误(class文件的前六个字节是个魔数用来标识class文件的),因为加密后的数据AppClasssLoader无法解析。 【Java|Java 类加载器】