类加载器简介
类加载器(ClassLoader)是负责加载“.class”文件(即字节码文件)的,每个字节码文件都会有一个特殊的文件标示,因此一个java文件只会有一个字节码文件。类加载器会把字节码文件的内容加载到内存中,并把这些内容转化成内存中的数据结构。类加载器只负责加载class文件,至于这个class文件是否可以被运行,则取决于执行引擎(Execution Engine)。如下图所示:
ClassLoader把class文件加载成一个Class类,如上图的Car Class类。它是一个类模板,依据这个类模板我们可以创建多个不同的实例对象。实例对象可以有多个,而类模板则只能有一个,如下测试所示:
public class Car {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}}
public class Test {public static void main(String[] args) {Car car1 = new Car();Car car2 = new Car();Car car3 = new Car();System.out.println("car1的哈希码:" + car1.hashCode());System.out.println("car2的哈希码:" + car2.hashCode());System.out.println("car3的哈希码:" + car3.hashCode());System.out.println("car1.getClass的哈希码:" + car1.getClass().hashCode());System.out.println("car2.getClass的哈希码:" + car2.getClass().hashCode());System.out.println("car3.getClass的哈希码:" + car3.getClass().hashCode());}}
类加载子系统
启动类加载器(Bootstrap)
启动类加载器也叫根加载器,采用c++编写,用于加载Java的核心类,即JAVA_HOME/jre/lib下的jar包:
拓展类加载器(Extension)
拓展类加载器采用Java编写,用于加载拓展类,即 JAVA_HOME/jre/lib/ext下的jar包:
应用程序加载器(AppClassLoader)
应用程序加载器采用Java编写,也叫系统类加载器,用于加载应用程序ClassPath下的类(包含jar包中的类),程序员自己编写的类由该类加载器加载。
线程上下文加载器(ThreadContextClassLoader)
线程上下文加载不是一个新的类型的类加载器,但更像一个类加载器的角色,ThreadContextClassLoader可以是上述类加载器的任意一种,但往往是AppClassLoader
自定义加载器
程序员可以自定义类加载器,只需要继承 ClassLoader类和覆盖findClass()方法,不过AppClassLoader一般来说是完全够用的,一般不需要使用自定义的类加载器。
双亲委派机制
首先需要了解Java自带的类加载器的层次结构(或不严格来说的继承关系):
在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。Launcher是JRE中用于启动程序入口main()的类 ,如:
public Launcher() {Launcher.ExtClassLoader var1;try {//加载扩展类类加载器var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {//加载应用程序类加载器,并设置parent为extClassLoaderthis.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}//设置默认的线程上下文类加载器为AppClassLoaderThread.currentThread().setContextClassLoader(this.loader);//此处删除无关代码...}
所有的类加载器都遵循双亲委派机制:
当任何层次的一个类加载器去加载类时先尝试让父类加载器去加载,如果父类加载器加载不了再尝试自身加载 。比如:AppClassLoader收到加载某个类类的请求,它会先把请求给它的父类ExtClassLoader,而ExtClassLoader又会给它的父类BootstrapClassLoader。因此,首先由根加载器加载这个类,如果BootstrapClassLoader无法加载,则才会让ExtClassLoader加载,如果ExtClassLoader还是无法加载,最后才会给到AppClassLoader去加载。
关于ExtClassLoader继承BootstrapClassLoader其实是不准确或者不正确的,因为BootstrapClassLoader是采用c++编写的,使用Java代码是无法获取BootstrapClassLoader的,如:
public class Test {public static void main(String[] args) {Car car = new Car();ClassLoader classLoader = car.getClass().getClassLoader();//尝试获取AppClassLoaderSystem.out.println(classLoader);//尝试获取ExtClassLoaderSystem.out.println(classLoader.getParent());//尝试获取BootstrapClassLoaderSystem.out.println(classLoader.getParent().getParent());}}

双亲委派模型能保证基础类仅加载一次,不会让jvm中存在重名的类。比如String.class,每次加载都委托给父加载器,最终都是BootstrapClassLoader,都保证java核心类都是BootstrapClassLoader加载的,保证了java的安全与稳定性。
4.沙箱安全机制(了解即可)
Java安全模型的核心就是Java沙箱(sandbox) ,沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。所有的Java程序运行都可以指定沙箱,可以定制安全策略。在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱Sandbox)机制。
组成沙箱的基本组件:
字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。
类加载器(class loader) :
其中类加载器在3个方面对Java沙箱起作用
它防止恶意代码去干涉善意的代码;
它守护了被信任的类库边界;
它将代码归入保护域,确定了代码可以进行哪些操作。
