是靠运行环境额外做了很多工作,来完成自动释放内存的操作。
主要回收堆,代码中大量的内存在堆上。
【注意】:GC不会出现"半个对象"的情况,对于这种一部分仍在使用,一部分不在使用的对象,整体来说是不释放的,等到这个对象彻底完全不使用,才真正的释放。主要还是为了让垃圾回收起来更方便,更简单。
垃圾回收的基本单位是"对象",而不是"字节"。
a、基于引用技术(不是Java中采取的方案)
针对每个对象都会额外引入一小块内存,保存这个对象有多少个引用指向他,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;当引用计数为0的时候,就不在使用被释放了。
缺点:
b、基于可达性分析(Java中采取的方案)
通过额外的线程,定期的针对整个内存空间的对象进行扫描,有一些起始的位置(称为GCRoots),会类似于深度优先遍历一样,把可以访问的对象都标记一遍(带有标记的对象就是可达的对象),没被标记的对象就是不可达,就是垃圾。
GCRoots:
可达性分析的优点:克服了引用计数的两个缺点:空间利用率低、循环引用
自身的缺点:系统开销大,遍历一次可能比较慢
找垃圾的核心就是确认这个对象未来是否还会使用,没有引用指向,就不使用了
a、标记-清除
标记就是可达性分析的过程;清除就是直接释放内存
此时如果直接释放内存,虽然内存是还给系统了,但是被释放的内存是离散的,带来的问题就是“内存碎片”
b、复制算法
"复制"算法是为了解决"标记-清理"的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使 用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后 再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配 时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。
c、标记-整理算法
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用 复制算法。 针对老年代的特点,提出了一种称之为"标记-整理算法"。标记过程仍与"标记-清除"过程一致,但后续步 骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的 内存。
d、分代算法
分代算法和上面讲的 3 种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策略, 从而实现更好的垃圾回收。这就好比中国的一国两制方针一样,对于不同的情况和地域设置更符合当地 的规则,从而实现更好的管理,这就是分代算法的设计思想。
当前 JVM 垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只 是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每 次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没 有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。