今天我们学习类加载器,关于类加载器其实和JVM有很大关系,在这里这篇文章只是简单的介绍下类加载器,后面学习到JVM的时候还会详细讲到类加载器,本文分为下面几个小节讲解:
一、认识类加载器
1.什么是类加载器?
所谓的类加载器可以从其作用来理解,其功能就是将classpath目录下.class文件,加载到内存中来进行一些处理,处理完的结果就是一些字节码.那是谁把这些class类加载到内存中来的呢?就是类加载器。
2.JVM中默认的类加载器有哪些?
java虚拟机中可以安装多个类加载器,系统默认三个主要的类加载器,每个类加载器负责加载不同位置的类:BootStrap,ExtClassLoader,AppClassLoader
注意的是:
1.类加载器本身也是一个java类,因为类加载器本身也是一个java类,那么这个特殊的java类【类加载器】是有谁加载进来的呢?这显然要有第一个类加载器,这第一个类加载器不是一个java类,它是BootStrap。
2.BootStrap不是一个java类,不需要类加载器java加载,他是嵌套在java虚拟机内核里面的。java 虚拟机内核已启动的时候,他就已经在那里面了,他是用c++语言写的一段二进制代码。他可以去加载别的类,其中别的类就包含了类加载器【如上面提到的Ext 和 app】。
案例:
下面我们写个例子来获取ClassLoaderTest这个类的类加载器的名字,代码如下:
package study.javaenhance;
import java.util.ArrayList;
public class ClassLoaderTest
{
public static void main(String[] args) throws Exception
{
//获取类加载器,那么这个获取的是一个实例对象,我们知道类加载器也有很多种,那么因此也有其对应的类存在,因此可以获取到对应的字节码
System.out.println(ClassLoaderTest.class.getClassLoader());
//获取类加载的字节码,然后获取到类加载字节码的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//下面我们看下获取非我们定义的类,比如System ArrayList 等常用类
System.out.println(System.class.getClassLoader());
System.out.println(ArrayList.class.getClassLoader());
}
}
结果如下:
sun.misc.Launcher$AppClassLoader@1c78e57
sun.misc.Launcher$AppClassLoader
null
null
结果分析:
ClassLoaderTest的类加载器的名称是AppClassLoader。也就是这个类是由AppClassLoader这个类加载器加载的。
System/ArrayList的类加载器是null。这说明这个类加载器是由BootStrap加载的。因为我们上面说了BootStrap不是java类,不需要类加载器加载。所以他的类加载器是null。
==================================
我们说了java给我们提供了三种类加载器:BootStrap,ExtClassLoader,AppClassLoader。这三种类加载器是有父子关系组成了一个树形结构。BootStrap是根节点,BootStrap下面挂着ExtClassLoader,ExtClassLoader下面挂着AppClassLoader.
代码演示如下:
package study.javaenhance;
import java.util.ArrayList;
public class ClassLoaderTest
{
public static void main(String[] args) throws Exception
{
//获取类加载器,那么这个获取的是一个实例对象,我们知道类加载器也有很多种,那么因此也有其对应的类存在,因此可以获取到对应的字节码
System.out.println(ClassLoaderTest.class.getClassLoader());
//获取类加载的字节码,然后获取到类加载字节码的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//下面我们看下获取非我们定义的类,比如System ArrayList 等常用类
System.out.println(System.class.getClassLoader());
System.out.println(ArrayList.class.getClassLoader());
//演示java 提供的类加载器关系
ClassLoader classloader = ClassLoaderTest.class.getClassLoader();
while(classloader != null)
{
System.out.print(classloader.getClass().getName()+"-->");
classloader = classloader.getParent();
}
System.out.println(classloader);
}
}
输出结果为:
sun.misc.Launcher$AppClassLoader-->sun.misc.Launcher$ExtClassLoader-->null
通过这段程序可以看出来,ClassLoaderTest由AppClassLoader加载,AppClassLoader的父类节点是ExtClassLoader,ExtClassLoader的父节点是BootStrap。
每一个类加载器都有自己的管辖范围。 BootStrap根节点,只负责加载rt.jar里的类,刚刚那个System就是属于rt.jar包里面的,ExtClassLoader负责加载JRE/lib/ext private static void encodeAndDecode(InputStream is,OutputStream os) throws Exception{ int bytes = -1; while((bytes = is.read())!= -1){ bytes = bytes ^ 0xff;//和0xff进行异或处理 os.write(bytes); } } }
这个类中定义了一个加密和解密的算法,很简单的,就是将字节和oxff异或一下即可,而且这个算法是加密和解密的都可以用!
当然我们还要先做一个操作就是,将ClassLoaderAttachment.class加密后的文件存起来,也就是在main方法中执行的,这里我是在项目中新建一个
同时采用的是参数的形式来进行赋值的,所以在运行的MyClassLoader的时候要进行输入参数的配置:右击MyClassLoader->run as -> run configurations
第一个参数是ClassLoaderAttachment.class文件的源路径,第二个参数是加密后存放的目录,运行MyClassLoader之后,刷新class_temp文件夹,出现了ClassLoaderAttachment.class,这个是加密后的class文件。
下面来看一下测试类:
package study.javaenhance;
import java.util.ArrayList;
public class ClassLoaderTest
{
public static void main(String[] args) throws Exception
{
//获取类加载器,那么这个获取的是一个实例对象,我们知道类加载器也有很多种,那么因此也有其对应的类存在,因此可以获取到对应的字节码
System.out.println(ClassLoaderTest.class.getClassLoader());
//获取类加载的字节码,然后获取到类加载字节码的名字
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
//下面我们看下获取非我们定义的类,比如System ArrayList 等常用类
System.out.println(System.class.getClassLoader());
System.out.println(ArrayList.class.getClassLoader());
//演示java 提供的类加载器关系
ClassLoader classloader = ClassLoaderTest.class.getClassLoader();
while(classloader != null)
{
System.out.print(classloader.getClass().getName()+"-->");
classloader = classloader.getParent();
}
System.out.println(classloader);
try {
//Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");
Class classDate = new MyClassLoader("class_temp").loadClass("study.javaenhance.ClassLoaderAttachment");
Object object = classDate.newInstance();
//输出ClassLoaderAttachment类的加载器名称
System.out.println("ClassLoader:"+object.getClass().getClassLoader().getClass().getName());
System.out.println(object);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
结果如下:
sun.misc.Launcher$AppClassLoader@6b97fd
sun.misc.Launcher$AppClassLoader
null
null
sun.misc.Launcher$AppClassLoader-->sun.misc.Launcher$ExtClassLoader-->null
ClassLoader:sun.misc.Launcher$AppClassLoader
Hello ClassLoader!
这个时候我们会发现调用的APP 的类加载器然后输出了结果,这个是正常的,因为这个时候会采用双亲委派机制。
那么这个时候,我们将自己生成的ClassLoaderAttachemet class文件,覆盖掉编译的时候生成的class 文件看下结果如何,如果正常应该会报错,因为这个时候走双亲委派机制在对应的classpath 是可以找到这个class 文件,因此APP类加载器会处理,但是因为我们的class 是加密的因此会报错,运行结果如:
那么如何让其走到我们自定义的类加载器呢,只需要将编译时候生成的目录下的.class 文件删掉即可,那么这个是APP加载不到,则会去调用findclass ,然后就会走到我们定义的类加载器中,运行结果如下:
参考资料:
张孝祥老师java增强视频
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。



