在Java中,通过可达性分析法来确定哪些垃圾是可以被回收的,然后就轮到我们的垃圾收集器开始进行收集了,由于Java虚拟机并没有规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,以下是一些常见的垃圾收集器的核心思想
标记清除(Mark-Sweep)是最基础的一种垃圾回收算法,他分未两个部分,即 标记 和清除,先把内存区域中的这些对象进行标记,哪些属于需要回收的标记出来,然后再执行我们的清除,将这些对象清除,清理掉的垃圾就变成了未使用的内存区域,等待再次被使用
这中逻辑简单清晰,并且操作也很简单,但是这个算法却有个很大的问题即使内存碎片的产生
假设上面的框框小的为 1M 大的为2M,当我们回收完后,内存就会被切成很多段,当我们开辟空间时,需要的时连续的内存当我们需要一个3M的内存时,上面的回收后产生的内存我们时无法分配,这样下去,只会让我们无法使用的内存无法使用,即内存碎片越来越多
这算法也是在标记清除算法的基础上演化而来的,解决效率与碎片的问题
它将内存按容量分为大小相同的两块,每次只是使用其中的一块,当一块内存快用完了之后,就将该内存上还活着的对象复制到了一块内存,然后再把已使用过的内存全部清理掉,这样保证了内存的连续,就不再产生碎片了,且运行高效
这个放发虽然不会产生碎片,但是从图就可以看出来,这个算法代价也太大了吧,足足一半没法使用,合着我们买了200平米的房子,只能住100平米啊
标记整理算法(Mark-Compact)标记的过程仍然和标记–清除算法一样,但是后续的不是直接回收,而是将存活的对象都向一段移动,然后直接清理掉端边界以外的内存
标记整理算法一方面在标记清除算法上做了升级,解决了内存碎片的的问题,也规避了复制算法只能利用一半内存的弊端
这个算法看起来美好,但是从我们的图中可以看出,它对内存变动更频繁。需要整理所有存活对象的引用地址,在效率上比复制算法要低得多
在严格的意义上来说,这不是一种思想或者理论,而是融合了前三种基础的算法思想,而产生了针对不同情况使用不同的算法
根据对象存活周期的不同将内存划分为不同的几个区域,一般是把Java堆划分为新身代与老年代,这样根据各个年代的特点而采用不同的算法,在新生代中,每一次的垃圾回收都会有大批的对象死去,只有少量的存活,这个时候就可以使用我们的复制算法,只需要付出少量存活对象的复制成本就能完成收集;而老年代中,对象的存活率较高,没有额外的空间对其进行担保,就应该采用标记清理或者标记整理的算法来回收了
Java堆主要分未老年代与年清代两个区域,且老年代大约占三分之二,新生代占三分之一,而新生代又分未Eden和Survivor,Survivor又分未From与To,三则占比 Eden:From:To = 8:1:1;
IBM公司专门研究过了有将近98%的对象都是朝生夕死的,于是针对这一现状,大多数情况下,对象会在新生代Eden区中进行分配,当Eden区没有足够空间分配时,虚拟机就发动一次Minior GC,Minior GC操作更频繁,回收速度也快,通过Minior GC后,Eden就会被清空了,还活着的就进入到From了,如果From不够,就只能进入老年代了
Survivor就相当于Eden与老年代的缓冲区,而Survivor又分未From与To区每次执行Minior GC后,会将Eden和From活着的对象放到To区(如果不够就直接进入老年代吧)
为什么需要Survivor区,且还分为From与To区?
以为Minior GC后,有些应该清除的对象还没有清除干净,而是等到下一波,这时如果直接放入我们的老年代,明显不是一个明智的选择,Survivor的预筛选保证只有经历了16次的Minior GC的对象才能放入我们的老年代,而还分为From与To,这是来解决我们的碎片问题的,如果只有一个区,则放到Survivor后,其中有一些其实也是要清除的,这是就只能使用标记清清除算法了,于是就产生了碎片了,如果分两块,那么就可以将对象复制到了一块了,当第二次的清除时,From与To指责兑换,这样反复的兑换就可以了
老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记 — 整理算法
大对象
大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。当你的系统有非常多“朝生夕死”的大对象时长期存活对象,虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中每经历一次 Minor GC,年龄就增加1岁。当年龄增加到15岁时,这时候就会被转移到老年代。当然,这里的15,JVM 也支持进行特殊设置。虚拟机并不重视要求对象年龄必须到15岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的总合大于 Survivor 空间的一半,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。