在谈类加载器之前,先谈一谈语言的类型。通常分为编译型语言(C、C++、Java、C#等等)和解释性语言(Javascript、Python、Ruby等等)
两种语言的区别在于前者需要编译,而后者可以在环境内直接运行。
对前者再细分,虽然都需要编译,但是C和C++编译的是机器码,也就是说如果编译环境是X86的cpu,那么只能运行在X86的cpu上,而编译在linux系统链上的,只能运行在对应类型的操作系统之上,因为它们编译出来的是机器码,与硬件及操作系统是密切耦合的。优势在于运行速度快,性能发挥到最大。
而对于Java和C#,虽然也编译,但是编译出来的是字节码,这些字节码需要对应的虚拟机去解释执行,所以只需要虚拟机能够对应不同硬件和操作系统的版本,而应用代码被编译后,由虚拟机解释并转换到目标机指令进行执行,所以字节码是独立于硬件和系统的。这种方式的优点是应用代码隔离于硬件,能够一次编译,多硬件环境运行。
对于Java和C#都有对应的类加载器,以Java为样本描述。
Java是以面向对象为编程的,应用的数据结构和算法都在类的内部,所以Java有一个专门的类加载器对所有需要使用到的类进行内存的加载,这意味着算法都被加载了起来,而数据结构基于类的初始化、实例化以及运行时,在堆上和栈上进行了分配并被引用。
回到类加载器,类加载器加载类,一个应用内能否有多个类加载器呢?能否有同包同名的不同类呢?
答案是:可以有多个类加载器,可以有同包同名的不同类。但对于java自身的类,不允许出现同包同名的。
原因在于虚拟机启动时,至少有如下二个类加载器,一个加载了所有java基本类,这是基类加载器,而另一个则是加载了应用类,可以称为下级类加载器,也是默认类加载器,下级类加载器中的类不允许和基类加载器冲突。
Java允许额外定义类加载器,可以再定义一个类加载器,只要这个类加载器内的类不和基类加载器冲突即可,可以和默认类加载器加载同包同名的类,当然,也可以定义更多的类加载器。
当在代码中new一个类的实例时,其实是通过该代码所在类的类加载器去寻找这个新类的定义并构建实例。如果希望通过某个自定义类加载器中的类去创建实例,那么可以通过自定义类加载器,使用类名获取到Class对象,再通过反射的方法实例化,而在这个类里面的所有new,都将通过这个自定义类加载器去实例化对象。
基类加载器(L1)、应用默认类加载器(L2)、自定义类加载器(L2)。L1的只有一个,加载的类只允许一份的存在,具有所有类加载器的全局性;L2的可以有多个,加载的类必须服从L1,但和别的L2互相独立。
基于上述说明,用户自定义的类,如果存在同名同包的,可以通过两个不同的L2级别的类加载器加载即可。
当类加载器被卸载时,所有该类加载器内的类以及这些类初始化、实例化以及运行产生的数据都讲被销毁。典型应用是web容器中的web站点加载。
类加载器概念先聊这么多,基于它的特性,让应用可以做到很多编译到机器码无法做到的事情。
譬如说可以结合原业务,书写新的业务,并通过特定接口注入到某个运行的环境内,从而达到一些可自定义的动态行为控制。
譬如说可以给Class文件加密,再defineClass的时候,加载字节数组,先解密,再加载真真的class数据,达到对class保密的效果,超越单纯的混淆。
ClassLoader、Class、Object
ClassLoader:findClass、defineClass(终极入参是字节数组)