这个部分呢,纯粹就是八股,只是面试要考的,日常工作却用不到,但在这一阶段仍要好好掌握。
面试里关于JVM常考的内容,分为三个方面,一是JVM内存区域划分;二是类加载机制;三是垃圾回收机制。
JVM内存区域划分
为什么要划分区域
JVMJava虚拟机是仿照真实的机器、真实的操作系统进行设计的,在真实的操作系统中,对于进程的地址空间是进行了分区域的设计,于是JVM也就仿照了操作系统的情况,也进行了分区域的设计。

JVM具体是怎么划分的呢?
核心区域有四个:
1.程序计数器
这个是一个很小的区域,保存在内存空间当中,只是用来记录当前指令执行到哪个地址了。
2.元数据区
这里用来保存当前类被加载好的数据。
我们都知道,我们写Java代码,把.java变成.class文件,要想运行这个代码,就需要把.class加载到内存里面(类加载)。
这里的类对象,就是图中的Test.class。

3.栈
栈的作用是保存方法的调用关系。当然,这里谈到的栈和堆与数据结构里的栈和堆是没有关系的。
写代码的时候,肯定会有方法调用,每次调用方法,就会进入方法的内部执行,当方法执行完毕,返回到调用位置,然后继续往后走。
在栈中,执行main方法,main方法在栈中申请一块空间,在main方法中,执行到调用的test1方法时,test1方法里面有参数、局部变量、返回值、返回的地址(test1结束后继续执行哪里),这个就是"栈帧"。

而JVM这个进程,是C++代码实现的程序,这个进程本身就是存在一系列方法调用的。
就像下图,操作系统原生的栈,叫做"本地方法栈",在这之后在C++代码中,构成了一个JVM虚拟机程序,通过这些C++代码,解释执行.class中的字节码,这些操作是操作系统实现的,不需要C++代码干预。


解释执行字节码的时候,又会涉及到一些Java的方法调用,通过上述C++代码,又构造出一个Java的栈,C++的栈帧中,其中有一个方法就在里面构造了Java的栈,这些操作是由C++代码实现的,放Java方法的栈(JVM栈),无法被Java代码干预。

当然,这两个栈之间还会有很多的联系,Java代码中有时候可能会调用到C++的代码,比如Thread.sleep这样的操作,本质上还是要调用操作系统的api的。
4.堆
堆的作用是保存new的对象。

图中的new Test()部分一定会保存在堆中,而对象t不一定,t保存了new Test()的地址,但要判定t保存在哪里,这要看t的成分:
如果t是一个局部变量,t就是在栈上
如果t是一个成员变量,t就是在堆上
如果t是一个静态成员变量,t就是在元数据区上
堆是JVM中最大的空间区域,可以为集合类里面添加元素,如果堆上的对象不再使用的话,就需要被释放掉(垃圾回收)。
这里是这四个划分的结构:

其中,元数据区和堆,整个Java进程共用同一份,程序计数器和栈,一个进程中可能有多份(因为一个进程可能有多个线程,每个线程有一份)。
JVM类加载
类加载本身是一个复杂的事情。
在面试角度,类加载主要关心两个方面:
1.类加载步骤有哪些
Java的官方文档上,也有对应的描述。一共有三个大的阶段,其中第二个阶段又分成三个步骤,一共是5个步骤。
(1)加载:找到.class文件
根据类的全限定名(包名+类名,形如java.lang.String),然后打开文件,读取文件内容到内存里。
(2)验证:解析、校验.class文件读到的内容是否是合法的,并且把这里的内容转成结构化的数据
这里的.class文件是二进制的文件,他的格式是有明确要求的。


这个表格中,constant_pool是常量池,这里用数组表示;this_class、super_class分别为子类与父类;interface是接口(同样用数组表示),校验这个.class文件是否符合表格中的格式。
(3)准备:给类对象申请内存空间
此处申请的内存空间,相当于是"全0"的空间。
(4)解析:针对字符串常量进行初始化
字符串常量本身就包含在.class文件中,就需要.class文件里解析出来的字符串常量放到内存空间里(在元数据区的常量池中)。
(5)初始化:针对刚才谈到的类对象进行最终的初始化
针对类对象的各种属性进行填充,包括类中的静态成员,因为刚申请到内存空间,类对象是"全0"的,当然,如果这个类还有父类,并且父类还没有加载,此环节也会触发父类的类加载。
2.类加载中的"双亲委派模型"是怎么回事
这里的内容,明天我们继续。
这些八股文该背还是要背的,希望大家好好掌握。