java堆
java堆从GC角度还可以细分为:新生代和老年代
新生代
新生代又可以分为eden区,from区 ,to区
用来存放新生的对象,一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收,采用复制算法
- eden
java新对象的出生地,所有刚创建的对象都会进入eden区,当Eden区不够的时候回触发MinorGC,对新生代进行一次垃圾回收 - ServivorFrom
上一次GC所未清除的对象,以及下次将被GC扫描的对象 - ServiviorTo
新生代中的MinorGC
MinorGC采用复制算法
过程:
- eden、ServiviorFrom中的对象复制到ServiviorTo区域,若对象年龄达到老年的标砖,则复制到老年代,同时对象的年龄+1
- 清空eden和ServiviorFrom区中的全部对象
- ServiviorTo和ServiviorFrom区进行互换,原ServiviorTo成为下一次GC的ServiviorFrom区
老年代
主要存放应用程序中生命周期长的内存对象,也就是年龄比较大的对象。
老年代对象比较稳定,因此FullGC不会频繁执行。
FullGC触发条件:
- 当老年代空间不够时触发
- 无法找到足够大的连续空间分配给新创建的较大对象
注意:如果执行完FullGC仍然无法给该对象分配空间,那么就会抛出异常,异常类型就是OutOfMemoryError。
FullGC
FullGC采用标记清除法
过程:
- 扫描所有老年代,标记出存活的对象
- 回收所有未标记的对象
永久代
主要存放类信息和元数据信息,GC不会在主程序运行期对永久区进行清理,所以这也导致了永久代的区域会随着加载的class增多而饱和,最终抛出OOM异常
java8中的元数据区
在java8中,永久代被移除,被称为元数据区(元空间)所取代。
区别:
- 元数据区并不在虚拟机中,而是使用本地内存。元数据区的大小受本地内存限制
垃圾回收与算法
如何确定垃圾
- 引用计数法
在java中引用和对象时关联的,如果想要操作对象则必须引用。因此可以引用计数来判断对象是否回收。如果对象引用计数为0,则可以被回收了。简单的说就是对象每次被引用,计数器+1,当引用失效后计数器-1,当计数器0时,将表示对象不被使用,垃圾回收期将回收该对象使用的内存。
优点:
引用计数器执行速度快,程序不会被长时间打断
缺点:
无法检测出循环引用。所谓循环引用就是父对象对子对象有一个引用,而子对象又反过来引用父对象,这样,他们的引用计数用不可能为0。 - 可达性分析
为了解决引用计数法的循环引用问题,java使用了可达性分析的方法,如果一个对象没有可达路径,则成该对象是不可达的。但是不可达对象不等于可回收对象,GC还会再给两次机会,回收对象经过两次标记过程变为可达对象,则不回收。
垃圾回收算法
- 标记清除法
标记需要回收的对象,在清除阶段进行清除
标记轻蹙算法根据特定的算法(如引用计数法或可达性分析等)标记处内存中哪些对象可以回收。
缺点:内存碎片化严重,效率低
- 复制算法
解决内存碎片化提出的算法。
按内存容量将其划分为等大小的两块,每次只使用其中一块,一块内存满后,将存活的对象复制到另一块上去,并进行压缩,然后清除这块内存。
问题:内存效果高,不易产生碎片,但是将内存压缩到了原来的一半 - 标记压缩算法
标记后,将存活对象一道内存一端,然后清除边界外的对象,这样就解决了碎片化严重的问题 - 分代收集算法
核心思想是根据对象存活的不同生命周期将内存划分为不同的域。将堆划分为新生代和老年代。
对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时很短,而老年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC.
注意:java中的垃圾回收机制基本上是多种算法的结合,不会单单只是用一种算法
垃圾回收过程
不可达对象并不会马上被回收,而是要经过至少两次标记的过程
- 第一次标记过的对象,会检查该对象是否重写了finalize()方法。若没有重写该方法,则加入即将回收集合,或重写了则放入另一个F-Query队列。
- 第二次标记之前,对所有对象逐个执行finalize()方法,这是对象避免自己被清除的最后手段。如果对象在执行finalize()方法时,重新与GC roots引用链相连,则第二次标记时将对象从F-Query队列清除,不会被回收。但是,这种好事不会无限发生,曾经执行过一次finalize()的对象,如果再被标记,那就只能被等待清除了
- GC将对F-Queue中的对象进行第二次小规模的标记,将队列中重新与GC Roots引用链恢复连接的对象清除出“即将回收”集合。
finalize作用
finalize()方法是在垃圾收集器清除对象之前对这个对象调用的,确定该对象却是没有被引用。它是object类中定义的,因此所有类都继承了它。
垃圾回收时的停顿现象
java进行垃圾回收时,会中断所有应用线程,目的是这样才能使系统不产生新的垃圾,保证系统装填在某一瞬间的一致性,有利于更好的标记垃圾对象。