UDN-企业互联网技术人气社区

板块导航

浏览  : 1086
回复  : 0

[讨论交流] 防止 Java 内存泄露的安全措施

[复制链接]
呵呵燕的头像 楼主
发表于 2016-9-15 21:56:42 | 显示全部楼层 |阅读模式
  GC关注这些不可达的对象,但是不能判断对象是否不再使用,对于应用中存在可能使用被回收的对象的情形,程序员应该格外注意相应的业务逻辑代码。可笑的小错误将会成为大问题。

  内存泄漏会在很多情形下产生,让我们看看下面的例子:

  例子 1:自动装箱

  1.   package com.example.memoryleak;

  2.   public class Adder {

  3.   public long addIncremental(long l)

  4.   {

  5.   Long sum=0L;

  6.   sum =sum+l;

  7.   return sum;

  8.   }

  9.   public static void main(String[] args) {

  10.   Adder adder = new Adder();

  11.   for(long i;i<1000;i++)

  12.   {

  13.   adder.addIncremental(i);

  14.   }

  15.   }

  16.   }
复制代码


  你能指出其中的内存泄漏么?

  这儿的代码有个小问题,我没有使用基本数据类型long,而是使用了封装类Long,正是如此,这儿会造成内存泄漏。因为自动装箱机制,sum=sum+l;这句在每一次循环中将会创建一个新的Long对象,因此创建了有1000个无用的对象。请避免混合使用封装数据类型和基础数据类型,如果可以,尽量使用基础数据类型。

  例子2: 使用缓存

  1.   package com.example.memoryleak;

  2.   import java.util.HashMap;

  3.   import java.util.Map;

  4.   public class Cache {

  5.   private Map map= new HashMap();

  6.   public void initCache()

  7.   {

  8.   map.put("Anil", "Work as Engineer");

  9.   map.put("Shamik", "Work as Java Engineer");

  10.   map.put("Ram", "Work as Doctor");

  11.   }

  12.   public Map getCache()

  13.   {

  14.   return map;

  15.   }

  16.   public void forEachDisplay()

  17.   {

  18.   for(String key : map.keySet())

  19.   {

  20.   String val = map.get(key);

  21.   System.out.println(key + " :: "+ val);

  22.   }

  23.   }

  24.   public static void main(String[] args) {

  25.   Cache cache = new Cache();

  26.   cache.initCache();

  27.   cache.forEachDisplay();

  28.   }

  29.   }
复制代码


  在这里,发生内存泄漏是由于内部map数据结构。这个类是从缓存中获取显示员工的职位。 一旦它们显示完成,就不必把这些元素存储在缓存中了。

  我们忘了清除缓存,尽管程序不在需要缓存中的对象,但它们不能被GC,因为map持有它们的强引用。

  因此,但你使用缓存时,缓存中的对象不再需要时记得清楚它们。 另外,,你应该使用 WeakHashMap来初始化缓存。 使用WeakHashMap的好处时,一旦key不被其它对象引用,它就可以被GC回收。

  WeakHashMap有许多值得讨论的地方,我将再写一篇文章介绍。务必小心使用WeakHashMap,如果你需要重新使用缓存中的数据,可能发生这样的情况:该数据可能不再被其他对象引用,那么引用可能被回收,他的值也会消失。

  例子 3:关闭连接

  1.   try

  2.   {

  3.   Connection con = DriverManager.getConnection();

  4.   …………………

  5.   con.close();

  6.   }

  7.   Catch(Exception ex)

  8.   {

  9.   }
复制代码


  在上面的例子中,我们在try代码块中关闭了(昂贵的)连接资源,因此如果一旦发生异常,close语句跳过执行,那么连接将不会被关闭。这样,这个连接不会再返回连接池,就引发了一次内存泄漏。

  请养成将关闭语句写在finally代码块中的习惯。

  例子4: 使用构造方法

  1.   package com.example.memoryleak;

  2.   import java.util.HashMap;

  3.   import java.util.Map;

  4.   public class CustomKey {

  5.   public CustomKey(String name)

  6.   {

  7.   this.name=name;

  8.   }

  9.   private String name;

  10.   public static void main(String[] args) {

  11.   Map map = new HashMap();

  12.   map.put(new CustomKey("Shamik"), "Shamik Mitra");

  13.   String val = map.get(new CustomKey("Shamik"));

  14.   System.out.println("Missing equals and hascode so value is not accessible from Map " + val);

  15.   }

  16.   }
复制代码


  就像在CustomKey中我们忘了提供equals() 和 hashcode() 的实现, 因此,存储在map中的一个键和值都不能被检索, 因为map 的get()方法检查 hashcode() 和 equals(). 但是这个元素不会被GC掉, 因为map有引用它,但应用程序却无法访问它.显示它是内存泄漏.

  因些创建自己的Custom key, 总是需要提供 equals 和 hashcode() 实现.

  例子 5: 可变的 Custom Key

  1.   package com.example.memoryleak;

  2.   import java.util.HashMap;

  3.   import java.util.Map;

  4.   public class MutableCustomKey {

  5.   public MutableCustomKey(String name)

  6.   {

  7.   this.name=name;

  8.   }

  9.   private String name;

  10.   public String getName() {

  11.   return name;

  12.   }

  13.   publicvoid setName(String name) {

  14.   this.name = name;

  15.   }

  16.   @Override

  17.   public int hashCode() {

  18.   final int prime = 31;

  19.   int result = 1;

  20.   result = prime * result + ((name == null) ? 0 : name.hashCode());

  21.   return result;

  22.   }

  23.   @Override

  24.   public boolean equals(Object obj) {

  25.   if (this == obj)

  26.   return true;

  27.   if (obj == null)

  28.   return false;

  29.   if (getClass() != obj.getClass())

  30.   return false;

  31.   MutableCustomKey other = (MutableCustomKey) obj;

  32.   if (name == null) {

  33.   if (other.name != null)

  34.   return false;

  35.   } elseif (!name.equals(other.name))

  36.   return false;

  37.   return true;

  38.   }

  39.   public static void main(String[] args) {

  40.   MutableCustomKey key = new MutableCustomKey("Shamik");

  41.   Map map = new HashMap();

  42.   map.put(key, "Shamik Mitra");

  43.   MutableCustomKey refKey = new MutableCustomKey("Shamik");

  44.   String val = map.get(refKey);

  45.   System.out.println("Value Found " + val);

  46.   key.setName("Bubun");

  47.   String val1 = map.get(refKey);

  48.   System.out.println("Due to MutableKey value not found " + val1);

  49.   }

  50.   }
复制代码


  尽管在这里我们为custom Key提供了 equals() 和 hashcode(), 将它保存在map中后我们没有刻意的改变它.如果它的属性被改变,那么这个元素将永远不会被应用程序找到,但是map持有它的一个引用,因此这里会发生内存泄漏.

  总是让你的 custom key 不可变.

  例子 6: 内部数据结构

  1.   package com.example.memoryleak;

  2.   public class Stack {

  3.   privateint maxSize;

  4.   privateint[] stackArray;

  5.   privateint pointer;

  6.   public Stack(int s) {

  7.   maxSize = s;

  8.   stackArray = newint[maxSize];

  9.   pointer = -1;

  10.   }

  11.   public void push(int j) {

  12.   stackArray[++pointer] = j;

  13.   }

  14.   public int pop() {

  15.   return stackArray[pointer--];

  16.   }

  17.   public int peek() {

  18.   return stackArray[pointer];

  19.   }

  20.   publicboolean isEmpty() {

  21.   return (pointer == -1);

  22.   }

  23.   public boolean isFull() {

  24.   return (pointer == maxSize - 1);

  25.   }

  26.   public static void main(String[] args) {

  27.   Stack stack = new Stack(1000);

  28.   for(int ;i<1000;i++)

  29.   {

  30.   stack.push(i);

  31.   }

  32.   for(int ;i<1000;i++)

  33.   {

  34.   int element = stack.pop();

  35.   System.out.println("Poped element is "+ element);

  36.   }

  37.   }

  38.   }
复制代码


  在这里,当栈第一次增长后接着收缩时我们面临一个棘手的问题。实际上由于内部实现,栈内部有一个数组,对外部程序来说,栈的活动部分是指向该数组的指针。

  所以当堆栈增长到1000时,内部的数组也就被元素填满了,但后面当我们弹出所有元素后,指针指到了0,对应用程序来说它也变为空的了,但是内部的数组却还包含被弹出对象的引用。在Java中,我们叫它过期引用。过期引用是不能再次被引用的引用。

  这个引用不能被回收,因为数组中还保留着被弹出后就不需要的元素。

  为了解决这个问题,我们需要在弹出操作中把值设为null,这样这些对象才能被回收。

  1.   public int pop() {

  2.   int size = pointer--

  3.   int element= stackArray[size];

  4.   stackArray[size];

  5.   return element;

  6.   }
复制代码


  防止内存泄露的安全措施:

1.jpg


原文作者:佚名  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部