在运行过程中没有任何指针指向的对象是垃圾。
垃圾标记阶段:对象存活判断
需要区分出内存中哪些是存活对象,哪些是已经死亡的对象,这个过程称为垃圾标记阶段。
两种方式:引用计数算法和可达性分析算法
- 引用计数算法 对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。
- 可达性分析算法,解决了引用计数算法中循环引用的问题,防止内存泄漏的发生。按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
在java语言中,GCROOTs包括以下几类元素:
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
- 方法区中静态属性引用的对象
- synchronized持有的对象
finalization 机制
作用:用于在对象被回收时进行资源回收
finalize()方法不能主动调用,原因如下:
- 在finalize() 时可能导致对象复活(这取决于对象能否与引用链取得联系)
- finalize()方法的执行时间是没有保障的,完全由gc线程决定
- 可能严重影响gc性能
一个对象的finalize()方法只会执行一次
垃圾清除阶段:清除算法
- 标记清除
从根节点开始遍历所有可达的对象,如果发现没有标记的对象,将其回收
缺点:效率不高,产生内存碎片,需要维护空闲列表
- 复制
将活着的内存空间分为2块,每次只使用其中的一块,在垃圾回收时将活着的对象复制到未被使用的内存块中。
优点:简单高效,没有碎片
缺点:需要2倍的内存空间
特别适用于垃圾对象很多,存活对象很少的情景,如young区的s0 和s1
- 标记压缩
第一阶段和标记清除算法一样
第二阶段将所有的存活对象压到内存的一端,按顺序排放。
优点:没有内存碎片,没有内存减半
缺点:效率低,需要移动对象。
指针碰撞:如果内存已用和未用的部分各占一边,当新对象分配内存时,只需要修改指针到第一个空闲位置上。
针对不同生命周期的对象可以采取不同的收集方式。
System.gc()
提醒 jvm的垃圾回收器执行gc,但是不确定是否马上执行gc.
stop the world
gc事件发生过程中,会产生应用程序的停顿。
safepoint 安全点
程序执行时并非在所有地方都能停下来gc,只有在特定位置才开始gc,这些位置被称为检查点。
引用
- 强引用 引用赋值,只要强引用关系存在,垃圾回收器就永远不会回收掉被引用的对象。
- 软引用 在即将发生内存溢出时,将这些对象列入垃圾回收器进行2次回收。如果这次回收之后还没有足够的内存,才会抛出异常
- 弱引用 被弱引用关联的对象只能生存到下一次垃圾回收之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收。
- 虚引用 在对象被收集器回收时收到一个系统通知。
垃圾回收器分类
串行与并行回收: 在同一个时间段内只允许有一个CPU用于执行垃圾回收工作
并发式与独占式: 并发式垃圾回收器与应用线程交替工作,以尽可能减少应用程序停顿的时间。独占式垃圾回收器:一旦运行,就停止应用中所有用户线程。
按碎片的处理方式划分:压缩式和非压缩式
按工作的内存区间划分:年轻代回收器和老年代回收器。
评价gc的性能指标
吞吐量:运行用户代码的时间占总运行时间的比例
暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
内存占用:java堆区所占的内存大小
对于一个交互时程序低暂停时间是非常必要的。但是会频繁地执行垃圾回收。
7款经典的垃圾回收器
串行垃圾回收器:Serial、Serial old
并行回收器:ParNew,Parallel Scavenge 、Parallel Old
并发回收器:CMS、G1
垃圾收集器与垃圾分代的关系
新生代收集器:Serial, ParNew, Parallel Scavenge
老年代收集器:Serial Old,Parallel old ,cms
整堆收集:G1
Serial回收器
采用复制算法,串行回收,stw。只会使用一个CPU或一条收集线程去完成垃圾收集工作。在进行垃圾回收时,必须暂停其他所有线程。
简单而高效,限定单核CPU。
Serila Old 与 Serial 配合工作
Parnew回收器
是Serial回收器的多线程版本。采用并行回收的方式,复制算法。
对于新生代,回收次数频繁,使用并行方式高效。但是在单个CPU的环境下,ParNew收集器并不比Serial收集器高。
对于老年代,回收次数少,使用串行方式节省资源。
除了Serial外,目前只有Parnew gc 能与 CMs配合工作。
Parallel Scavenge 回收器
吞吐量优先,复制算法,并行回收,stw机制
适合在后台运算而不需要太多交互的任务。
采用标记压缩算法,并行回收和stw机制。
新生代采用复制算法,暂停所有用户线程。老年代采用标记压缩算法。
在吞吐量优先的场景中,Parallel 一般和 Parallel old 组合。java8默认是此收集器。
CMS 回收器
实现了让垃圾收集线程与用户线程同时工作。采用了标记清除算法。
- 初始标记 标记出GC Roots能直接关联到的对象,stw,速度非常快
- 并发标记 遍历整个对象图,耗时较长,但是不需要停顿用户线程
- 重新标记 修正并发标记期间因用户线程执行而导致的标记产生变动的对象,stw
- 并发清除 删除已经标记的死亡的对象,释放内存
由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。
cms 算法采用的是标记清除算法,会产生内存碎片
优点:并发收集,低延迟
缺点:
- 产生内存碎片
- 并发阶段占用一部分线程导致用户程序变慢总吞吐量降低。
- 产生浮动垃圾 在重新标记阶段会重新判断已经标记为垃圾的对象是不是垃圾。无法判断原来不是垃圾的对象会不会变成垃圾。
为什么不使用标记压缩算法?
采用压缩算法会影响用户线程的执行
G1回收器
它是一个并行回收器,把堆内存分为很多不相关的区域,每次根据允许收集的时间,优先回收价值最大的region。是jdk9以后的默认垃圾回收器。
特点:
空间整合:G1 将内存划分为一个个region。内存的回收是以region为基本单位的,region之间是复制算法,但整体上可以看作标记压缩算法。
可预测的停顿时间模型:G1跟踪region里面垃圾堆积的价值大小,在后台维护一个列表,每次根据允许收集的时间,优先回收价值最大的region
缺点:占用内存和负载高
G1在大内存上可以发挥优势,适用于大内存,多处理器的机器。
化整为零:G1将java堆划分为2048个region块。 所有region大小相同,但年轻代与老年代不再物理隔离。G1还设置了humongous区域存储大对象。
G1回收器垃圾回收的过程:
- 年轻代GC
- 年轻代 GC+并发标记过程
- 混合回收
Remember Set 记忆集:
用来解决一个对象被不同区域引用的问题。
一个region 区域不可能是孤立的。其中的对象有可能被其他区域所引用。如果全部扫描非常浪费资源。于是出现了Remember Set。
每个region都有一个Remember Set。这样在扫描时可以保证不进行全局扫描也不会有遗漏。