类加载器简介

类加载器(ClassLoader)是负责加载“.class”文件(即字节码文件)的,每个字节码文件都会有一个特殊的文件标示,因此一个java文件只会有一个字节码文件。类加载器会把字节码文件的内容加载到内存中,并把这些内容转化成内存中的数据结构。类加载器只负责加载class文件,至于这个class文件是否可以被运行,则取决于执行引擎(Execution Engine)。如下图所示:
image.png
ClassLoader把class文件加载成一个Class类,如上图的Car Class类。它是一个类模板,依据这个类模板我们可以创建多个不同的实例对象。实例对象可以有多个,而类模板则只能有一个,如下测试所示:

  1. public class Car {
  2. private String name;
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Car car1 = new Car();
  4. Car car2 = new Car();
  5. Car car3 = new Car();
  6. System.out.println("car1的哈希码:" + car1.hashCode());
  7. System.out.println("car2的哈希码:" + car2.hashCode());
  8. System.out.println("car3的哈希码:" + car3.hashCode());
  9. System.out.println("car1.getClass的哈希码:" + car1.getClass().hashCode());
  10. System.out.println("car2.getClass的哈希码:" + car2.getClass().hashCode());
  11. System.out.println("car3.getClass的哈希码:" + car3.getClass().hashCode());
  12. }
  13. }

测试结果:
image.png

类加载子系统

启动类加载器(Bootstrap)

启动类加载器也叫根加载器,采用c++编写,用于加载Java的核心类,即JAVA_HOME/jre/lib下的jar包:
image.png

拓展类加载器(Extension)

拓展类加载器采用Java编写,用于加载拓展类,即 JAVA_HOME/jre/lib/ext下的jar包:
image.png

应用程序加载器(AppClassLoader)

应用程序加载器采用Java编写,也叫系统类加载器,用于加载应用程序ClassPath下的类(包含jar包中的类),程序员自己编写的类由该类加载器加载。

线程上下文加载器(ThreadContextClassLoader)

线程上下文加载不是一个新的类型的类加载器,但更像一个类加载器的角色,ThreadContextClassLoader可以是上述类加载器的任意一种,但往往是AppClassLoader

自定义加载器

程序员可以自定义类加载器,只需要继承 ClassLoader类和覆盖findClass()方法,不过AppClassLoader一般来说是完全够用的,一般不需要使用自定义的类加载器。

双亲委派机制

首先需要了解Java自带的类加载器的层次结构(或不严格来说的继承关系):
image.png
在虚拟机启动的时候会初始化BootstrapClassLoader,然后在Launcher类中去加载ExtClassLoader、AppClassLoader,并将AppClassLoader的parent设置为ExtClassLoader,并设置线程上下文类加载器。Launcher是JRE中用于启动程序入口main()的类 ,如:

  1. public Launcher() {
  2. Launcher.ExtClassLoader var1;
  3. try {
  4. //加载扩展类类加载器
  5. var1 = Launcher.ExtClassLoader.getExtClassLoader();
  6. } catch (IOException var10) {
  7. throw new InternalError("Could not create extension class loader", var10);
  8. }
  9. try {
  10. //加载应用程序类加载器,并设置parent为extClassLoader
  11. this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
  12. } catch (IOException var9) {
  13. throw new InternalError("Could not create application class loader", var9);
  14. }
  15. //设置默认的线程上下文类加载器为AppClassLoader
  16. Thread.currentThread().setContextClassLoader(this.loader);
  17. //此处删除无关代码
  18. ...
  19. }

所有的类加载器都遵循双亲委派机制:
当任何层次的一个类加载器去加载类时先尝试让父类加载器去加载,如果父类加载器加载不了再尝试自身加载 。比如:AppClassLoader收到加载某个类类的请求,它会先把请求给它的父类ExtClassLoader,而ExtClassLoader又会给它的父类BootstrapClassLoader。因此,首先由根加载器加载这个类,如果BootstrapClassLoader无法加载,则才会让ExtClassLoader加载,如果ExtClassLoader还是无法加载,最后才会给到AppClassLoader去加载。
image.png
关于ExtClassLoader继承BootstrapClassLoader其实是不准确或者不正确的,因为BootstrapClassLoader是采用c++编写的,使用Java代码是无法获取BootstrapClassLoader的,如:

  1. public class Test {
  2. public static void main(String[] args) {
  3. Car car = new Car();
  4. ClassLoader classLoader = car.getClass().getClassLoader();
  5. //尝试获取AppClassLoader
  6. System.out.println(classLoader);
  7. //尝试获取ExtClassLoader
  8. System.out.println(classLoader.getParent());
  9. //尝试获取BootstrapClassLoader
  10. System.out.println(classLoader.getParent().getParent());
  11. }
  12. }

image.png

双亲委派模型能保证基础类仅加载一次,不会让jvm中存在重名的类。比如String.class,每次加载都委托给父加载器,最终都是BootstrapClassLoader,都保证java核心类都是BootstrapClassLoader加载的,保证了java的安全与稳定性。

4.沙箱安全机制(了解即可)
Java安全模型的核心就是Java沙箱(sandbox) ,沙箱是一个限制程序运行的环境。沙箱机制就是将Java代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么? CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。所有的Java程序运行都可以指定沙箱,可以定制安全策略。在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱Sandbox)机制。
image.png
组成沙箱的基本组件:
字节码校验器(bytecode verifier) :确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。

类加载器(class loader) :
其中类加载器在3个方面对Java沙箱起作用
它防止恶意代码去干涉善意的代码;
它守护了被信任的类库边界;
它将代码归入保护域,确定了代码可以进行哪些操作。