一、类文件到虚拟机(类加载机制)
1.1 装载(Load)
类加载的第一步是对类的装载过程:
1)通过一个类的全限定名获取定义此类的二进制字节流。
2)将这个字节流代表的静态存储结构转化为方法区的运行时数据结构
3)在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
1.2 链接(Link)
类加载的第二步是链接,链接这一步中包含验证、准备、解析的步骤。
1.2.1 验证(Verify)
保证被家族类的正确性
文件格式验证
元数据验证
字节码验证
符号引用验证
1.2.2 准备(Prepare)
为类的静态变量分配内存,并将其初始化为默认值。
1.2.3 解析(Resolve)
把类中的符号引用转换为直接引用。
1.3 初始化(Initialize)
类加载的第三步是初始化操作。
对静态变量、静态代码块执行初始化操作。
1.4 类加载机制图解
使用和卸载并不算是类加载过程中的阶段,只是画完整了一点。
二、类装载器(ClassLoader)
在装载(Load)阶段,其中第一步是通过类的全限定类名获取其定义的二进制字节流,需要借助类装载器完成。顾名思义,就是用来装载Class文件的。
2.1 分类
1)Bootstrap ClassLoader:负责加载$JAVA_HOME中jir/lib/rt.jar 里所有的class或Xbootclassoath选项指定的jar包。由C++实现,不是ClassLoader的子类。
2)Extension ClassLoader 负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。
3)App ClassLoader 负责加载classpath中指定的jar包及 Djava.class.path 所指定目录下的类和jar包。
4)Custom ClassLoader 通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。
2.2 图解类装载器
2.3 双亲委派机制
定义:如果一个类加载器在接到加载类的请求时,它首先不会自己去尝试加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成此加载任务时,才自己去加载。
优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。
比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载。因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,由各个类加载器自己去加载的话,那么系统中会存在多个不同的Object类。
破坏:可以继承ClassLoader类,然后重写其中的loadClass方法。
三、运行时数据区(Run-Time Data Areas)
在装载阶段的第2、3步可以发现有运行时数据、堆、方法区等名词。
其实说的就是类文件被类装载器装载进来之后,类中的内容(比如变量、常量、方法、对象这些数据)要被存储起来,存储的位置肯定是在JVM中有对应的空间。
3.1 图解
3.2 各部分简介
3.2.1 Method Area(方法区)
方法区是各个线程共享的内存区域,在虚拟机启动时创建。
用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它又有个别名叫Non-Heap(非堆),目的是和Java堆区分开来。
![](https://i-blog.csdnimg.cn/blog_migrate/283b831916828afce364f55deca075a7.png)
![](https://i-blog.csdnimg.cn/blog_migrate/197a01adb6c8fcc3ddff5e7742575baf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/f0a9948ea749e701040e113c857b7e95.png)
四、运行时数据区提炼总结
名称 | 是否线程共享 | 作用 | 可能出现的异常 |
方法区 | 线程共享 | 存放类信息(版本、字段、方法、接口等)、常量、静态变量、即时编译后的代码等数据。 | 内存不足时,抛出OutOfMemoryError(内存不足错误)。 |
虚拟机栈 | 线程私有 | 存放局部变量表、操作数据栈、动态链接、方法出口等信息。 | 栈帧深度大于允许最大深度时,抛出StackOverflowError(栈溢出错误); 内存不足时,抛出OutOfMemoryError(内存不足错误) |
堆 | 线程共享 | 存放对象实例和数组。 | 内存不足时,抛出OutOfMemoryError(内存不足错误) |
程序计数器 | 线程私有 | 记录当前线程所执行的字节码指令。 | 唯一不会出现OutOfMemoryError的区域。 |
本地方法栈 | 线程私有 | java虚拟机栈类似,不过是为了JVM用到的Native方法服务。 | 栈帧深度大于允许最大深度时,抛出StackOverflowError(栈溢出错误); 内存不足时,抛出OutOfMemoryError(内存不足错误) |
运行时常量池 | 线程共享 | 属于方法区的一部分。 存放编译器生成的各种字面量和符号引用。 | 内存不足时,抛出OutOfMemoryError(内存不足错误) |