JVM原理—垃圾回收机制算法


在面向对象语言程序中,我们的程序在运行中会创建很多对象,程序会为对象在内存中开辟一段空间并分配好内存地址,当对象使用结束后,需要释放占用的内存空间,释放对象内存的机制就叫垃圾回收机制(Garbage Collection,GC)。在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数,而C++中,我们则使用运算符 new 和 delete 来管理内存,这样虽然能灵活有效的申请和释放内存,但是对程序员来说,有时候使一种负担,而Java的垃圾回收机制则很好的解决了这一问题。

一、垃圾回收机制:

JVM自动不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收,垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。当然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的。

1. 不可达对象: 对象没有被引用,获知对象没有存活。

2. finalize方法:

Java在垃圾收集器将对象从内存中清除出去前,使用finalize()方法做必要的清理工作。该方法在Object类中定义,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize方法是在垃圾收集器删除对象之前对这个对象调用的。

3. 新生代和老年代:

堆内存被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。

新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor(S0)、To Survivor(S1)。S0区和S1区内存大小相等。

新生代:刚new出不久的对象存放在新生代中,存放不经常被使用的对象。

老年代:存放比较活跃的对象,存放经常被引用的对象。

垃圾回收机制在新生代回收的次数比较频繁,而在老年代回收的次数相对较少。而且一般情况下,老年代的内存空间大于新生代的内存空间。

二、判断对象存活的方法:

1.引用计数法:

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1。任何时刻计数器值为0的对象就是不可能再被使用的。当对象计数器的值大于15时,会被存放到堆内存的老年代中,当对象的值大于0小于15时则存放在新生代中。因其很难解决对象之间相互循环引用的问题,所以该算法逐渐被淘汰,而别很少出现在主流的Java虚拟机中。

2.根搜索算法:

根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

在Java语言中,可以作为GCRoots的对象包括下面几种:

(1). 栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。

(2). 方法区中的类静态属性引用的对象。

(3). 方法区中常量引用的对象。

(4). 本地方法栈中JNI(Native方法)引用的对象。

三、垃圾回收策略:

1、标记清除算法:

标记阶段:遍历堆,将不可达对象标记为1,可达对象标记为0。

清除阶段:遍历堆,逐个把标记为1的不可达对象回收。

缺点:由于堆内存空间不连续,所以逐个回收对象时会产生内存空间碎片化,而且效率低。

应用场景:用于对象存活周期较长的老年代。

2、复制算法:

复制算法的基本思想是JVM一开始就会将可用内存分为大小相等的两块:from域(S0区)和to域(S1区)。每次只是使用from域,to域则空闲着。当from域内存不够了,开始执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理。

应用场景:新生代。在新生代中,内存会被分为Eden区、S0区和S1区。刚new出来的对象刚开始会被存放在Eden区,当Eden区内存满后,触发新生代的GC操作,把可达对象拷贝至S0区,清除Eden区中的所有对象,当Eden区再次触发GC操作时,会扫描Eden区和S0区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到S1区域,并将Eden和From区域清空;当后续Eden又发生GC回收时,会对Eden和S1区域进行垃圾回收,存活的对象复制到S0区域,并将Eden和S1区清空;如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还是存活,就存入到老年代。

优缺点:性能高,解决了碎片化问题,但是S0和S1区总有一块内存空白,造成内存空间浪费。

应用场景:新生代。

3、标记压缩算法:

在标记清除算法的基础上,将对象进行排序,使不可达对象尽可能地排序在一起,GC操作时就可整段删除不可达对象,从而解决标记清除算法中产生碎片化的问题。

4、分代算法:

根据对象的存活周期的不同将内存划分成几块,新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。新生代采用复制算法,老年代使用标记压缩算法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

友情链接更多精彩内容