JDK全称为Java Development Kit,是Java开发人员在编写Java程序时使用的开发工具包。如图所示,其包括了JRE和JVM。
其中JRE是Java程序的运行环境,提供了JVM的实现和一些基本类库。
而JVM则是识别.class文件中的指令并调用操作系统完成指令动作。

仅仅是将我们的 .java 文件转换成了 .class 文件,实际上就是文件格式的转换。

所谓类加载机制就是:虚拟机把Class文件加载到内存,并对数据进行校验、转换解析和初始化等操作,形成虚拟机可以直接使用的Java类型,即java.lang.Class 。具体为以下几个步骤:

装载的操作其实就是查找和导入class文件
- 通过一个类的全限定名获取定义此类的二进制字节流(全限定名是将类名中的.替换为/,例如 java/lang.String)
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备(Prepare):为类的静态变量分配内存,并将其初始化为默认值
public class Demo1 {
private static int i;public static void main(String[] args) {// 正常打印出0,因为静态变量i在准备阶段会有默认值0System.out.println(i);
}
}
public class Demo2 {public static void main(String[] args) {// 编译通不过,因为局部变量没有赋值不能被使用int i;System.out.println(i);}
}
解析(Resolve):把类中的符号引用转换为直接引用的过程
主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
符号引用就是一组符号来描述目标
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
对类的静态变量,静态代码块执行初始化操作
在装载(Load)阶段,其中第(1)步:通过一个类的全限定名获取定义此类的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class文件的。

双亲委派是指在加载类的时候,如上图,按照类加载器的顺序向上检查该类是否加载过,向下委派是否可以加载该类。 避免类重复加载并且保证安全性。其执行过程如下图

类加载源码
protected Class> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}

方法区是各个线程共享的内存区域,在虚拟机启动时创建。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常
注意:JVM运行时数据区是一种规范,在JDK 8中的实现就是Metaspace,在JDK6或7中的实现就是Perm Space
Java堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享。
Java对象实例以及数组都在堆上分配。
虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。所以虚拟机栈是线程私有的,随着线程的创建而创建。
每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。(调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。)
每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、动态链接、方法返回地址和附加信息。

如下图,堆被分为Young区、Old区,其中Young区又分为Eden区、Survivor区,而Survivor区又分为S0和S1。

为什么这样设计有以下原因:


当有一个新的对象Young区无法分配空间时,直接放到老年代。

方法区可以看作是一个规范,JDK1.7之前的实现是持久代,JDK1.8以及其之后的实现是元数据区。
元数据区的优势是,如果本机内存为16G尽管只给JVM分配了4G,元数据区也是会用到本机的其他12G内存,这样的好处是我们不用估算类信息、常量、静态变量这些数据的占用空间。
如下图,方法区中的静态变量、常量在运行时内存中,而类信息在直接内存中。

