Java垃圾回收机制

一、垃圾回收机制的概念

     1、GC(Garbage Collection):垃圾回收。在GC中,垃圾所指的是程序在运行过程中,会产生出一些无用的对象,或者说是已经被弃用的对象,而这些对象会占用着一部分的内存空间,如果长时间不去回收这些内存空间,那么最终会导致OOM(内存泄漏)。GC就负责处理这些已用对象(new)。

         垃圾回收是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象所占据的内存空间的一种机制。是Java与C++/C的主要区别之一,在使用JAVA的时候,一般不需要专门编写内存回收和垃圾清理代 码。这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制。

     2、JVM堆:  

          (1)新域(coping算法):用来存放新产生的对象。

          (2)旧域(tracing算法):用来存放经过几次回收还没有回收掉的对象。

          (3)永久域:用于存放静态文件,如Java类、方法等。对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。独立。不包括在JVM堆内。默认为4M。  

     3、注意:

         (1)JVM堆的大小决定了GC的运行时间。如果JVM堆的大小超过一定的限度,那么GC的运行时间会很长。

         (2)对象生存的时间越长,GC需要的回收时间也越长,影响了回收速度。

         (3)大多数对象都是短命的,所以,如果能让这些对象的生存期在GC的一次运行周期内就很好了。

         (4)应用程序中,建立与释放对象的速度决定了垃圾收集的频率。

         (5)如果GC一次运行周期超过3-5秒,这会很影响应用程序的运行,如果可以,应该减少JVM堆的大小了。

         (6)通常情况下,JVM堆的大小应为物理内存的80%。

、GC工作流程

       新生成的对象都放在中。满了,GC开始工作。停止应用程序的运行,开始收集垃圾,把所有可找到的对象都copy到初始空间中,这个空间满了就copy到目标空间中(会覆盖原有存储对象),来回copy。工作流程大致可以分为:  

       (1)当满了,触发Scavenge GC          

       (2)GC做两件事:   

               a、去掉一部分没用的object                          

               b、把老的还被引用的object发到初始空间里面,几次GC以后再放到old里面当old满了,触发Full GC。Full GC耗内存,把大部分垃圾都收掉,这个时候用户线程都会被阻塞。   

        注意:Full GC完全垃圾回收 对整个堆进行整理,包括Young、Tenured和Perm,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。

三、GC如何判断一个对象是垃圾

     1、引用计数法(Reference Counting Collector)

          1)算法分析:引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

           2)优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

          3)缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。例如:

public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
          
        object1.object = object2;
        object2.object = object1;
          
        object1 = null;
        object2 = null;
    }
}

最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。

2、可达性分析算法(GC Roots Analysis)——主流

     1)基本思路:通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。

          

     2)在Java语言里,可作为GC Roots对象的包括如下几种:

         a、虚拟机栈(栈桢中的本地变量表)中的引用的对象

         b、方法区中的类静态属性引用的对象

         c、方法区中的常量引用的对象

         d、本地方法栈中JNI的引用的对象

3、finalize()方法最终判定对象是否存活

     即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。(标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。)

      (1)第一次标记并进行一次筛选。

            筛选的条件:此对象是否有必要执行finalize()方法。

          当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。

      (2)第二次标记

        如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。

        Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。(任何一个对象的finalize方法都只会被系统自动调用一次。)

          

四、内存泄漏问题

      1、java和c++在内存分配和管理上的区别:

          Java与C++之间有一堵由动态内存分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人想出来。

          对于从事C和C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权利的皇帝,也是从事最基础工作的劳动人民-----既拥有每一个对象的所有权,又担负着每一个对象从生命开始到终结的维护责任。

          对于Java程序员来说,虚拟机的自动内存分配机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,而且不容易出现内存泄露和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是因为Java程序员把内存控制的权利交给Java虚拟机,一旦出现内存泄露和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误将会是一项异常艰难的工作。

         并且好的Java程序在编写的时候肯定要考虑GC的问题,怎样定义static对象,怎样new对象效率更高等等问题,简称面向GC的编程。也可以说Java的内存分配管理是一种托管的方式,托管于JVM。

        C++经过编译时直接编译成机器码,而Java是编译成字节码,由JVM解释执行。C++是编译型语言,而Java兼具编译型和解释型语言的特点。

      2、java有了GC同样会出现内存泄漏问题

          (1)静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

     static Vector v=new Vector();
     for(int i=1;i<100;i++) 
     {
	    Object o=new Object();
	    v.add(o);
	    o=null;
     }

          在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 for 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏

        2)各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

       3)监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

上庸者-不服周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值