一、JVM内存结构 1.1 内存结构---概略图
1.2 内存结构--详细图 二、类加载器子系统的作用类加载器子系统负责从文件系统或网络中加载.Class文件,文件需要有特定的标识(cafe babe)。
ClassLoader只负责.Class文件的加载,至于它是否可以运行,由执行引擎决定。
加载的类信息存放于一块被称为“方法区”的内存空间。除了类信息外,方法区还会存放运行时常量池信息,可能还包括字符串字面量(字面量指的是固定值,初始值)和数字常量(这部分常量信息是.Class文件中常量池部分的内存映射)
.Class文件被解析加载到 JVM,类的对象加载到堆区,类信息被加载到方法区(java8 中方法区的实现是“元空间”)。这部分工作是类加载子系统完成的
三、类加载的过程假设定义了一个类,名为HelloWorld,运行其 Main 方法,流程如图:
加载(Loading)是狭义上的加载,“类加载”中的加载是广义上的。
通过一个类的全限定名获取定义此类的二进制字节流;
将这个字节流所代表的静态存储结构转化为方法区的运行时数据机构;
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
加载.class文件的方式:
从本地系统中直接加载。
通过网络获取,典型场景:Web Applet。
从zip压缩包中读取,成为日后Jar、war格式的基础。
运行时计算生成,使用最多的是动态代理技术。
有其他文件生成,典型场景是JSP应用。
从专有数据库中提取.class文件,比较少见。
从加密文件中获取,典型的防Class文件被反编译的保护措施。
3.2 链接(Linking)链接可细分为三步:验证(verify)准备(prepare)、解析(resolve)
3.2.1 验证目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。
主要包括四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证。
3.2.2 准备为类的变量分配内存并设置默认初始值,即零值(不同数据类型的零值不同,布尔值为false,引用类型为null)。下面这个语句在这个阶段,a将被初始化为0。
static int a = 2;这里不包含final修饰的static,因为final修饰的变量不再是变量而是不可更改的常量,在编译期就已经分配内存,准备阶段会将其显示初始化。如上面的赋值语句,加上final后,a的值就被初始化为2。
final static int a = 2;这里不会为实例变量分配内存和初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
3.2.3 解析将常量池内的符号引用转换为直接引用的过程。
事实上,解析动作往往伴随着 JVM 在执行完初始化之后再执行。
3.3 初始化(Initialization)初始化阶段就是执行类构造方法<clinit>()的过程。
此方法不需要定义,是javac编译器自动收集类中所有类静态变量的赋值动作和静态代码块的语句合并而来。
只有当有静态变量static int a = 1;或者静态代码块static {}时才会创建并执行该方法。
静态方法并不会使得虚拟机创建执行该方法。
<clinit>()中指令按语句在源文件中出现的顺序执行。具体表现就是一个静态变量的最后的取值决定于最后一行它的赋值语句。
public class ClassInitTest { private static int num = 1; static{ num = 2; number = 20; System.out.println(num); //System.out.println(number);//报错:非法的前向引用。 } private static int number = 10; //linking之prepare: number = 0 --> initial: 20 --> 10 public static void main(String[] args) { System.out.println(ClassInitTest.num);//2 System.out.println(ClassInitTest.number);//10 } }在以上代码中,num和number的最终值分别为2和10。
4. <clinit>()不同于我们常说的类的构造函数。类的构造函数在虚拟机中是<init>(),在任何时候都是会创建的,因为所有的类都至少有一个默认无参构造函数。
5. 若该类具有父类,JVM会保证子类的 <clinit>()执行前,父类的 <clinit>()已经执行完毕。
6. 虚拟机必须保证一个类的 <clinit>()方法在多线程下被同步加锁。保证同一个类只被虚拟机加载一次(只调用一次 <clinit>()),后续其它线程再使用类,只需要在虚拟机的缓存中获取即可。