Java虚拟机--垃圾收集算法
标记-清除 算法
算法分为"标记"和"清除"两个阶段,首先标记出需要回收的对象。然后统一回收掉之前被标记的所有对象。它是最基础的收集算法,后续的收集算法都是基
于这种思想并对其缺点进行改进而产生的。
主要缺点:
1. 执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行
效率都随对象数量增长而降低;
2. 内存空间的碎片化问题,标记、清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找到
足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记-复制 算法
将可用内存按容量划分为两块,每次只使用其中的一块,当内存使用完了后,就将还存活着的对象复制到另外一块上面,然后在把前面一块内存一次性清理掉。
优点 :
每次只操作一块内存,分配内存的时候无需考虑内存碎片的情况,只需要移动对象的指针,按顺序分配内存即可,实现简单,运行高效。
缺点 :
1. 会将内存缩小为原来的一半。
2. 对于存活率较高的对象,就会对其进行多次复制,从而导致效率降低。
标记-压缩 算法
和标记-清除算法一样,只不过标记后的动作不是清除,而是将所有对象向一端移动,然后直接清理掉边界以外的对象(被标记的对象)。
分代收集 算法
把java的堆分为"新生代"和"老年代",对于不同的年代采用不同算法。
在新生代中,由于对象生命周期非常短暂,所以每次垃圾回收的时候都会有大量的对象死去,只有少量存活,这样,采用"复制算法",就只需要付出少量存活对
象的复制成本,就能完成回收。
在老年代中,由于对象生命周期比较长,存活率较高,没有额外的空间对它进行分配和担保,那就必须使用"标记-清除算法"或者"标记-压缩算法"来进行回收。
Minor GC: 从年轻代空间(包括Eden和其中一个Survivor区域)回收内存被称为Minor GC
Major GC: 清理老年代
Full GC: 清理整个堆空间—包括年轻代和老年代
年轻代: 是所有新对象产生的地方。年轻代被分为3个部分(Enden区和两个Survivor区,也叫From和To)
在1989年,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策略,现在称为“Appel式回收”。HotSpot虚拟机的Serial、
ParNew等新生代收集器均采用了这种策略来设计新生代的内存布局。Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的
Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空
间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整
个新生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会被“浪费”的。当然,98%的对象可被回收仅
仅是“普通场景”下测得的数据,任何人都没有办法百分百保证每次回收都只有不多于10%的对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”
的安全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(际上大多就是老年代)进行分配担保(Handle
Promotion)。
经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间,年轻代有资格提升到年老代是通过设定年龄阈值来控制的,需要注意,Survivor
的两个区是对称的,没先后关系,from和to是相对的。
老年代: 在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中,都是生命周期较长的对象。对于年老代,则会执行 Major GC 来清理。
在某些情况下,则会触发Full GC,来清理整个堆内存。
元空间: 堆外的一部分内存,通常直接使用的是系统内存,用于存放运行时常量池等内容,垃圾回收对应元空间来说没有明显的影响。