bootstrap 数据加载中提示_JAVA类加载器总结整理

一、What(是什么?)

1、概念

Java类加载器是Java运行时环境的一部分,负责动态加载Java类到JVM的内存空间中。每个Java类必须由某个类加载器装入到内存中。每一个类加载器都有一个父类加载器(BootStrap引导类加载器没有)。

2、 JVM中有3个默认的加载器:

(1) BootStrap:引导类加载器。这个加载器很特殊,它不是JAVA类,因此它不需要被别人加载,它嵌套在JVM内核里,也就是说JVM启动的时候BootStrap就启动了,它是C++写的二进制代码,可以加载别的类。这也是为什么System.class.getClassLoader()结果为null的原因,因为它不是JAVA类,所以它的引用返回null。负责加载核心Java库,存储在<JAVA_HOME>/jre/lib/rt.jar

(2) ExtClassLoader:扩展类加载器。

(3) AppClassLoader:根据类路径来加载java类。一般我们自定义的类都是通过这个AppClassLoader加载。

3、类加载器及其委托机制

34bddc684160cfc263e799e3b1cda3cc.png

(1) 当Java虚拟机加载一个类时,如何加载呢?

首先当前线程的类加载器去加载线程中的第一个类(如类A):

l 如果类A引用了类B,Java虚拟机将加载类A的加载器去加载类B

l 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类

(2) 委托机制的作用—防止内存中出现多分同样的字节码

例如类A和类B都要加载System类:

l 如果不用委托机制,都是自己加载,那么类A会加载一份Sysem字节码,同时类B也会加载一份字节码看,这样内存中就出现了两份System字节码。

l 如果使用委托机制,会递归地向父类查找,首选用BootStrap尝试加载,如果找不到就向下。这里System就能在BootStrap中找到然后加载。如果此时B也加载System,也从BootStrap,此时BootStrap发现已经加载过System字节码,则直接返回内存中的System字节码而不是重新加载,这样就保证了内存中只有一份字节码。

例如:用户使用一个自定义的类(没有使用自定义类加载器),那么系统就开始从AppClassLoader向父类加载器发送请求,一直到BootStrap,然后BootStrap类加载器没有父类,于是就开始查找对应路径下是否有符合要求的类。如果没有,则又向下查询,最终回到AppClassLoader(请求的发起者),如果有则返回,没有则会抛出ClassNotFoundException的异常。如果在AppClassLoader之前,其他类加载器已经找到,则由对应的类加载其返回。

代码实例1:

public class ClassLoaderTest {

	public static void main(String[] args)   {
		 
 System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
        System.out.println("=========================================");
		ClassLoader loader = ClassLoaderTest.class.getClassLoader();
		while(loader != null) {
			System.out.println(loader.getClass().getName());
			loader = loader.getParent();
		}
        System.out.println(loader);
	}
}

de8ad8ba9264fd75f10c71846e5c6a5e.png

代码实例2:

首先我们自定义一个类:

public class Secret{

   public String key(){
       return "The key is 5132561";
   }

}

然后打印加载这个类的类加载器:

System

输出结果如下:

695253f7e59ce7f043a3d87ef1175b9d.png

因为从BootStrapèExtClassLoaderèAppClassLoader这个过程中,只有到AppClassLoader才找到对应的类,所以打印AppClassLoader。

代码实例3:

如果我们将Secret的字节码打成jar包并放到ExtClassLoader所指向的目录–<JAVA_HOME>/jre/lib/ext目录下,那么BootStrapèExtClassLoader,到ExtClassLoader就找到了对应的类并返回,这时就打印ExtClassLoader。

打包成jar包并保存到<JAVA_HOME>/jre/lib/ext目录下。

146217f1e9b39a57cc14746966f9fc39.png

92a14adddcc78cab370920b45a499c83.png

再次输出:

System.out.println(new Secret().getClass().getClassLoader().getClass().getName());

d655356169487d2c526dbc9798f989bc.png

二、How(如何自定义类加载器?)

自定义的类加载器必须继承ClassLoader,并实现重载findClass方法。

代码实例:

public class DecodeClassLoader extends ClassLoader{
	private String classDir;

	public DecodeClassLoader(){}
	
	public DecodeClassLoader(String classDir){
		this.classDir = classDir;
	}

	@Override
	protected Class<?> findClass(String name) {
		File f = new File(classDir,name.substring(name.lastIndexOf(".") + 1) + ".class");
		try {
			InputStream in = new FileInputStream(f);
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			encode(in, out);
			byte[] bytes = out.toByteArray();
			in.close();
			out.close();
			return defineClass(bytes, 0, bytes.length);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}
		return  null;
	}
	
	private static void encode(InputStream in, OutputStream out) throws IOException{
		int b = 0;
		while((b=in.read())!= -1){
			out.write(b ^ 0xff);
		}
	}	
}

三、Where(在什么地方使用?)

(1)运行时装载或卸载类。常用于:

  • 实现脚本语言
  • 用于bean生成器
  • 允许用户定义的扩展性
  • 允许命名控件之间的通信。

(2)改变Java字节码的装入,例如Java类字节码的加密

(3)修改以装入的字节码

四、类加载器的综合应用实例

通过一个加密类对一个重要的类字节码进行加密,使得只有使用解密类加载器才可以成功加载并使用。示意图如下:

0d6a2a5c81e3d8b4cf4ac33a964ccaf6.png

0e38a4ac8568e9bf8b595eeea697dd93.png

(1)DecodeClassLoader.java见上面。

(2)加密类:对Secret字节码进行加密

public class EncodeUtil {
	private static void encode(InputStream in, OutputStream out) throws IOException{
		int b = 0;
		while((b=in.read())!= -1){
			out.write(b ^ 0xff);
		}
	}
	
	public static void main(String[] args) throws IOException {
		String srcPath = "E:java_workspace004ClassLoaderDemobincomshuwoomclassloaderSecret.class";//args[0];
		String destDir = "shuwoomlib";
		
		FileInputStream in = new FileInputStream(srcPath);
		String destFileName = srcPath.substring(srcPath.lastIndexOf("") + 1);
		String destPath = destDir + "" + destFileName;
		System.out.println(destPath);
		FileOutputStream out = new FileOutputStream(destPath);
		encode(in,out);
		in.close();
		out.close();
	}
}

(3)被加载的类:

public class Secret{

	public String key(){
		return "The key is 5132561";
	}
}

首先运行EncodeUtil加密工具类,将Secret.class文件加密并保存到指定的shuwoomlib目录下,此时,在bin/bom/shuwoom/classloader目录下的Secret是未经加密的字节码。现在我们将shuwoomlib目录下加密的Secret.class替换掉bin/bom/shuwoom/classloader目录下的Secret.class。那么AppClassLoader找到的就是经过加密的字节码。

ec72969cca4ae8dd313293a9373332ac.png

8826218c1732f417c50f82c107e03a82.png

//如果直接使用AppClassLoader加载,会报错。

System.out.println(new Secret().key());

编译运行会报错:

3bd25bb74521336f4ec4dbc0f4f12a0f.png

//通过DecodeClassLoader类加载器获得Secret原字节码

ClassLoader classLoader = new DecodeClassLoader("shuwoomlib");
Class clazz = classLoader.loadClass("Secret");

Method getKeyMethod = clazz.getMethod("key");
System.out.println(getKeyMethod.invoke(clazz.newInstance(), null));

此时才能正常使用。

6e01d3e8e39255de434b24e614afdfca.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值