垃圾回收相关算法
垃圾标记阶段(判断对象存活): 引用计数算法、可达性分析算法
清除阶段(进行垃圾回收):标记-清除算法、复制算法、标记-压缩算法
引用计数算法
对每一个对象保存一个整型的引用计数器属性,用于记录对象被引用的情况。
优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。
缺点:增加了存储空间的开销。增加了时间开销。无法处理循环引用的情况。
可达性分析算法(根搜索算法、追踪性垃圾收集)
通过一系列的"GC roots"对象作为起点搜索。可以有效解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
“GC Roots”根集合就是一组必须活跃的引用。
在Java语言中,GC Roots包括以下几类元素:
1.虚拟机栈中引用的对象
2.本地方法栈内JNI(本地方法)引用的对象
3.方法区内常量引用的对象
4.所有被同步锁synchronized持有的对象。
标记-清除(Mark-Sweep)算法
一种非常基础和常见的垃圾回收算法。
标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
清除:Collector对堆内存从头到尾进行线性遍历,如果发现某对象在其Header中没有标记为可达对象,则将其回收。
缺点:效率不高;进行GC时候,需要STW,用户体验差;产生内存碎片,需要维护一个空闲列表。
复制算法
核心思想:空闲内存空间分为两块。每次只使用一块,进行垃圾回收时候将正使用的内存中的存活对象复制到未被使用的内存块中,之后清除正使用的内存块中的所有对象。最后进行垃圾回收。
优点:运行效率高;不会出现“碎片”问题。
缺点:需要两倍内存空间;
标记-压缩(整理)算法
执行过程:第一阶段和标记-清除算法一样,从根节点开始标记所有被引用对象
第二阶段将所有的存活对象压缩到内存的一端,按顺序排放,之后清理边界外所有的空间。
优点:消除了标记-清除算法当中内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。消除了复制算法当中内存减半的高额代价。
缺点:从效率上说,低于复制算法;移动过程中,需要全程STW。
分代收集算法
基于一个事实:不同的对象的生命周期是不一样的。不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
目前几乎所有的GC都是采用分代收集算法执行垃圾回收的。其核心思想是根据对象存活的不同生命周期将内存划分为不同的域。
分代的思想被现有的虚拟机广泛使用,几乎所有的垃圾回收器都区分新生代和老年代。
增量收集(Incremental Collecting)算法
基本思想:每次垃圾回收时,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复直到垃圾收集完成。
增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成工作。
缺点:线程切换和上下文切换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
分区算法
根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间。从而减少一次GC所产生的的停顿。
分区算法将整个堆空间划分成连续的不同小区间region。每个小区间独立使用、独立回收。这种算法的好处是可以控制一次回收多少个小区间。
内存溢出(OOM)
一般情况下不太容易出现OOM的情况。
当没有空闲内存,并且垃圾收集器也无法提供更多的内存时。容易出现OOM
内存溢出出现的原因:
1.Java虚拟机的堆内存设置不够。
2.代码中创建了大量的大对象,并且长时间不能被垃圾收集器收集(存在被引用)
在抛出OOM之前,通常垃圾收集器会被触发,尽其所能清理出空间。
内存泄漏(Memory Leak)
严格来说,只有对象不会再被程序用到了,但是GC又不能回收它们的情况,才叫内存泄漏。
一些不太好的实践会导致对象的生命周期变长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”。
举例:
1.单例模式
单例的生命周期和应用程序是一样长的,所以在单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的。则会导致内存泄漏。
2.一些提供close的资源未关闭导致内存泄漏。数据库连接、网络连接等