服务热线:13616026886

技术文档 欢迎使用技术文档,我们为你提供从新手到专业开发者的所有资源,你也可以通过它日益精进

位置:首页 > 技术文档 > JAVA > 新手入门 > 基础入门 > 查看文档

编写对gc友好,又不泄漏的代码

    1.使用更多生命周期短的、小的、不改变指向(immutable)的对象,编写清晰的代码。 

    出于懒惰也好,朴素的节俭意识也好,我们都习惯对一个变量重用再重用。但是.... 

    java的垃圾收集器喜欢短生命周期的对象,对象如果在新生代内,在垃圾收集发生前就死掉了,垃圾收集器就什么都不用做了。 
现代jvm构建一个新对象只需要10个本地cpu指令,并不弱于c/c++。 (但垃圾收集没有压缩算法时会稍慢,更频繁的new对象也导致更频繁的gc)。 
大对象的分配效率更低,而且对非压缩算法的垃圾收集器,更容易造成碎片。 
对象重用增加了代码的复杂度,降低了可读性。 
   所以有标题的呼吁,比如不要害怕为中间结果分配小对象。但编程习惯的改变也不是一朝一夕的事情。 

    2.将用完的对象设为null其实没什么作用。 

    貌似很酷的把对象主动设为null 的"好习惯"其实没什么用,jit compiler会自动分析local变量的生命周期。
    只有一个例外情况,就是string[1024] foo 这种赤裸裸的数组,你需要主动的foo[100]=null释放第100号元素,所以最好还是直接用arraylist这些标准库算了。 

 

    3.避免显式gc--system.gc()。 

    大家都知道system.gc()不好,full-gc浪费巨大,gc的时机把握不一定对等等,甚至有-xx:+disableexplicitgc的jvm参数来禁止它。 

    哈哈,但我还不会用system.gc()呢,不怕不怕。真的不怕吗? 

先用findbugs 查一下所用到的全部第三方类库吧... 
至少rmi 就会老实不客气的执行system.gc()来实现分布式gc算法。但我也不会用rmi啊。那ejb呢,ejb可是建在rmi上的.... 
    如果无可避免,用-dsun.rmi.dgc.client.gcinterval=3600000 -dsun.rmi.dgc.server.gcinterval=3600000 (单位为微妙) 增大大gc的间隔(原默认值为1分钟),-xx:+explicitgcinvokesconcurrent 让system.gc() 也cms并发执行。 

 

    4.继续千夫所指的finalize() 

    大家也都知道finalize()不好,分配代价昂贵,释放代价更昂贵(要多走一个循环,而且他们死得慢,和他们相关联的对象也跟着死得慢了),又不确定能否被调用(jvm开始关闭时,就不会再进行垃圾收集),又不确定何时被调用(gc时间不定,即使system.gc()也只是提醒而不是强迫gc,又不确定以什么样的顺序调用,所以finalize不是c++的析构函数,也不像c++的析构函数。 

   我们都知道啊,所以我从来都没使用。都是在显式的维护那些外部资源,比如在finally{}里释放。


    5.weakreference/softreference 

   这是个平时不怎么会搭理,偶然知道了又觉得有用的java特征。大家都知道java里所有对象除int等基本类型外,都是pass by reference的指针,实例只要被一个对象连着,就不会被收集。
    而weakreference就是真正意义上的c++指针,只是单纯的指向一个对象,而不会影响对象的引用计数。
    而softreference更特别,在内存足够时,对象会因为softreference的存在而不被收集,但内存不足时,对象就还是会被收集,怎么看都是做简单缓存的料子。代码如下: 

  foo foo = new foo(); 
  softreference sr= new softreference(foo); 
  foo bar =  sr.get(); 
  如果foo已被垃圾收集,sr.get()会返回null; 

  另外还有一个referencequeue的机制,使得对象被回收时能获得通知,比finalize()完全不知道gc何时会执行要聪明的多。 

  referencequeue rq = new referencequeue();
  ref = new weakreference(foo, rq); 
  weakreference cleaned = rq.pool(); 

  cleaned就是刚刚被gc掉的weakreference。

    6.内存泄漏 

   java 不是有垃圾收集器了吗?怎么还泄漏啊,唬我啊??
   嗯,此泄漏非比泄漏。c/c++的泄漏,是对象已不可到达,而内存又没有回收,真正的内存黑洞。
   而java的泄漏,则是因为各种原因,对象对应用已经无用,但一直被持有,一直可到达。
   总结原因无外乎几方面: 

    被生命周期极长的集合类不当持有,号称是java内存泄漏的首因。
    这些集合类的生命周期通常极长,而且是一个辅助管理性质的对象,在一个业务事务运行完后,如果没有将某个业务对象主动的从中清除的话,这个集合就会吃越来越多内存,可以用weakreference,如weakhashmap,使得它持有的对象不增加对象的引用数。 
scope定义不对,这个很简单了,方法的局部变量定义成类的变量,类的静态变量等。 
异常时没有加finally{}来释放某些资源,jdbc时代也是很普遍的事情。 
    另外一些我了解不深的原因,如:swing里的listener没有显式remove;内部类持有外部对象的隐式引用;finalizers造成关联对象没有被及时清空等。 
内存泄漏的检测 

有不少工具辅助做这个事情的,如果手上一个工具也没有,可以用jdk自带的小工具: 

看看谁占满了heap?
用jmap可以显示运行程序中对象的类型,个数与所占的大小
先用jps 找到进程号,然后jmap -histo pid 显示或 jmap -dump:file=heap_file_name pid 导出heap文件 
为什么这些对象仍然可以到达?
用jhat(java heap analysis tool) 分析刚才导出的heap文件。
先jhat heap_file_name,然后打开浏览器http://localhost:7000/ 浏览。 

扫描关注微信公众号