内存溢出和内存泄漏
渣男的三个例子
由直接内存导致的内存溢出, 一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况, 如果读者发现内存溢出之后产生的Dump文件很小, 而程序中又直接或间接使用了DirectMemory(典型的间接使用就是NIO) , 那就可以考虑重点检查一下直接内存方面的原因了。
垃圾收集器
Serial收集器
是Hotspot在client模式下的默认垃圾回收器。新生代采用的是SerialNew,复制算法,老年代采用的是SerialOld,标记整理算法。都是单线程工作模式。优点是实现简单,单线程模式下较高效。
ParNew/CMS收集器:
CMS:关注用户停顿时间
ParNew采用的复制算法,使用多线程并行的方式可以提高处理速度,CMS采用的是单线程的标记清理算法,而且老年代回收次数较少,使用单线程减少线程切换的资源消耗。
CMS的垃圾回收过程分为4个阶段:初始标记,并发标记,重新标记,并发清理。
- 初始标记:进入STW,标记GCroots能直接关联的对象。
- 并发标记:不用STW
- 重新标记:(多线程)进入STW,修改并发标记时程序执行导致的记录产生变动的标记
- 并发清理:删除标记阶段判断已经死亡的对象;(由于不用进入STW,可以和用户线程并发)。
在清理期间,如果内存不够(清理线程和用户线程并行),会触发Concurrent Mode Failure,启用SerialOld进行垃圾回收
优点:
- 并发收集
- 低延迟
缺点:
- Parallel Scavenge工作在新生代,采用复制算法,采用多线程并行回收。
- Parallel Old工作在老年代,采用标记整理算法,采用多线程并行回收的方法
G1垃圾回收器
- 为什么叫G1:垃圾回收的时候优先回收垃圾多的Region
- G1特点:将堆空间划分为等大的Region,单个大小约为2的整数次幂M,每个Region可以视为伊甸园区、survival区等区域,还有一个H区域,当对象大于0.5个Region时就存放到H区。在区域之间的垃圾回收算法可以视为:复制算法,整体可以视为标记整理算法。
- GC流程:
- 年轻代GC:并行独占式收集,将Eden区的存活对象移动到Survivor区或者老年区。
- 老年代并发标记过程:堆内存使用超过45%时启动。
- 混合回收:年轻代区域和老年代区域一同回收。同时老年代只收集部分老年代Region。
- FullGC(如果需要,作为保底机制存在)
首先分配Eden区的region,垃圾回收之后会产生Surivivor区。之后可能Survivor区直接升级为Old区。
- RememberSet:主要是解决一个对象被不同区域引用的问题,为了避免全堆扫描,每个区域都有一个RememberSet,在写入引用对象的时候使用写屏障。检查引用对象和被写入对象是否在同一个region,如果不在同一个region,就把相关引用信息写到被引用对象的rememberSet里面。这样做可达性分析的时候,就可以直接读取RSet,避免全局扫描。
三色标记法
用于标记出哪些对象是存活的,哪些对象是垃圾。
将对象分为三种颜色:
- 一开始所有对象都在白色集合中。
- 初始标记:标记GCRoots直接相连的对象,将这些对象移到灰色集合中
- 之后就处理灰色集合,将所有引用的节点移到灰色集合中,然后将当前节点移到黑色集合。
- 重复步骤三,直到灰色集合为空。
- 结束后,仍未白色集合的对象即为GCRoots。
多标
标记期间,原本可达的对象变为不可达,但是之后的节点可能已经进入灰色或者黑色节点。
这些后面的节点就是浮动垃圾,这些只能在下一次标记中被发现。
还有并发标记之后开始的新对象,通常的做法是全部标为黑色,也算浮动垃圾。
漏标-读写屏障
标记期间,在黑色节点中又新增了引用,同时灰色对象中没有指向新对象的引用,导致无法被发现,出现漏标。
CMS采用写屏障+增量更新,在向黑色节点新增节点的时候,使用AOP的方法,新增GCROOTS指向该新增节点。
然后在重新标记的时候采用增量更新,扫描新增的GCROOTS解决漏标问题。