1、什么是垃圾回收?
“垃圾”是指在内存堆中长时间未使用或者已经死亡了的数据;
“回收”是指将该数据从内存堆中进行移除。
2、垃圾回收存在的意义是什么?
内存是我们程序运行的载体,如果这个载体出现多无效数据、超负荷甚至是溢出(即内存泄漏)的话,那么我们的程序运作是会严重受限甚至是出错乃至终止等等,因此我们需要进行一定程度的垃圾回收,以便尽可能的保证内存堆中的数据是高效有存在意义的,保障内存的可持续存在。
3、如何进行“垃圾”的确定呢?
虽然我们已经知道内存中的一些不可用数据是需要移除的,那我们又应该如何去确定哪些数据是不可用的,即哪些数据是“垃圾”?以下分享垃圾回收机制中确定“垃圾”的两种方法。
引用计数法
在Java中,引用与对象是有关联的,即对象在内存中会分配一个记录该对象被引用次数的存储空间,简单来说就是该对象在程序中被引用一次,则引用次数就加一。因此,当该对象的引用次数未0时,说明该对象可为可回收对象。
可达性分析法
该方法的存在一定程度上解决了引用计数法中循环引用的问题。该方法呢是通过一系列的“GC roots”对象作为起点去搜索和其他对象之间有没有可达的路径,若不可达,则为可回收对象。同时,在此也需注意,不是说可回收对象一被确认就一定会被回收,而是需要经过两轮的标记过程才会被回收。
4、垃圾是如何进行回收的呢?
在垃圾回收机制中,有四种回收垃圾的方法,如下:
标记清楚算法
该方法是最基础的算法,主要为 标记 和 清楚 两个阶段。即标记可回收对象,清楚可回收对象。
如图,该方法的一个缺点即是内存碎片化严重,当一个对象所需内存较大时,内存由于碎片化严重可能会出现无法分配对象内存的情况。
复制算法
该方法一定程度上解决了标记清楚算法中产生的内存碎片化的现象的问题。该方法的实现原理主要是将内存分为等大的两片区域。即将当前需要的对象的内存片分配到同一片1/2区域中,当该1/2区域的内存快满时,则将不可回收对象完整复制到另一个1/2区域中,之后将原先的1/2区域的所有对象移除。
如图,可以看出该方法很好的解决了内存碎片化的问题,但与此同时也产生了另一个问题,就是在内存的使用效率上降低了1/2,这对于我们进行程序运行来说是很不友好的,而且内存的空间也是我们计算机性能的一个体现。
标记整理算法
该方法是较为完美的解决了上面两个算法的问题,当然也是上面两个方法的另一种事项方式。即不断的将可回收对象进行内存移除,同时将不可回收对象进行同一极端移动,从而保证各个对象之间是一直紧凑一起的。
分代收集算法
分代收集法是目前大部分JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(YoungGeneration)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
新生代与复制算法
目前大部分JVM的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照 1: 1来划分新生代。一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另块Survivor 空间中。
老年代与标记复制算法
而老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法
1、JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 cass 类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。
2、对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。
3、当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC,进行 GC后,EdenSpace 和From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 FromSpace 进行清理。
4、如果 To Space 无法足够存储某个对象,则将这个对象存储到老生代。
5、在进行 GC后,使用的便是 Eden Space 和 To Space 了,如此反复循环。
6、当对象在 Survivor 区躲过一次 GC 后,其年龄就会+1。默认情况下年龄到达 15 的对象会被移到老生代中。