1 什么是垃圾回收
垃圾回收(Garbage Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。
1.1什么时候回收
a:System.gc()
b : 根据Eden区和From Space区的内存大小来决定。当内存大小不足时,则会启动GC线程并停止应用线程。
2 哪些内存需要回收
2.1 分析:
如果一个对象失去了他的引用,则他已死去(即再也不可能被任何途径使用的对象)
2.2 关于引用:
引用事关对象的存活与否,过去Java中引用的定义很传统,只是被引用与没有被引用2种状态。因此为了描述一些“食之无味,弃之可惜”的对象,我们引入了一下四种引用
- 强引用:Object obj = new Object(),这类引用永远不会被回收
- 软引用:用来描述还有用但是不是必须的对象,即有内存的时候放着,没内存就回收他
- 弱引用:下一次垃圾回收时肯定会被回收
- 幻影引用(虚引用 | 幽灵引用):没有任何实际作用的引用,甚至无法通过这个引用获取到对象的实例,存在的唯一目的就是为了在对象被回收时收到一个系统通知。
2.3 判断引用数目
2.3.1:引用计数算法
对象被引用一次则引用计数器+1,计数器为0的对象被视为垃圾。
无法解决循环引用的问题
2.3.2:根搜索算法
这种算法的基本思路:
从一系列的Gc Roots对象作为起始点,乡下搜索,走过的路径被称为引用链,如果一个对象与引用链没有构成关系,则这个对象被视为不可达的,会被判定为可回收的对象
标记可达对象:
由上文描述我们可以看出,Gc Root对象是至关重要的一环,那么哪些对象可以被视为Gc Root呢?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象,即方法中的局部变量
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中的(Native方法)引用的对象
是不是引用不可达就一定会被gc?
通过可达性分析法无法搜索到的对象和可以搜索到的对象。对于搜索不到的方法进行1次标记。之后会筛选是否需要执行finalize方法的对象(执行条件:1重写了finalize方法 2 尚未执行过finalize1次方法),如果不需要执行直接回收,否则执行finalize如果在执行这个方法期间与引用链建立了联系,则对象重生。
3 垃圾回收算法:
3.1 标记-清除算法
步骤:
- 标记出需要回收的对象
- 回收
优点:简单高效,不需要移动对象
缺点:效率差,留下了很多不连续的内存碎片
3.2 复制算法
将内存分为2个部分,一个部分a预留,另一个部分gc时将存活的对象全部复制到a中,并把它本身的对象全部清除。新生代使用了这种方法,但是由于一半的内存实在过于浪费,而且研究表明,98%的对象都是“朝生夕灭”的,因此在新生代的gc中,实际上是1个Eden区,与2个Survivor区(From, to),Eden:survivor为8:1,当Eden内存不足时,发生一次MinorGc,将Eden,From区域的存活者的对象存到to这个区域,当然如果to区域放不下,(这是完全有可能的)老年代将会作为内存担保,大的对象将会直接进入老年代。
3.3 标记-整理算法
- 步骤:
- 标记出需要回首的对象
- 让所有的对象都向一端移动,从而覆盖需要回收的对象
优点:操作简单只需要改变指针,无内存碎片,都是连续的内存
缺点:需要操作所有的对象,比较耗时
老年代使用这种算法,因为老年代对象较少,且存活能力较强
4 MinorGc 与 FullGc
MinorGc 发生于新生代,当Eden区域内存不足时触发
FullGc 发生于老年代
发生时机:
1 System.gc() 建议使用FullGc,但不是一定会使用
2 老年代空间不足
3 方法区空间不足
4 通过Minor GC后进入老年代的平均大小大于老年代的可用内存。由于minorgc时存活下来的对象大小是不可预估的,因此jvm会根据以往的”经验“,根据现在老年代的内存是否大于过去minorgc时需要的对象来判断是不是需要fullgc,如果需要则gc。
5 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
当老年代放不下对象了得时候,抛出OOM错误
4.1 Stop the world
VM thread在进行GC前,必须要让所有的Java线程阻塞,从而stop the world,开始标记,不管什么算法的收集器,都需要有这个标记的过程,只是时间长短的问题。这一步是非常关键的一步,关于这个部分的内容的介绍也比较少。
How Stop the world
因为gc发生的时间是不确定的, 而不同线程,不同时刻,对象的引用关系,状态时刻在变化,因此无论什么收集器,都需要在某一个时刻让所有线程阻塞下来,然后gc
相关概念:
准确式GC:
作用对象是执行栈中的方法, 即准确的描述数据的类型。
OopMap :
在某些相对耗时的特定指令位置(while, exception),会生成OopMap
SafePoint
这个特定的位置称为safePoint安全点
SafeRegon
有些指令会导致阻塞,因此引入了safe区域,到达了这个区域中,即被认为引用关系不改变
如何“跑”到安全点
抢先式中断:
先让所有线程停下,再激活尚未到达安全点的线程,直至所有线程到达安全点,开始gc
主动式中断:
对线程设立一个标志,这个标志表示是否需要中断,每到达一次safepoint则轮询一次这个标志,为真则阻塞
垃圾收集器
Serial
单线程的处理新生代的处理器,适用于在客户端中使用
Serial Old
单线程的,使用 “标记-整理” 的垃圾收集器。
ParNew
多线程版本的serial,处理新生代,只有他与serial能与CMS配合工作
Parallel Scavenge
新生代收集器,与parnew类似,但是其性能的关注点在于提高吞吐率
Paralle Old
老年代版本Parallel Scavenge
CMS
是一款并发的,使用标记-清除的针对老年代的gc,号称“几乎不需要STW(stop the world)”
step:
初始标记 : 对GC Root 进行可达性分析,需要STW
并发标记阶段是和用户线程并发执行的过程,与用户线程并发的进行Gc ROOT Tracing(根搜索)
重标记:需要STW,因为并发标记阶段用户线程是继续在运行的,因此引用关系可能发生了变化,我们需要将发生了变化的dirty 数据再次标记
标记-整理:并发的gc
缺陷:
消耗cpu资源,cpu敏感(并发程序都这样)
无法处理浮动垃圾
标记-整理算法会产生内存碎片
参考 https://www.cnblogs.com/Leo_wl/p/5393300.html
G1
最新的研究成果,特点如下:
收集过程是并发的
分代收集,g1不需要和其他收集器配合使用
空间整合 “标记-整理” 算法,没有空间碎片的
停顿可以预测 ,因为不需要对整个java堆进行全区域的垃圾收集,他将堆内存划分为了一个个的独立区域 Region,每次会尽量回收价值最大的Region