文档结构  
翻译进度:已翻译     翻译赏金:0 元     ¥ 我要打赏

Java 通过 GC(一个守护线程)隐性回收内存资源。GC 会定期检查有没有哪个对象是不可达的,准确来说,没有引用指向这个对象。如果有,GC 就会回收这块内存。

现在的问题是我们应不应该担心内存泄漏问题或者说 Java 怎么处理这个问题的?

关注一下定义:一个对象只有在无可到达(无法使用)且没有任何现有线程可以使用到它时才会被垃圾回收器回收。

所以如果一个对象在应用中没有使用但无意中被引用了,那它是不会被垃圾回收器回收的,也就存在内存泄漏的危险了。

GC关注这些不可达的对象,但是不能判断对象是否不再使用,对于应用中存在可能使用被回收的对象的情形,程序员应该格外注意相应的业务逻辑代码。可笑的小错误将会成为大问题。

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

例子 1:自动装箱

package com.example.memoryleak;
public class Adder {
       public long addIncremental(long l)
       {
              Long sum=0L;
               sum =sum+l;
               return sum;
       }
       public static void main(String[] args) {
              Adder adder = new Adder();
              for(long i;i<1000;i++)
              {
                     adder.addIncremental(i);
              }
       }
}
第 2 段(可获 0.66 积分)

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

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

例子2: 使用缓存

package com.example.memoryleak;
import java.util.HashMap;
import java.util.Map;
public class Cache {
       private Map<String,String> map= new HashMap<String,String>();
       public void initCache()
       {
              map.put("Anil", "Work as Engineer");
              map.put("Shamik", "Work as Java Engineer");
              map.put("Ram", "Work as Doctor");
       }
       public Map<String,String> getCache()
       {
              return map;
       }
       public void forEachDisplay()
       {
              for(String key : map.keySet())
              {
                String val = map.get(key);                 
                System.out.println(key + " :: "+ val);
              }
       }
       public static void main(String[] args) {            
              Cache cache = new Cache();
              cache.initCache();
              cache.forEachDisplay();
       }
}

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

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

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

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

例子 3:关闭连接

try
{
  Connection con = DriverManager.getConnection();
  …………………
  con.close();
}

Catch(Exception ex)
{
}

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

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

例子4: 使用构造方法

package com.example.memoryleak;
import java.util.HashMap;
import java.util.Map;
public class CustomKey {
       public CustomKey(String name)
       {
              this.name=name;
       }
       private String name;
       public static void main(String[] args) {
              Map<CustomKey,String> map = new HashMap<CustomKey,String>();
              map.put(new CustomKey("Shamik"), "Shamik Mitra");
              String val = map.get(new CustomKey("Shamik"));
              System.out.println("Missing equals and hascode so value is not accessible from Map " + val);
       }
}

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

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

例子 5: 可变的 Custom Key

package com.example.memoryleak;
import java.util.HashMap;
import java.util.Map;
public class MutableCustomKey {
       public MutableCustomKey(String name)
       {
              this.name=name;
       }
       private String name;
       public String getName() {
              return name;
       }
       publicvoid setName(String name) {
              this.name = name;
       }
       @Override
       public int hashCode() {
              final int prime = 31;
              int result = 1;
              result = prime * result + ((name == null) ? 0 : name.hashCode());
              return result;
       }
       @Override
       public boolean equals(Object obj) {
              if (this == obj)
                     return true;
              if (obj == null)
                     return false;
              if (getClass() != obj.getClass())
                     return false;
              MutableCustomKey other = (MutableCustomKey) obj;
              if (name == null) {
                     if (other.name != null)
                           return  false;
              } elseif (!name.equals(other.name))
                     return false;
              return true;
       }
       public static void main(String[] args) {
              MutableCustomKey key = new MutableCustomKey("Shamik");             
              Map<MutableCustomKey,String> map = new HashMap<MutableCustomKey,String>();
              map.put(key, "Shamik Mitra");
              MutableCustomKey refKey = new MutableCustomKey("Shamik");
              String val = map.get(refKey);
              System.out.println("Value Found " + val);
              key.setName("Bubun");
              String val1 = map.get(refKey);
              System.out.println("Due to MutableKey value not found " + val1);
       }
}
第 7 段(可获 0.99 积分)

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

总是让你的 custom key 不可变.

例子 6: 内部数据结构

package com.example.memoryleak;
public class Stack {
       privateint maxSize;
       privateint[] stackArray;
       privateint pointer;
       public Stack(int s) {
              maxSize = s;
              stackArray = newint[maxSize];
              pointer = -1;
       }
       public void push(int j) {
              stackArray[++pointer] = j;
       }
       public int pop() {
              return stackArray[pointer--];
       }
       public int peek() {
              return stackArray[pointer];
       }
       publicboolean isEmpty() {
              return (pointer == -1);
       }
       public boolean isFull() {
              return (pointer == maxSize - 1);
       }
       public static void main(String[] args) {
              Stack stack = new Stack(1000);
              for(int ;i<1000;i++)
              {
                     stack.push(i);
              }
              for(int ;i<1000;i++)
              {
                     int element = stack.pop();
                     System.out.println("Poped element is "+ element);
              }
       }
}
第 8 段(可获 0.73 积分)

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

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

第 9 段(可获 1.29 积分)

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

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

public int pop() {
              int size = pointer--
              int element= stackArray[size];
              stackArray[size];
              return element;
       }

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

Image title

第 10 段(可获 0.61 积分)

文章评论

合肥访客
胡言乱语的文章!
杭州访客
愚人节么?
访客
赞同上面两位。明明是自己持有了对象导致不能释放内存,建议先看下内存泄漏的定义。
另外,第一个自动封箱的例子,只会导致性能很差,哪里泄漏了,哪里有不合理的持有对象了……后面的例子也是各种问题,就不细说了。
ypddw
为啥不提交翻译纠正呢
深圳访客
误人子弟
成都访客
stackArray[size]; 这有什么用?