Java垃圾回收机制与内存泄露问题

1 .垃圾收集算法的核心思想
Java 语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。
垃 圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃 圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。
 
2 .触发主 GC Garbage Collector )的条件( 2 个条件)
JVM 进行次 GC 的频率很高 , 但因为这种 GC 占用时间极短 , 所以对系统产生的影响不大。更值得关注的是主 GC 的触发条件 , 因为它对系统影响很明显。总的来说 , 有两个条件会触发主 GC:
1 当应用程序空闲时 , 即没有应用线程在运行时 ,GC 会被调用。因为 GC 在优先级最低的线程中进行 , 所以当应用忙时 ,GC 线程就不会被调用 , 但以下条件除外。
 
2 Java 堆内存不足时 ,GC 会被调用。当应用线程在运行 , 并在运行过程中创建新对象 , 若这时内存空间不足 ,JVM 就会强制地调用 GC 线程 , 以便回收内存用于新的分配。若 GC 一次之后仍不能满足内存分配的要求 ,JVM 会再进行两次 GC 作进一步的尝试 , 若仍无法满足要求 ,   JVM 将报“ out of memory ”的错误 ,Java 应用将停止。
由于是否进行主 GC JVM 根据系统环境决定 , 而系统环境在不断的变化当中 , 所以主 GC 的运行具有不确定性 , 无法预计它何时必然出现 , 但可以确定的是对一个长期运行的应用来说 , 其主 GC 是反复进行的。
 
3 .减少 GC 开销的措施
根据上述 GC 的机制 , 程序的运行会直接影响系统环境的变化 , 从而影响 GC 的触发。若不针对 GC 的特点进行设计和编码 , 就会出现内存驻留等一系列负面影响。为了避免这些影响 , 基本的原则就是尽可能地减少垃圾和减少 GC 过程中的开销。具体措施包括以下几个方面 :
1 不要显式调用 System.gc()
此函数建议 JVM 进行主 GC, 虽然只是建议而非一定 , 但很多情况下它会触发主 GC, 从而增加主 GC 的频率 , 也即增加了间歇性停顿的次数。
 
2 )尽量减少临时对象的使用
临时对象在跳出函数调用后 , 会成为垃圾 , 少用临时变量就相当于减少了垃圾的产生 , 从而延长了出现上述第二个触发条件出现的时间 , 减少了主 GC 的机会。
 
3 )对象不用时最好显式置为 Null
一般而言 , Null 的对象都会被作为垃圾处理 , 所以将不用的对象显式地设为 Null, 有利于 GC 收集器判定垃圾 , 从而提高了 GC 的效率。
 
4 )尽量使用 StringBuffer, 而不用 String 来累加字符串(详见 blog 另一篇文章 JAVA String StringBuffer
由于 String 是固定长的字符串对象 , 累加 String 对象时 , 并非在一个 String 对象中扩增 , 而是重新创建新的 String 对象 , Str5=Str1+Str2+Str3+Str4, 这条语句执行过程中会产生多个垃圾对象 , 因为对次作“ + ”操作时都必须创建新的 String 对象 , 但这些过渡对象对系统来说是没有实际意义的 , 只会增加更多的垃圾。避免这种情况可以改用 StringBuffer 来累加字符串 , StringBuffer 是可变长的 , 它在原有基础上进行扩增 , 不会产生中间对象。
 
5 )能用基本类型如 Int,Long, 就不用 Integer,Long 对象
基本类型变量占用的内存资源比相应对象占用的少得多 , 如果没有必要 , 最好使用基本变量。
 
6 )尽量少用静态对象变量
静态变量属于全局变量 , 不会被 GC 回收 , 它们会一直占用内存。
 
7 )分散对象创建或删除的时间
集中在短时间内大量创建新对象 , 特别是大对象 , 会导致突然需要大量内存 ,JVM 在面临这种情况时 , 只能进行主 GC, 以回收内存或整合内存碎片 , 从而增加主 GC 的频率。集中删除对象 , 道理也是一样的。它使得突然出现了大量的垃圾对象 , 空闲空间必然减少 , 从而大大增加了下一次创建新对象时强制主 GC 的机会。
 
4 gc finalize 方法
1 gc 方法 请求垃圾回收
使用 System.gc() 可以不管 JVM 使用的是哪一种垃圾回收的算法,都可以请求 Java 的垃圾回收。需要注意的是, 调用System.gc() 也仅仅是一个请求。JVM 接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。
 
2 finalize 方法透视垃圾收集器的运行
JVM 垃圾收集器收集一个对象之前 ,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下, Java 提供了缺省机制来终止化该对象释放资源,这个方法就是 finalize ()。它的原型为:
protected void finalize() throws Throwable
finalize() 方法返回之后,对象消失,垃圾收集开始执行。原型中的 throws Throwable 表示它可以抛出任何类型的异常。
因此,当对象即将被销毁时,有时需要做一些善后工作。可以把这些操作写在 finalize() 方法里。
protected void finalize(){
// finalization code here
}
 
3 )代码示例
class Garbage{
    int index;
    static int count;
    Garbage(){
        count++;
        System.out.println("object "+count+" construct");
        setID(count);
    }
   
    void setID(int id){
        index=id;
    }
   
    protected void finalize(){ // 重写finalize 方法
        System.out.println("object "+index+" is reclaimed");
    }
   
    public static void main(String[] args){
        new Garbage();
        new Garbage();
        new Garbage();
        new Garbage();
        System.gc();  // 请求运行垃圾收集器
    }
}
 
结果:
object 1 construct
object 2 construct
object 3 construct
object 4 construct
object 4 is reclaimed
object 3 is reclaimed
object 2 is reclaimed
object 1 is reclaimed
 
4 )这里给出 Thinking in Java 3rd 中关于 finalize() 的两段介绍
finalize() 工作原理
一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用其 finalize() 方法,并且在下一次回收动作发生时,才真正回收对象占用的内存。
 
finalize() 用途
之所以使用 finalize() ,是由于在分配内存时可能采用了类似 C 语言中的做法,这种情况主要发生在使用“本地方法”的情况下。在释放存储空间时,使用如 free() 函数,所以需要在 finalize() 中用本地方法调用它。
 
5 Java 内存泄漏
由于采用了垃圾回收机制,任何不可达对象( 对象不再被引用 )都可以由垃圾收集线程回收。因此通常说的 Java 内 存泄漏其实是指无意识的、非故意的对象引用,或者无意识的对象保持。无意识的对象引用是指代码的开发人员本来已经对对象使用完毕,却因为编码的错误而意外 地保存了对该对象的引用(这个引用的存在并不是编码人员的主观意愿),从而使得该对象一直无法被垃圾回收器回收掉,这种本来以为可以释放掉的却最终未能被 释放的空间可以认为是被“泄漏了”。
考虑下面的程序 , ObjStack 类中 , 使用 push pop 方法来管理堆栈中的对象。两个方法中的索引 (index) 用于指示堆栈中下一个可用位置。 push 方法存储对新对象的引用并增加索引值 , pop 方法减小索引值并返回堆栈最上面的元素。在 main 方法中 , 创建了容量为 64 的栈 , 64 次调用 push 方法向它添加对象 , 此时 index 的值为 64, 随后又 32 次调用 pop 方法 , index 的值变为 32, 出栈意味着在堆栈中的空间应该被收集。但事实上 ,pop 方法只是减小了索引值 , 堆栈仍然保持着对那些对象的引用。故 32 个无用对象不会被 GC 回收 , 造成了内存渗漏。
class ObjStack {
private Object[] stack;
private int index;
ObjStack(int indexcount)  {
stack=new Object[indexcount];
          index=0;
}
public void push(Object obj) {
        stack[index]=obj;
        index++ ;
    }
    public Object pop() {
        index - -;
        return stack[index];
    }
}
public class Push-pop {
    public static void main(String args[ ]) {
    int i=0;
    Object tempobj;
    ObjStack stack1=new ObjStack(64);
    while (i<64)
    {  tempobj=new Object();
stack1. push(tempobj);
i++;
System.out.print( “第 "+i+ “次进栈 "+ /t"); }
            while (i>32)
        { tempobj=stack1.pop();
i --;
System.out.print( “第 "+(64-i)+ “次出栈” + /t"); }
          //…
}
}
正确的 pop 方法可改成如下所指示 , 当引用被返回后 , 堆栈删除对他们的引用 , 因此垃圾收集器在以后可以回收他们。
public Object pop() {
index - -;
Object temp = stack [index];
stack [index]=null;
return temp;
}
 
6 .参考资料
[1] 池炜成, Java 垃圾收集的机制及调优,计算机应用研究, 2004 03
[2] 王伟民, 谈谈Java 程序中的内存渗漏, 浙江工业大学学报, 2003 04
[3]Thinking in Java 3rd
[4] 孙鑫视频
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值