整理自以下内容
Jvm 自动垃圾回收在堆和方法区中应用,其他的内存区域,比如程序计数器,虚拟机栈,本地方法栈随代码和执行而出栈,进栈,无需特意进行内存管理。
垃圾收集器概览
- Serial 收集器:历史最悠久,单线程工作,回收垃圾时,必须暂停用户进程,采用复制算法;
- ParNew收集器:本质为 Serial 收集器的多线程版本,采用复制算法;
- Parallel scavenge:具备自使用调节功能,以提供最合适的暂停时间和吞吐量,采用复制算法;
- Serial old 收集器:是Serial 收集器的老年代版本,同样为单线程,但采用的是“标记-整理”算法;
- Parallel old 收集器:Parallel scavenge 收集器的老年代版本,多线程,采用的是“标记-整理”算法;
- CMS 收集器:即Concurrent Mark Sweep收集器,以获取最短停顿时间为目标,采用“标记-清除”算法;
- G1 收集器: 即Garbage-First收集器,是目前最新的收集器,采用与其它收集器完全不通的设计思想,历时十年才实现商用,采用了混合算法,兼有“复制”和“标记-整理”算法的特点;
jvm 参数指定:
- -XX:+UseSerialGC,虚拟机运行在Client模式下的默认值,Serial+Serial Old。
- -XX:+UseParNewGC,ParNew+Serial Old,在JDK1.8被废弃,在JDK1.7还可以使用。
- -XX:+UseConcMarkSweepGC,ParNew+CMS+Serial Old。
- -XX:+UseParallelGC,虚拟机运行在Server模式下的默认值,Parallel Scavenge+Serial Old(PS Mark Sweep)。
- -XX:+UseParallelOldGC,Parallel Scavenge+Parallel Old。
- -XX:+UseG1GC,G1+G1。
哪些对象需要回收?
当然是不会被使用的对象需要回收,那么如何确定哪些对象不会被使用?
我们没有办法通过分析静态代码来确定一个对象会不会在之后被使用到,需要一个对象不会被使用的充分条件:不存在一个指向对象的引用。引用计数法和可达性分析都是基于是否还存在对一个对象的引用判断一个对象是否应该被回收。
4 类引用
Java 中的引用依据垃圾器不同的回收策略分为 4 类:
- 强引用:我们在代码中创建的引用变量大部分属于强引用,只要有对一个对象的强引用,这个对象不会被回收
引用计数法
计算每个对象的引用的数量,如果一个对象的引用数量为 0,那么意味着当前对象已经不会被使用了,可以被回收,同时这个对象可能还持有对其他对象的引用,那么在回收这个对象后,需要更新其引用的对象上对引用的计数。
这会出现一个问题,即两个对象循环引用的话,那么这两个对象都无法被回收。
可达性分析
通过一些称为“GC Root”的对象集合作为初始节点,递归的搜索出所有的引用,如果在搜索结束之后一个,引用的结果集中没有一个对象的引用,那么就表示无论无何都不可能得到这个对象的引用,这个对象需要被回收。
Jvm 中固定作为 GC Root 的有:
- 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等
- 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量
- 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用
- 在本地方法栈中JNI(即通常所说的Native方法)引用的对象
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器
- 所有被同步锁(synchronized关键字)持有的对象
- 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
根据垃圾回收器的不同,和不同的垃圾回收(比如针对老年代和新生代的垃圾回收)GC Root 中还会包括其他的一些区域。
根节点枚举
可达性分析算法耗时最长的查找引用链的过程已经可以做到与用户线程一起并发,但是所有收集器在根节点枚举这一步骤时都是必须暂停用户线程的。
当用户线程停顿下来之后,其实并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到哪些地方存放着对象引用的。在HotSpot 的解决方案里,是使用一组称为 OopMap 的数据结构来达到这个目的。一旦类加载动作完成的时候, HotSpot 就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。
垃圾回收算法
分代收集理论
- 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
- 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
- 跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极 少数。
标记清除
首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
问题:
- 堆空间碎片化
- 执行效率不稳定,执行时间随对象数量正比上升
标记复制算法
它将可用内存按容量划分为多个内存块(比如 1 个 80 % 的 eden,2 个 10% 的 survivor),每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块(可能还有一块作为分配担保,在一块内存区域无法保存所有对象时)上面,然后再把已使用过的内存空间一次清理掉。
标记整理算法
的标记过程仍然与“标记 - 清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端动,然后直接清理掉边界以外的内存。
是否移动对象
- 移动对象(标记整理):如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须全程暂停用户应用程序才能进行
- 不移动对象(标记清除):弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决,内存的访问是用户程序最频繁的操作,甚至都没有之一,假如在这个环节上增加了额外的负担,势必会直接影响应用程序的吞吐量。
HotSpot 虚拟机里面关注吞吐量的 Parallel Scavenge 收集器是基于标记-整理算法的,而关注延迟的CMS收集器则是基于标记-清除算法的。(CMS收集器面临空间碎片过多时,再采用标记-整理算法收集一次,以获得规整的内存空间)
continue:

