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