JVM 堆内存
在Java 虚拟机中,堆内存被划分为两个不同的区域:
新生代和老年代。新生代:被划分为三个区域,分别是:Eden、From survivor 和 To survivor
堆内存划分的目的是:为了使JVM 更好地管理JVM 内存对象,包括内存的分配以及内存的
Java 垃圾回收
JVM 在对堆内存进行垃圾回收的时候,需要判断对象是否还在使用当中,如果仍然在使用的话,则不回收,如果已经不会再被使用,则将其内存回收掉。
如何判断堆中的对象是否已死,Java虚拟机使用的是:可达性分析算法
可达性分析算法
这个算法的核心思想是:通过一系列名为“GC roots” 的对象为起始点,从这个被称为“GC roots” 的对象向下搜索
如果一个对象到GC roots 没有任何的引用链相连,则说明该对象不可用
总而言之:以给定的一个集合的引用作为根出发点,通过引用关系遍历对象图,能被遍历到的(可到达的对象)则被判定为存活,不可达的则被判定为死亡。
可以作为GC roots 的四大对象
- 虚拟机栈(栈帧中的局部变量表)中应用的对象
- 方法区中的类静态属性应用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI (Native 方法) 应用的对象
常用垃圾回收算法
- 标记-清除 算法
该算法分为“标记”和“清除”两个阶段。首先标记处所有需要回收的对象,在标记完之后统一回收。
优点:节省空间
缺点:产生内存碎片,标记和清除的效率都低
- 标记-压缩 算法
该算法与标记-清除算法的标记方式一致,首先对存活的对象进行标记,然后让所有存活的对象都向一端移动,然后直接清除掉边界以外的内存。
- 复制算法
该算法的核心思想是:将可用的内存划分为容量相等的两块,每次只使用其中的一块。当这一块内存使用完时,则把当前内存块还存活的对象复制到另一个内存块中,然后再把使用过的内存块清除掉。
优点:实现简单,运行高效
缺点:内存的使用代价高,毕竟每次都只使用其中的一半
- 分代收集 算法(常用)
分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活
的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代和新生代。
老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回
收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取不同的垃圾回收算
法。
主要的垃圾回收器
Serial 收集器
- Serial 垃圾回收器通过暂停应用程序所有的线程进行工作。
- 它为单线程环境设计,只使用一个单独的线程进行垃圾回收,通过冻结所有应用程序线程进行工作,所以可能不适合服务器环境。
- 它最适合的是简单的命令行程序(单CPU、新生代空间较小及对暂停时间要求不是非常高的应用)。
- 使用该垃圾回收器,新生代和老年代都会使用串行回收收集器,新生代使用复制算法,老年代使用标记-整理算法
ParNew 收集器
- 该垃圾回收器只对新生代起作用
- ParNew 回收器也需要冻结用户的工作线程,在新生代中使用多线程来运行复制算法来进行垃圾回收,在老年期还是使用单线程和标记-整理算法来进行垃圾回收
Parallel Scavenge 收集器
- 它使用多线程进行垃圾回收。当执行垃圾回收的时候它也会冻结所有的应用程序线程。
- 新生代和老年代都用多个线程并行去运行
- 新生代和老年代都使用复制算法
CMS并发标记扫描垃圾回收器
- 用户线程和垃圾回收线程同时执行,适用于对响应时间有要求的场景
- 只要在老年代开启了CMS,新生代会自动开启ParNew,Serial Old会作为CMS 出错的后备收集器
CMS并发标记扫描垃圾四个步骤(重要):
1、初始化标记Initial Mark(冻结用户线程):只是标记一下GC Roots能直接关联的对象,速度很快
2、并发标记Concurrent Mark(GC 线程和用户线程一起工作):进行GC Roots跟踪过程,这是主要的标记过程,标记所有的对象
3、重新标记Remark(冻结用户线程):为了修正正在并发标记期间,因用户程序继续运行而导致标记发生变动的那一部分对象的标记记录,任然需要暂停所有工作线程。(二次确认)
4、并发清除Concurrent sweep(GC 线程和用户线程一起工作)
G1垃圾回收器
- G1 是一款面向服务端应用的垃圾收集器
- 可以建立可预测的停顿时间模型(G1 的优秀之处)
- G1 将整个Java 堆划分为多个大小相同的独立区域(Region),虽然还保留着新生代和老年代的概念,但新生代和老年代不再是物理隔离了。
G1 运作四个步骤
- 初始标记:标记FC Root能直接关联到的对象
- 并发标记:从GC Root开始对堆中对象进行可达性分析,找出存活对象
- 最终标记:为了修正正在并发标记期间,因用户程序继续运行而导致标记发生变动的那一部分对象的标记记录,仍然需要暂停所有工作线程。
- 筛选回收:对各个Region 的回收价值和成本进行排序,根据用户所期望的GC 停顿时间来制定回收计划。
GC是什么时候触发的
GC有两种类型:Scavenge GC 和 Full GC
Scavenge GC
原因: 当新对象生成,并且在新生代申请空间失败时,就会触发Scavenge GC
Scavenge GC 流程:
当发生 Scavenge GC时,Eden 区和 from 指向的 Survivor 区中的存活对象会被复制到 to 指向的 Survivor区中,然后交换 from 和 to指针,以保证下一次 Minor GC时,to 指向的 Survivor区还是空的。
注意: from与to只是两个指针,它们是会变动的,to指针指向的Survivor区是空的
Full GC
原因
- 年老代(Tenured)被写满
- 持久代(Perm)被写满
- System.gc()被显示调用
- 上一次GC之后Heap的各域分配策略动态变化
Full GC 流程
对整个堆进行整理,包括新生代和老年代。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。
在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。