什么是GC垃圾回收
gc( garbage collection) : 即垃圾收集, 是指 JVM 用于释放那些不再使用的对象所占用的内存。
什么时候出发GC
手动出发:当前程序去调用System.gc()的时候触发。注意:GC回收的时候程序会停止运行,同时回收的过程中,会消耗大量的系统性能,所以一般情况下我们不会主动去调用gc回收方法。
手动触发:根据Eden区和From和Space区的内存大小来决定的。当内存大小不足是,则会启动GC线程并停止应用线程。
程序再次申请内存的时候,系统(jvm)发现内存不足,这个时候就会触发GC。
GC回收的算法
首先标记清除
分为两个步骤,第一个步骤就是标记,也就是标记处所有需要回收的对象,标记完成后进行统一的回收掉那些带有标记的对象。这种算法优点就是简单,缺点就是效率问题,还有一个最大的缺点就是空间问题,标记清除之后会产生大量不断的内存碎片,当程序在以后的运行过后的运行过程需要分配比较大对象的时无法找打足够的连续内存而造成内存空间浪费。
标记复制
复制完将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还活着的对象复制到另外一个上面,然后再把已使用过得内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。只是这种算法的代价是将内存缩小到原来的一半。
标记整理
标记整理标记整理算法与清除算法很相似,但是最显著的区别是:标记清除算法仅对不存活的对象进行处理,剩余存活对象不做任何处理,造成内存碎片;而标记整理算法不仅对不存活对象进行处理清除,还对剩余的存活对象进行整理,程序整理,一次其不会产生内存碎片。
分代收集
新生代->老年代->永久代
新生代
新生代是用来存放新生的对象。一般占据堆的三分之一空间。由于频繁创建对象,所以新生代会频繁触发MinorGc进行垃圾回收。新生代又分为Eden区、ServivorFrom、ServivorTo三个区。
Eden区
Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。
当Eden区内存不够的时候回触发MinorGC,对新生代进行一次垃圾回收
ServiorFrom
上一次GC的幸存者,作为这一次GC的被扫描者
ServivorTo
保留了一次MinorGC过程的幸存者
MinorGC的过程(复制 - 清空 - 互换)
MinorGC采用复制算法
老年代
主要是用来存放应用程序中生命周期场的内存对象
老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC钱一般都会进行一次MinorGC,使得有新生代的对象晋升入老年代,导致空间不够用时才触发。当无法找到足够的连续空间分配给新创建的较大的对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
MajorGC采用标记清除算法:首先扫描一次所有老年代,标记处存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描在回收。MajorGC会产生内存碎片,为了减少内存消耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也装满了装不下的时候,就会抛出OOM异常
永久代
指定内存的研究保存区域,主要是存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域,他和存放实例的区域不同,GC不会在主线程运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增加而胀满,最终抛出OOM异常
新生代:尽可能快速收集回收掉生命周期短的对象,一般情况下新创建得到对象都放入到新生代中。
老年代:老年代中对象的生命周期比较长。新生代经过N次回收以后,任然存活的对象。
永久代:主要是存放于静态文件,如:java类,方法。
新生代中的对象如何向老年代过度
新生代分三个区域:eden,survivor0,survivor1三个区域,大量的对象都在eden区域,在gc的时候,先将eden区存活的对象复制到survivor0区域,然后清空了eden。当survivor0区域满了以后,则将eden和survivor0区域中存活的对象复制到survivor1区域。然后清空eden和survivor0区域。把survivor1中的数据全部复制到survivor0区域中。经过反复的gc,当survivor1区域不足以放下eden和survivor0区域中的存活对象,就直接将存活的对象放入到老年代。
老年代中因为对象效率存活高、没有额外控件对他进行分配担保,就必须对标记-清除或者标记-整理。
注意在jdk8的时候java废弃了永久代,但是并不意味着我们以上的结论失效,因为java提供了与永久代类似的叫做“元空间”的技术。