zoukankan      html  css  js  c++  java
  • Java基础:GC机制

      上一节,简单的介绍了java当中的内存模型,那么经常和内存模型一起提到的JAVA垃圾回收机制当然也需要在这里一并的总结一下。

      所谓是垃圾回收机制,用通俗的话来说,就是将那些没有被任何变量引用的实例对象自动的进行垃圾回收,重新释放出可用的内存,以供之后使用的方法。在java当中垃圾回收机制几乎是程序员不可编程的,但是也正因为程序员不可编程,也可以说是不需要编程,所以java相比C++来在这一点来说,人性化的很多,因为C++当中需要在实例化一个对象之后,当这个对象不再被需要的时候,手动调用析构函数对无用实例进行回收,而java当中有一个完整的垃圾回收机制对这些对象进行回收,编程人员无需关注这些不再被引用的实例何时回收,对于编程人员来说,可以把精力更加的注重于逻辑上的业务开发。

      那么问题来了,java当中,如何实现对不再被引用(有的说法叫不被其他任何对象持有)的实例进行回收的呢?接下来才是我们要介绍的重头戏。

      首先我们要先介绍几种常规的垃圾回收机制的算法(非java使用的垃圾回收机制的算法):

    1.引用计数算法

      该算法的大概思路就是每一个实例对象都计算指向它的指针数量(在java当中是引用变量指向它的数量),当有一个指针(或者引用变量)指向自己的时候,计数值+1,反之,当指向自己的指针删除(或引用变量不再指向自己的时候)计数值-1,当计数值为0的时候,说明该对象已经不存在指向的指针(或者指向它的引用变量,它不再被持有),所以就可以把它安全的回收。

    算法优点:算法简单,并且回收没有延迟。

    算法缺点:1)需要存储计数器,增加了存储空间的开销、2)需要更新计数器,增加了时间开销、3)无法处理环形引用的问题。

    2.标记-清除算法

      该算法的主要思路是对于所有或者的实例对象,进行一次全局的遍历来确定哪些实例是可以被回收的、哪些实例是还被持有的、遍历的过程如下:从根出发,找到所有可达的实例,对这些可达的实例进行标记,然后剩下的没有被标记的实例,则是可以被清除的,然后将这些实例进行回收。

    算法优点:1)很自然的解决了环形引用的问题。2)不需要提供额外的计数器的开销。

    算法缺点:1)该算法在垃圾回收器运行的过程中、应用程序(stop-to-world)必须停止。2)需要遍历所有的存活对象,会造成一定的开销。3)在清除阶段,清除垃圾对象后会造成大量的内存碎片(你想问为什么会造成碎片?其实很简单,看下图就知道了。)

    3.标记-清除-复制

      为了解决上述的内部碎片化的问题,是对于上边的一种方法的改进,基本的标记算法和上述大致相同,不一样的地方在于这种算法会把内存分为了A、B两个区域,当内存回收的时候,将A中的内存块被标记的实例拷贝到B中(这时候拷贝过去就对对象所存的位置进行了整理,使它不再碎片),然后一次性清空A。

    算法优点:解决了碎片化的问题

    算法缺点:对内存的大小要求为原来的两倍。

    上述介绍的图片部分来源于:http://blog.csdn.net/qq_26437925/article/details/53728388

    4.标记-清除-压缩

      除了对内存进行分区,然后交替使用的算法外,还有一种标记-清除-压缩算法,标记部分算法和上述一样,但是不同的是,在进行清除之前,不再进行分区,而是将对象都就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。

      注意,该算法是一个需要暂停应用程序的算法(stop-to-world)

    介绍完这几种典型的垃圾回收算法之后,你可能会问,说了一大堆,你还没有告诉我,java的垃圾回收机制是什么样的呢?错!java的回收机制,就是相当于集了以上几种算法的大成之作!

    5.java的垃圾回收机制

    java当中的垃圾回收机制(GC机制)主要面向的对象是内存当中的堆,还有方法区,并且在java当中,采用了分代回收的算法,那么都分为了什么代呢?

    1)年轻代

      主要是用来装新生成的对象的,而年轻代设立出来的主要目的,就是为了尽可能快速的回收那些声明周期短的对象。而在年轻代当中,一般又被分为了三个区,一个名叫Eden区,而剩下两个则都为Survivor区(姑且称为SA区和SB区吧,这两个区的地位是完全相同的,当然Survivor区也可以分为多个)。大部分的实例都是在这个Eden区当中生成(一些很大的实例,直接就在老年代当中),当Eden区被占满后,将会触发垃圾回收,面向年轻代的垃圾回收被称之为Minor GC,这时候会通过标记-清除-复制算法,将Eden区当中还被持有的实例进行标记后,整理复制到Survivor区(A或者B都有可能),并且将Eden区当中的所有的实例都回收掉,而这时候被复制到Survivor区的则是这次GC的幸存者。而当Survivor区也被占满时(注意,SA区和B区,在同一时间,一定是一个区被用,则另一个区为空的,这里姑且假设被用的A区)就会又触发Minor GC,这时候还是采用标记-清楚-复制算法,将A区的被标记的实例再整理复制到B区当中,并且将A区清空。而当B区又被占满,这时候还是同样再将幸存下来的实例在复制到A去当中,但是有一点不同的是,如果再A区和B区的GC当中都幸存下来的实例,则将会复制到老年代当中,而不再存在于年轻代。

    2)老年代

      在年轻代中经历了N次(在上述例子当中,应该是3次Minor GC)垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为老年代是存着一些生命周期较长的对象,而当老年代也被占满的时候,则会触发Major GC(面向老年代的GC被称为Major GC,速度比minorGC要慢),一般采用的是标记--清除-压缩算法。

    3)永久代

      也就是在内存机制当中经常说到的方法区,一般不会被回收。

    注意:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代GC称为Major GC,而Full GC是对整个堆来说的,在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。Major GC的速度一般会比Minor GC慢10倍以上。下边看看有那种情况触发JVM进行Full GC及应对策略。

     注意:在MinorGC当中使用的标记-清除-复制算法也是需要stop-the-world的,但是对于年轻带来说,需要标记的对象不多,所以时间可以忽略不计。而且在MajorGC当中,由于需要标记大部分的对象,所以这个时间很长。导致了MajorGC比MinorGC时间要长的多的原因之一,而另一个原因是MajorGC当中由于还需要压缩,所以也相当耗时。

    接下来的内存,参考了博客:http://www.importnew.com/15330.html

    接下来我们再详细的看看垃圾回收算法有哪几种引用的形式?

    1)串行回收(只用一个CPU)和并行回收(多个CPU才有用):串行回收是不管系统有多少个CPU,始终只用一个CPU来执行垃圾回收操作,而并行回收就是把整个回收工作拆分成多个部分,每个部分由一个CPU负责,从而让多个CPU并行回收。并行回收的执行效率很高,但复杂度增加,另外也有一些副作用,如内存碎片增加。

    2)并发执行和应用程序停止 :应用程序停止(Stop-the-world)顾名思义,其垃圾回收方式在执行垃圾回收的同时会导致应用程序的暂停。并发执行的垃圾回收虽然不会导致应用程序的暂停,但由于并发执行垃圾需要解决和应用程序的执行冲突(应用程序可能在垃圾回收的过程修改对象),因此并发执行垃圾回收的系统开销比Stop-the-world高,而且执行时需要更多的堆内存。

    (后边都是一些垃圾回收器的介绍,表示看不懂了。。。。。。)

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

      这里更正一下,只有对象不被引用的时候,垃圾回收机制才会回收该对象,这句话是不严谨的,因为环型持有的情况下,也会对循环持有的对象进行回收。比如A持有B,B持有C,C持有A,这时候A/B/C可能都会被回收,是如何回收的呢?垃圾回收机制除了检查对象是否被引用外,还要看对象是否被至少一个GC roots对象直接或者间接引用,所以当A和B对象都退出了方法的时候,这时候这两个对象虽然相互为强引用,但是却不被GC roots对象直接或者间接的引用,所以会被回收。

      那么问题来了。所谓的GC roots对象又是什么对象呢?换个问题,所谓的从根节点遍历,这个根节点又是什么呢?

    •   虚拟机栈(栈帧中的本地变量表)中引用的对象
    •   本地方法栈中JNI(即一般说的Native方法)引用的对象
    •   方法区中类静态属性引用的对象
    •   方法区中常量引用的对象

    看到这里,恭喜你,对java当中的垃圾回收的了解就更进一步了,那么我们再来看看,垃圾回收当中的一个重要方法 finalize()方法。

      在之前我们只是简单的提到过这个方法,是在对象要被回收之前一般都会执行的一个方法(一般,说明JVM不保证finalize函数一定会被调用,比如在JVM退出的当口,内存中那些对象的finalize函数可能就不会被调用了),在执行了finalize方法之后,只有在下一次垃圾收集过程中,才会真正回收对象的内存。这里需要提到一点,执行finalize方法之后的对象不一定就会被回收,根据 Java 文档,finalize() 是一个用于释放非 Java 资源的方法,换个方式来解释,就是比如你的对象当中,需要用到外部的资源,比如tcp socket、mysql的连接,而当你的对象突然不被使用了,需要被回收了,如果没有finalize方法,可能你的对象只是回收了java部分的资源,而外部的资源却没有被close,所以finalize方法就是确保对象被回收之前,外部资源都会被释放的方法。

  • 相关阅读:
    动态加载js文件以支持跨域脚本
    获取页面宽高的一些代码
    根据dom对象或其id获取对象位置的代码
    Exchange2007用户用户全部访问权限授权命令及验证脚本
    frame页面地址转向跨域解决方法
    过滤掉多余的重复记录的SQL语句
    读写cookie的方法
    识别移动设备脚本
    winrar打包部署程序
    自己动手搭建MinGW
  • 原文地址:https://www.cnblogs.com/WellHold/p/7428865.html
Copyright © 2011-2022 走看看