知识图谱

JVM内存模型

- 栈(Stack):线程执行方法是会同时创建一个栈帧,储存局部变量表、操作栈、动态链接、方法出口等信息。方法调用时入栈,方法退出时出栈。
- 本地方法栈(Native Method Stack):也是存储方法调用是的信息,针对于Native方法,执行Java方法使用栈
- 程序计数器(Program Counter Register):保存当前线程执行的字节码位置,每个线程工作时都有一个独立的计数器,程序计数器为执行Java方法服务,执行native方法时,计数器为空。
- 堆(heap):几乎所有对象实例都在这里分配。当堆内存没有可用空间时,抛出OOM异常。根据对象存活周期的不同,JVM把对内存进行分代管理,由垃圾回收器进行对象的回收管理。
- 方法区(Method Area):又叫非堆区,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据。JDK1.7的永久代和1.8的Metapace都是方法区的实现。
JMM
Java内存模型,主要是定义程序中变量的访问规则。

所有共享变量都存储于主内存中,每个线程有自己的工作内存,工作内存中保存的是主内存中变量的副本,线程对变量的读写等操作必须在自己的工作内存中进行。
多线程进行数据交互时,例如线程A给一个共享变量赋值后,有线程B来读取这个值,A修改的变量是保存在自己工作内存中的副本,对xianchengB不可见。只有A将修改后的数据写回主内存后,B在从主内存中读取数据到自己的工作内存中才能进行进一步的操作。由于指令重排的存在,读-写顺序可能被打乱,因此JMM需提供原子性、可见性、有序性。

原子性
long和double等基础数据类型的读写操作是原子性的。synchronized通过Java高级字节码指令:monitorenter、monitorexit保证原子性
可见性
volatile强制变量的赋值会同步刷新回主内存,强制变量的兑取会从主内存中重新加载保证不同线程能够看到该变量值得更新
有序性
volatile阻止指令重排,保证变量读写的有序性 happens-before:
- 程序顺序原则:一个线程必须保证语义串行性
- 锁规则:对同一个锁的解锁一定发生在再次加锁之前
- happens-before原则的传递性、线程启动、中断、终止规则等。
类加载机制

- 加载:通过类的完全限定名查找类字节码文件,并利用字节码文件创建Class对象
- 验证:确保Class文件符合当前虚拟机的要求,不会危害虚拟机自身的安全
- 准备:内存分配,为类中由static修饰的变量分配内存,并设置初始值,这里的初始值是0或者null,而不是代码证设置的值。代码中的赋值实在初始化阶段完成,也不包含final修饰的静态变量,因为final在编译时分配。
- 解析:解析字段、接口、方法。将常量池中的符号引用替换为直接引用。
- 初始化:完成静态块执行与静态变量赋值,是类加载的最后阶段,若被加载类的父类没有初始化,则先初始化父类。
初始化的触发条件包括:创建类的实例、访问类的静态方法或静态变量、Class.forName()反射类或者某个子类初始化。
由Java虚拟机自带的三种类加载器加载的类在虚拟机的整个生命周期中不会被卸载,自由用户自定义的类加载器所加载类才可以被卸载。
类加载器
实现通过类的权限定名获取该类二进制字节流的代码块

- 启动类加载器加载java home中lib目录下的类
- 扩展类加载器负责加载ext目录下的类
- 应用加载器加载classpath指定目录下的类
双亲委派机制
一个类加载器在加载类时,先把这个请求委托给自己的父类加载器去执行,如果父类加载器还存在父类加载器,就继续向上委托,直到启动类加载器。如
图中红色箭头。如果父类加载器能够完成类加载,就成功返回,否则,子加载器才会尝试加载,如绿色箭头。避免类重复加载,也避免核心API被篡改。
分代回收

- 年轻代:存放新创建的对象。分为Eden和Survivor1/Survivor2。大部分对象在Eden中生成,Eden满时,还存在的对象会在两个Survivor区交替保存,达到一定次数的对象会晋升老年代
- 老年代:存放从年轻代晋升来的,存活时间较长
- 永久代:主要保存类信息等内容
垃圾回收
- 引用计数法:通过对象呗引用次数确定对象是被使用,缺点:循环引用问题
- 复制法:from/to两块内存空间。对象分配时只在from块中进行,回收时把存活对象复制到to块中,并清空from。然后to块变from块,from块变to块。缺点:内存使用率低。
- 标记清除法:分标记对象和清除不在使用对象。缺点:产生内存碎片
Serial、ParNew、Parallel Scavenge是复制算法,CMS、G1、ZGC是标记清除算法
CMS

JDK1.7之前的主流垃圾回收算法,并发收集、停顿小
- 初始标记:stop the world,标记对象是从root集最直接可达对象
- 并发标记:GC线程和应用线程并发执行,标记可达对象
- 重新标记:主要对对象进行重新扫描并标记
- 并发清理:进行并发的垃圾清理
- 并发重置:为下一次GC重置相关数据结构
G1

JDK1.9后的默认垃圾回收算法,保持高回收率的同时,减少停顿。 取消堆中年轻代和老年代的物理划分,但仍属于分代收集器。G1将堆划分为若干个区域,称作Region。一部分做年轻代,一部分做老年代,一部分用于存储巨型对象。
-
G1年轻代回收,采用复制算法,并进行收集,收集过程会STW
-
G1老年代回收,同时也会对年轻代回收
- 初始标记完成对root对象标记,STW
- 并发标记,和用户线程并行执行
- 最终标记,完成三色标记周期
- 复制/清除,优先对可回收空间大的Region进行回收,即garbage first。
ZGC
JDK1.11中的高效垃圾回收算法,可支持TB级别的堆,能做到10ms一下的回收停顿
- 着色指针技术,64位平台,一个指针可用位为64位,ZGC限制最大支持4TB的堆,寻址使用42位,剩余22位存储额外信息。着色指针技术利用指针的额外信息位,在指针对象上做着色标记
- 使用读屏障,解决GC线程和应用线程可能并发修改对象状态的问题。
- 由于读屏障的作用,进行垃圾回收的大部分时候不需要STW,因此ZGC大部分时间是并发处理
- 基于Region,region可以动态的创建和销毁,大小也是动态的
- 压缩整理,CMS算法清理对象时原地回收,ZGC和G1会在回收后对Region中对象进行移动合并,解决碎片问题。

- 开始回收,短暂的STW,进行root标记
- 并发标记,通过对对象指针进行着色标记,结合读屏障解决单个对象的并发问题。
- 清理阶段,将标记为不在使用的对象进行回收,如图中黄色部分。
- 重定位,对GC后存活的对象进行移动,释放大块内存空间,解决碎片问题。
- 短暂STW,重定位集合中的root对象
- 并发重定位