类的加载
何为类加载器,简单的说就是JVM通过类加载器ClassLoader,把.class文件中的信息,拼装成Class对象放入内存中。
JVM的类加载器,就是字节码和JVM的桥梁。如下图所示,我们写好的.java文件经过编译器,编译成.class的二进制文件(字节码),然后通过类加载器把.class文件读到内存中,组成我们使用的Class对象。
类加载到JVM内存中,分成两部分:
A 在方法区,生成该类运行时的数据结构,如类信息(版本、字段,方法,接口等描述信息),常量,静态变量等;
B 在Java堆中,生成该类的java.lang.Class对象,一方面封装方法区中对应的信息,另一方面构建可被使用的Class类结构。具体可以参考JDK的API和源码。
Class 类的实例表示正在运行的Java应用程序中的类和接口。Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass()自动构造的。
当需要创建Class的实例时,再到堆中查找该类的Class对象,整合方法区中的信息构建该类的实例对象。
加载模型
从J2SE1.2开始,JVM使用了三种类加载器:bootstrap类加载器、extension类加载器和system类加载器。他们是父子继承的关系,其中bootstrap类加载器在顶端,而system加载器在结构的最底层。 【Bootstrap > Extension > System】
Bootstrap Class Loader,是JVM的一部分,加载JVM需要的类。一旦调用java.exe程序,bootstrap类加载器就开始工作。因此,它必须使用native实现。另外,它还负责加载所有的Java核心类,例如java.lang,java.io等。 【系统类】
Extension Class Loader,负责加载标准扩展目录下面的类。这样就可以使得编写程序变得简单,只需把JAR文件拷贝到扩展目录下面即可,类加载器会自动的在下面查找。不同的供应商提供的扩展类库是不同的,Sun公司的JVM的标准扩展目录是/jdk/jre/lib/ext。 【扩展类】
System Class Loader,是默认的加载器,它在环境变量CLASSPATH目录下面查找相应的类。 【应用类】
Java的ClassLoader采用全盘负责的委派机制:
全盘负责:当前classLoader要加载一个Class的时候,这个Class所依赖的所有类都需要由这个classLoader来加载。
委派模型:JDK的默认类加载器是System类加载器,每次加载时都会先调用System类加载器。但是他不会马上load,而是check该类是否存在,如果不存在就会交给父类来处理,一直传递到Bootstrap;然后Bootstrap尝试加载此类,如果查找不到就会传给Extension,一直往下,直到成功加载此类,否者抛java.lang.ClassNotFoundException异常。
这样一来一回构成了java加载器的委派模型,每次都是由上到下地加载类。这样能保证每次都是先加载核心类,一层一层往外加载,再加上类重复加载的检查,能避免同名类引起的冲突。
试想,你在扩展jar或者是classpath中重写了一个恶意的java.lang.Object,想加载到JVM中制造破坏。但是,在这种加载模式中,程序需要加载一个java.lang.Object,只会从Bootstrap开始查找,加载类库的java.lang.Object,所以恶意的java.lang.Object永远都无法进入JVM内存。
自定义类加载器,一个System类加载器肯定无法满足所有的需求,而且只能加载classpath的文件,所以用户可以定义自己的类加载器,定义自己的规则以及加载的方式,加载的来源(如文件系统中的.class文件,jar包,网络等)。
自定义类加载器要继承ClassLoader抽象类,其loadClass()方法是final的,所以还得遵循全盘负责委派机制,只是其他方面能够自由一些。这也是尽量地保证核心类的安全性。
另外,Java运行所需的基本类会预先加载,主要包括rt.jar里面的.class文件。而其他的类库,以及用户自定义的类,就会等到使用的时候才加载进JVM内存。