java垃圾回收机制

一、什么是垃圾回收机制

java的垃圾回收机制是Java虚拟机提供的能力,用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间。需要注意的是垃圾回收回收的是无任何引用的对象占据的内存空间,而不是对象本身。真正的垃圾回收机制不是立马回收,具体在什么时间开始是不确定的,这和抢占式的线程在发生作用时的原理一样。

二、关于在讲解垃圾回收时会用到的一些概念

1.强制垃圾回收。由于程序无法精确控制java垃圾回收的时机,但是我们依然可以强制系统进行垃圾回收,这种强制指的是只是通知系统进行垃圾回收,使垃圾回收容易发生或者提早发生。但是系统是否进行垃圾回收依然不确定。强制系统垃圾回收有如下两个方法:(1)调用System类的gc()静态方法:System.gc()(2)调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()

2.finalize方法。当垃圾回收机制在回收某个无用对象所占用的内存空间之前,通常要求程序调用适当的方法来清理资源,在没有明确指定资源清理的情况下,Java提供了默认的机制来清理该对象的资源,这个方法就是finalize。当finalize()返回后,对象消失,垃圾回收机制开始执行。任何java类都可以覆盖finalize()方法,如果Java类重写finalize方法,在这个方法里调用无用的对象,则会使这个对象重新激活。但是要注意的是finalize方法不确定什么时候执行,不确定一定会执行。

3.对象在堆内存中的状态。当一个对象再堆内存中运行时,可以把它分为三种状态:可达状态、可恢复状态和不可达状态。可达状态即当一个对象被创建以后,有一个以上的引用变量引用它;可恢复状态即当一个对象没有引用变量引用时,它会先进入可恢复状态,此时垃圾回收机制准备回收,在正式回收之前,系统会调用该对象的finalize方法进行资源清理,如果这个方法能让一个以上的引用变量引用该对象,则该对象就会变成可达状态,否则该对象进入不可达状态;不可达状态即该对象的所有关联都被切断而且调用该对象的finalize方法也没有令该对象进入可达状态,那么这个对象将永久的失去引用,最后变成不可达状态,系统才会真正回收该对象所占有的资源。

4.java对对象的引用方式。

(1)强引用。程序创建一个对象,并把这个对象赋值给一个引用变量,这个引用变量就是强引用。被强引用所引用的java对象绝不会被垃圾机制回收,即使系统内存非常紧张,即使这些Java对象永远不会被用到,jvm也不会回收这些被强引用所引用的Java对象。

(2)软引用。软引用通过SoftReference类来实现,当一个类只有软引用时,若内存空间充足,则软引用和强引用没有什么区别,但是内存空间不足时被软引用引用的Java对象可以被垃圾回收机制回收,从而避免系统内存不足的异常。

SoftReference<Person>[] people=new SoftReference[100];                                                                                                         

(3)弱引用。弱引用通过WeakReference来实现,弱引用比软引用所引用对象的生存期更短,对只有软引用对象来说,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存空间,但是这并不是说当一个对象只有弱引用时,它就会被立即回收,它和那些失去引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收。

WeakReference<String> wr=new WeakReference<String>("ltt"); 

(4)虚引用。虚引用通过PhantomReference类来实现。虚引用主要作用是跟踪对象被垃圾回收的状态,程序通过检查与虚引用相关联的引用队列中是否包含了指定的虚引用,从而了解虚引用所引用的对象是否被回收。虚引用在对象释放前,将把它的虚引用添加到与它关联的引用队列中。虚引用类似于没有引用,虚引用无法获取它所引用的对象。

//创建一个引用队列
ReferenceQueue<String> rq=new ReferenceQueue<String>();
//创建一个虚引用,让此虚引用引用到字符串。
PhantomReference<String> pr=new PhantomReference<String>("ltt",rq);
5.java内存泄漏。

程序运行中会不断地分配内存空间,那些不再使用的内存空间应该立即回收它们,如果存在无用的内存没有被回收回来,那就是内存泄漏。

对于C++程序而言,对象所占用的内存空间是由程序员显示回收的;对于Java来说,所有不可达的对象都由垃圾回收机制回收,因此程序员不用担心这部分内存的泄露,但是如果程序中的一些Java对象处于可达状态但是永远不会被用到,那它们所占用的内存空间也不会被回收,则它们所占用的空间会产生内存泄露。

三、垃圾回收算法

对于jvm的回收机制来说,是否回收一个对象的标准在于是否还有引用变量引用该对象,jvm的垃圾回收机制采用有向图的方式来管理内存中的对象。

垃圾回收机制主要完后才能两件事情(1)跟踪并监控每个Java对象,当每个对象处于不可达时,回收该对象所占用的内存空间。(2)清理内存分配,回收过程中产生的内存碎片。

垃圾回收算法要做主要工作就是上述两方面。

如何判断什么时候回收,如下:

1.标记计数方法。

标记计数方法即给对象添加一个引用计数器,每当增加一个引用变量引用该对象时,计数器数值就加1,当减少一个引用变量引用该对象时,计数器数值就减1,当最终计数器数值变为0时就认定该对象已经没有被引用变量引用了,即变成无用的变量了。但是这个方法很难解决的一个问题就是对象之间的相互循环引用问题。如下:

class C{ 
         public Object x; 
    } 
    C obj1、obj2 = new C(); 
    obj1.x = obj2; 
    obj2.x = obj1; 
    obj1、obj2 = null; 
上述代码,obj1指向obj2,obj2的计数器数值加1;obj2指向obj1,obj1的计数器数值加1,此时即使把obj1和obj2都设置为null,两个对象也不能被回收,因为这两个对象虽然为null但是它们的引用计数值还是1。

2.可达性分析算法。

通过一系列的称为“GC Roots”的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,即GC Roots到这个对象不可达,则说明该对象是没有用的。但是要注意的是在可达性分析算法中,要至少经过两次标记过程才能判定一个对象是否是无用的。第一步:GC 第一次标记并进行了一次筛选,筛选出那些覆盖了finalize方法并且第一次调用finalize方法的对象,这些对象会被放在F-Queue的队列中,然后由虚拟机自动建立的低优先级的线程去执行该对象的finalize方法。第二步:GC进行第二次标记,如果在上一步中那些筛选出来的对象的finalize方法没有使自己变成可达状态,则这些对象和上一步中没有被删选出来的对象都会被回收。

垃圾回收算法

1.标记-清除算法(mark-sweep)

顾名思义该方法分为标记和清除两个阶段:首先标记出所有需要回收的对象,即不可达的对象,标记完成后再进行一次遍历,把所有标记为不可达标志的对象进行回收处理。执行过程如下(黑色表示不可达状态,蓝色表示可达状态,白色表示没有使用):

回收前的状态:


回收后的状态:


标记-清除算法的缺点:(1)需要两次遍历堆内存空间,遍历成本较大(2)标记清除后产生大量的不连续的空间碎片,当要分配较大对象时会无法找到足够的连续空间内存。

2.复制算法

复制算法把堆内存分成两个相同的空间,从根开始访问每一个关联的可达对象,将空间A中的所有可达对象复制到空间B中,然后一次性的回收整个A空间。执行过程如下(黄色是保留区域):

回收前的状态:


回收后的状态:


复制算法每次对整个半区进行内存回收,运行高效,而且在那块使用内存上进行内存分配时,不用考虑内存碎片的问题,但是将内存缩小为原来的一半,代价较高。

3.标记-压缩算法(mark-sweep-compact)

标记压缩算法充分利用上述两种算法的优点,垃圾回收器先从根开始访问所有的可达对象,把它们标记为可达状态,但是标记完成后并不直接对可回收对象进行处理,而是把可达对象都移到一端,即把可达状态的对象搬迁在一起,然后清理掉端边界以外的内存,算法示意图如下:

回收前的状态:


回收后的状态:


4.分代算法

现行的垃圾回收器用分代的方式来采用不同的回收设计。分代的基本思路就是根据对象的生存期的长度,把堆内存分为三个代即Young代、Old代和Permanent代。

采用这种分代回收的策略基于如下两点事实(1)绝大多数的对象不会被长时间引用,这些对象在其Young代就会被回收。(2)生存时间很长的对象和生存时间很短的对象之间很少存在相互引用的情况。

(一)Young代

绝大多数最先被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可达,所以很多对象被创建在Young代然后消失,对象从Young代消失的过程我们称作是次要回收(minor GC)。

Young代被分成三个空间即一个Eden空间和两个Survivor空间。绝大多数刚刚被创建的对象会被保存在Eden区,在Eden执行了第一次GC以后,存活的对象被移动到其中一个Survivor空间,此后在Eden执行GC以后,存活的对象会被放到上次存放存活对象的Survivor空间,当这个Survivor空间满了,还在存活的对象会被移动到另一个Survivor空间或者内存很大的对象直接移动到Old代,之后会清空已经饱和的那个Survivor空间,在前面的步骤中重复几次仍然存活的对象就会被移到Old代。需要注意的是在Young代中,其中一个Survivor空间必须是空的。过程图如下:


在经过GC以后:


Young代中采用的是复制算法。

(二)Old代

如果Young代中对象经过数次垃圾回收依然还没有被回收掉,则垃圾回收机制就会把这个对象转移到Old代,如下图:


Old代的大部分对象都是经过很长时间没有被回收的,随着时间流逝,Old代的对象会越来越多,因此Old代的空间要比Young代的空间要大。对象从Old代中消失的过程称为主要回收(major GC /full GC)。old代中垃圾回收一般采用的是标记压缩算法。

(三)Permanent代

Permanrnt代主要用于装载class等信息,垃圾回收机制通常不会回收Permanent代中的对象,程序因为内存不足而终止的时候往往是因为Permanent代内存耗尽。

四、关于垃圾收集器

串行回收器、并行回收期、并行压缩回收器、并发标识清理回收器。(具体的本人不是很清楚。。)

五、内存管理小技巧

1.尽量使用直接量,而不用new的方式来创建对象。比如String a="hello"。

2.尽量使用StringBuffer和StringBuilder。StringBuffer和StringBuilder是可变长的,它在原有基础上进行扩增,不会产生中间对象。

3.尽早释放无用的对象。

3.尽量使用静态变量。静态变量属于类本身,因此它的生命周期和类同步,类对象一旦被创建,类变量就会常驻内存,直到程序运行结束。

4.避免在经常调用的方法循环中创建java对象。

5.缓存经常使用的对象。

6.尽量不使用finalize方法。

7.考虑使用SoftReference来包装数组元素。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值