1. 类的加载, 连接和初始化
当程序使用某个类时, 如果该类还未被加载到内存中, 则系统会通过加载, 连接, 初始化三个步骤来对类进行初始化. 如果没有意外, jvm 将会连续完成这三个步骤, 有时也把这三个步骤统称为类的加载和类初始化.
1.1 类的加载
类加载指的是将类的 class 文件读入内存, 并且为之创建一个 java.lang.Class 对象, 也就是说当程序中使用任何类时, 都会为之创建一个 java.lang.Class 对象.
类的加载由类加载器完成, 类加载器通常由 JVM 提供, 这些类加载器是前面所有程序运行的基础, JVM 提供的这些类加载器通常被称为系统类加载器. 除此之外, 开发者可以通过继承 ClassLoader 基类来创建类加载器.
1.2 类的连接
当类被加载之后, 系统会为之生成一个 Class 对象, 接着将进入连接阶段, 连接阶段负责把类的二进制数据合并到 JRE 中. 连接阶段分为三个步骤:
a. 验证: 验证阶段用于验证被加载的类是否有正确的内部结构, 并和其他类协调一致.
b. 准备: 准备阶段负责为类变量分配内存, 并且设置默认的初始值.
c. 解析: 解析阶段会让 JVM 检查类文件中所引用的类型是否都是已知的类型, 如果有运行时未知的类, 那么它们也会被加载进来.
1.3 类的初始化
在类的初始化阶段, 虚拟机负责对类进行初始化, 主要就是对类变量进行初始化, 在 java 类中对类变量进行初始化有两种方式: 1. 声明类变量时指定初始值. 2. 使用静态初始化块对类变量指定初始值.
类变量和静态初始化块的执行优先级是同级的, 例如:
- public class Test {
- static{
- n=10;
- }
- static int n=9;
- public static void main(String[] args) {
- System.out.println("n="+n);//9
- }
- }
这里的输出值是 9, 并非 10, 因为静态初始化块和类变量是同级的. 当程序加载, 连接完成以后, 进入准备初始化阶段, 这个时候 n 为 0, 程序按照先后循序首先使用 10 为 n 赋值, 然后再使用 9 为 n 赋值.
经过上面的介绍, 我们已经知道一个对象的建立需要经历加载, 链接, 初始化. 在类加载的时候, 需要使用类加载器, 接下来介绍类加载器的作用.
2. 类加载器
类加载器负责将. class 文件加载到内存中, 并为之生成 java.lang.Class 对象, 这是后面讲到的 java 反射机制的基础.
当 JVM 启动时, 会形成由三个类加载器组成的类加载器结构.
Bootstrap ClassLoader: 根类加载器
Extension ClassLoader: 扩展类加载器
System ClassLoader: 系统类加载器
如果用户自定义了类加载器, 那么还有用户类加载器. 除了根类加载器外所有的类加载器都是 java.lang.ClassLoader 的子类, 并且这些类加载器是具有层次结构的.
如图:
根类加载器: 负责加载 java 的核心类.
扩展类加载器: 负责加载 JRE 的扩展目录 (%JAVA_HOME%/jre/lib/ext 或者 java.ext.dirs 指定的目录) 中的 jar 包.
系统类加载器: 负责在 JVM 启动时, 加载来自 java 命令的 - classpath 选项, 或是 java.class.path 系统属性, 或是 CLASSPATH 环境变量所指定的 JAR 包和类路径.
- public static void main(String[] args) throws IOException {
- System.out.println("// 系统类加载器");
- ClassLoader systemLoader=ClassLoader.getSystemClassLoader();
- System.out.println("系统类加载器:"+systemLoader);
- /* 获得系统类加载器的加载路径
- * 通常为 CLASSPATH 环境变量指定的值.
- * 如果没有指定 CLASSPATH 环境变量, 那么默认以当前路径作为系统类的加载路径.
- */
- Enumeration<URL> em1=systemLoader.getResources(".");
- while(em1.hasMoreElements()){
- System.out.println(em1.nextElement());
- }
- System.out.println("// 扩展类加载器");
- ClassLoader extensionLoader=systemLoader.getParent();// 系统类加载器的父类就是扩展类加载器
- System.out.println("扩展类加载器:"+extensionLoader);
- System.out.println("扩展类加载器的加载路径:"+System.getProperty("java.ext.dirs"));
- System.out.println("扩展类加载器的父加载器:"+extensionLoader.getParent());// 获得根加载器
- System.out.println("// 根类加载器");
- URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
- for(int i=0;i<urls.length;i++){
- System.out.println(urls[i].toExternalForm());
- }
- }
- View Code
打印结果为:
// 系统类加载器
系统类加载器: sun.misc.Launcher$AppClassLoader@addbf1
- file:/D:/workspace/pc141/Test/bin/
- // 扩展类加载器
扩展类加载器: sun.misc.Launcher$ExtClassLoader@42e816
扩展类加载器的加载路径: D:\App\jdk\jre1.6\lib\ext;C:\Windows\Sun\Java\lib\ext
扩展类加载器的父加载器: null
- // 根类加载器
- file:/D:/App/jdk/jre1.6/lib/resources.jar
- file:/D:/App/jdk/jre1.6/lib/rt.jar
- file:/D:/App/jdk/jre1.6/lib/sunrsasign.jar
- file:/D:/App/jdk/jre1.6/lib/jsse.jar
- file:/D:/App/jdk/jre1.6/lib/jce.jar
- file:/D:/App/jdk/jre1.6/lib/charsets.jar
- file:/D:/App/jdk/jre1.6/lib/modules/jdk.boot.jar
- file:/D:/App/jdk/jre1.6/classes
从上面的打印结果可以看出, 系统类加载器的父加载器是扩展类加载器, 扩展类类加载器的父加载器是根加载器, 但是为什么调用扩展类加载器的 getParent()却返回 null 呢? 这是因为根加载器比较特殊, 它不是 ClassLoader 的子类.
来源: http://www.bubuko.com/infodetail-2970228.html