堆的概念
堆是被线程共享的一块内存区域,创建的对象和数组都保存在Java堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域,堆从GC角度角度还可以细分为新生代和老年代
新生代
-
Eden区
Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代),当Eden区内存不够的时候就会触发MinorGC,对新生代区进行一次垃圾回收
MinorGC采用的是复制算法,因为新生代对象大量死去,只有少部分存活
将少部分存活的对象复制到copy区域后,直接将其余部分抹去 -
Survivor1区和Survivor2区
保存的是上一次垃圾回收后存活下来的对象(比如上面copy区的对象),要说明的是survivor1与survivor2之间必定有一个是空的,不用过于在意哪个是from哪个是to,都只是代号而已,将存活的对象复制到空的servivor区中,清除其他两个区,依次反复,直到有的对象的年龄达到了老年的标准(多大年龄进入老年区与垃圾回收种类有关),或者survivor的一个区放不下了,则放到老年代区对象在Survivor区每经历一次垃圾回收,年龄+1
老年代
主要存放应用程序中生命周期长的内存对象
老年代的对象比较稳定,所以MajorGC不会频繁执行,在进行MajorGC前一般都先进行了一次MinorGC,使得有新生代对象晋升到老年代,导致老年代空间不够时才触发,当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间
老年代垃圾回收算法使用的是 : Mark Compact (标记压缩)或 Mark Sweep(标记清除)
MajorGC/FullGC : 在老年代无法继续分配空间时触发,新生代和老年代同时进行垃圾回收
如何确定垃圾
-
引用计数法
在Java中引用和对象是有关联的,如果要操作对象则必须引用,那么我们就可以通过引用计数来判断一个对象是否可以回收
问题 :循环引用
对象1、2、3它们的引用计数都不为0,这就会形成一堆一堆的垃圾,最后占满整个空间 -
可达性分析
如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的,但是不可达对象不等于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程,两次标记后仍是可回收对象,则面临回收
之所以要标记两次是因为对象可以使用finalize()方法进行自救
哪些对象是根对象?
- JVM stack
- native method stack
- run-time constant pool
- static references in method area
- Clazz
GC算法
- Mark Sweep 标记清除
标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占的空间,标记清除算法会产生碎片,导致找不到连续的空间
-
Copying 复制算法
按内存容量将内存划分为等大小的两块,每次只用其中一块,当前块内存满后将尚存活的对象复制到另一块,清空当前块 ,问题是浪费空间 -
Mark Compact 标记压缩
结合上面两种算法,标记阶段和Mark Sweep算法相同,标记后不是清理对象,而是将存活的对象移到内存的一端,然后清除端边界外的对象
GC垃圾收集器
因为内存越来越大才诞生了各种不同的垃圾回收机制
常用的垃圾收集器组合
-
Serial + Serial Old
Serial 是单线程的收集器,使用的是复制算法
Serial Old 是Serial 老年代版本,同样是单线程的垃圾收集器使用标记压缩算法
当内存过大的时候,会导致垃圾回收一直在运行,业务卡死
搭配运行过程图如下
-
Parallel Scavenge+ Parallel Old
Parallel Scavenge 是多线程的收集器,使用的是复制算法
Parallel Old 是Parallel Scavenge老年代版本,同样是多线程的垃圾收集器,使用标记缩算法
搭配运行过程图如下
-
ParNew+ CMS
ParNew是Parallel Scavenge的变种,是多线程的收集器,使用的是复制算法
CMS使用多线程标记-清除算法,最短的垃圾收集停顿时间
4个阶段 :
1、初始标记
标记GC Roots能直接关联的对象,速度很快,仍会STW
2、并发标记
进行GC Roots的跟踪过程,和用户线程一起工作,不需要STW
3、重新标记
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记
记录,仍会STW
4、并发清理
清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并
发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看
CMS 收集器的内存回收和用户线程是一起并发地执行。CMS运行过程图图如下