jvm垃圾回收机制: Java语言无需开发人员手动参与内存的分配与回收,得益于垃圾回收机制,自动内存管理机制,使得程序员更加专注于业务代码的开发
存在的问题:在实习编程过程中,会遇到OOM,要排查并解决问题,我们要真正了解jvm是如何管理对象。
回收区域
对于年轻代,老年代,全堆和方法区
从回收频率上讲:年轻代>老年代>方法区
垃圾回收相关算法
垃圾回收分为两个阶段:垃圾标记阶段,垃圾回收阶段
垃圾标记阶段:垃圾回收前要判断哪些对象为垃圾对象,才能之后进行回收
对象存活的判断:这里一般有两种算法
1.引用计数法: 每有一个引用指向该对象,则引用计数器会+1,当引用失效时引用计数器-1,当引用计数器为0时,表示该对象不再使用,被认为是垃圾。
优点: 实现简单,垃圾对象便于识别;判定效率高,回收没有延迟性
缺点:
需要单独的字段存储计数器,增加了存储空间的开销。
每次随着加法与减法的操作,无疑增加了时间的开销。
**最严重的问题时无法解决循环依赖的问题,导致在垃圾回收器中很少使用该算法。
public class TestGC{
object reference=null;
public static void main(String[] args){
TestGC obj1=new TestGC();
TestGC obj2=new TestGC();
//循环依赖
obj1.reference=obj2;
obj2.reference=obj1;
obj1=null;
obj2=null;
//执行垃圾回收
System.gc();
}
}
在上述代码中虽然最后将两个引用都置为null,即引用指向两个对象,本该引用计数器都应为0,标记为垃圾.但是由于代码中的循环依赖,两个对象的reference引用还在只想两个方法,则他们的引用计数器会大于0,导致不能被回收,严重的情况下可能会发生OOM。
2.可达性分析算法 (跟搜索算法,追踪性垃圾收集算法):以根对象集合(GC Roots)为起始点,从上至下的搜索方式搜索被根对象连接的目标对象是否可达。 在可达性分析算法后,存活的对象直接或间接的于根对象相关联,如果一个对象不与根对象相连,则会被标记为垃圾进行回收。只有跟根对象直接或间接相连的对象才是存活的对象。
哪些对象能够成为GC Roots对象?
1.虚拟机栈中引用的对象
2.方法区中类静态属性所引用的对象
3.方法区中类静态属性引用的对象
除了一些固定的集合以外,根据所选择的垃圾回收器及内存区域的不同,还可以会有一些其他的对象加入
垃圾回收阶段:当确定哪些对象垃圾对象之后,接下来就会进行垃圾回收,释放掉内存中的空间,以便为新的对象分配内存。
常用垃圾回收算法:1.标记-清除算法 2.复制算法 3.标记压缩算法 4.分代手机算法 5.增量手机算法 6.分区算法
标记清除算法:
执行过程:
- 标记:从根对象开始遍历,标记所有被引用的对象,一般在对象的Healder中记录为可达对象
- 清除:重新从根对象进行便利,如果发现对象中的Headler中没有被标记为可达对象,将其回收。
缺点:
- 效率不高
- 在进行GC时,需要停止整个应用程序。
- 清理出来的内存空间是不连续的,产生内存碎片
复制算法
核心思想:将内存空间分为两个部分from区和to区,每次只使用其中一块(from区),将活着的对象复制到没有使用的内存块中(to区),之后清除正在使用的内存块(from区)中所有的对象。之后from区变为to区,to区变为from区,转移内存的方向总是从from区到to区。
优点:
- 无标记和清除阶段,运行高效
- 保证了空间的连续性,不会出现内存碎片。
缺点:
- 需要2倍的内存空间。
应用场景:在要回收的对象很多,存活的对象很少,需要转移的存活对象也比较少。新生代中经常发生。
标记-压缩算法
复制算法在存活对象少,垃圾对象多的时候适用,比如在年轻代,但是在老年代中存活对象多,垃圾对象少,不适合用复制算法。因而出现了标记压缩算法
执行过程:
第一阶段和标记清除算法一样,从根对象向下寻找所有存活对象。
第二阶段将存活的对象移动压缩的内存的一端,之后清理边界外的区域。
与标记清除算法相比,相当于标记清除后将存活的对象移动到一边,这样会减少内存碎片的产生
指针碰撞:经过标记-压缩算法后,内存区域被严格的分为存活区与非存活区。临界点创建一个指针,如果要分配对象,将指针往非存活区的方向移动一个对象的距离,这种分配方式就是所谓的指针碰撞。
优点:
- 不会产生内存碎片
- 与复制算法相比,充分利用了内存的空间
缺点:
- 效率比标记整理算法要低一些
- 移动对象,还用将引用的地址告知引用,修改引用地址
- 移动过程中,要全程停止应用程序
分代算法
核心思想:分代指的是区分不懂的内存区域(年轻代Young Gen,老年代Tenured Gen).上面的三个算法都有明显的优缺点。衍生出了分代算法。即不同区域采取不同的回收算法。
在Java运行时 ,会产生大量的对象,有大量的对象与业务相关,如session对象,线程,socket连接对象等等,这些都是生命周期比较大的对象。还有一些对象时临时的变量,这些对象寿命周期都非常短,比如String类型的变量。
目前绝大部分的GC 都使用分代算法。
年轻代:复制算法的速度是最快的
老年代:该区域存在大量存活率高的对象,一般由标记-清除算法与标记-整理算法混合使用。
增量收集算法
上诉的算法,在垃圾回收的过程中应用程序会停止,俗称Stop The Word状体,该状态下所有的线程将会暂停工作,等待垃圾回收完成。为了解决问题,于是产生了增量收集算法。
核心思想:如果一次性进行垃圾回收,会造成长时间的应用程序的停止,那么让垃圾回收线程与应用程序线程交替执行,每次垃圾回收只回收一小部分的区域,接着替换到应用程序线程,反复交替,知道垃圾收集完成。
增量算法其实本质上还是基于传统的标记-清除算法,标记-整理算法,复制算法,知识每次处理的内存区域变小,不回让应用程序停止很长的时间。
缺点:虽然在运行过程中,停顿的感觉变少。但是线程切换与上下文转换的发生造成成本的上升。
分区算法
正常情况下,一个GC区域越大, GC执行时长越久,应用程序停顿的时长变久,为了更好的减少停顿的时长,将一个内存区域分成多个小块,根据停顿时间,合理的回收部分小块,而不是整个堆空间。
这种算法的好处是可以控制一次挥手多少个小的内存区域。



