一 静态集合类
静态集合类,如 HashMap、LinkedList 等等。如果这些容器为静态的,那么它们的生命周期与 JVM 程序一致,则容器中的对象在程序结束之前不能被释放,从而造成内存泄漏。简单而言,长生命周期对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
package chapter03;
import java.util.ArrayList;
import java.util.List;
public class MemoryLeak {
static List list = new ArrayList();
// 该方法执行完后,obj 引用的对象发生内存泄漏
public void oomTest() {
Object obj = new Object(); // 局部变量
list.add(obj);
}
}
二 单例模式
单例模式,和静态集合导致内存泄漏原因类似,因为单例的静态特性,它的生命周期和 JVM 的生命周期一样长,所以如果单例对象如果持有外部对象引用,那么这个外部对象也不会被回收,那么就会发生内存泄漏。
三 内部类持有外部类
内部类持有外部类,如果一个外部类的实例的方法返回一个内存类的实例对象。
这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象不会被垃圾回收,这也造成内存泄漏。
四 各种连接,如数据库连接、网络连接和IO连接等
各种连接,如数据库连接、网络连接和 IO 连接等。
在对数据库进行操作时,首先需要建立与数据库的连接,当不再使用时,需要调用 close 方法来释放与数据库的连接。只有连接被关闭,垃圾回收器才会回收对应对象
否则,如果在访问数据库的过程中,对 Connection、Statement 或 ResultSet 不显示地关闭,将会造成大量的对象无法被回收,从而引起内存泄漏。
五 变量不合理的作用域
package chapter03;
public class UsingRandom {
private String msg;
public void receivemsg(){
// String msg; // 可以将 msg 放在方法内
readFromNet(); // 从网络中接收数据保存到 msg 中
saveDB(); // 把 msg 保存到数据库中
msg = null; // 加这个防止内存泄漏
}
}
六 改变哈希值
1 说明
2 代码
package chapter03;
import java.util.HashSet;
/**
* 演示内存泄漏
*/
public class ChangeHashCode {
public static void main(String[] args) {
HashSet set = new HashSet();
Person p1 = new Person(1001, "AA");
Person p2 = new Person(1002, "BB");
set.add(p1);
set.add(p2);
p1.name = "CC"; // 导致了内存的泄漏
set.remove(p1); // 对象哈希值发生变化,检索不到,导致删除失败
System.out.println(set);
set.add(new Person(1001, "CC"));
System.out.println(set);
set.add(new Person(1001, "AA"));
System.out.println(set);
}
}
class Person {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
if (id != person.id) return false;
return name != null ? name.equals(person.name) : person.name == null;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
3 运行结果——内存溢出,并且运行异常
[Person{id=1002, name=’BB’}, Person{id=1001, name=’CC’}]
[Person{id=1002, name=’BB’}, Person{id=1001, name=’CC’}, Person{id=1001, name=’CC’}]
[Person{id=1002, name=’BB’}, Person{id=1001, name=’CC’}, Person{id=1001, name=’CC’}, Person{id=1001, name=’AA’}]
4 结果分析
5 代码
package chapter03;
import java.util.HashSet;
/**
* 演示内存泄漏
*/
public class ChangeHashCode1 {
public static void main(String[] args) {
HashSet hs = new HashSet();
Point cc = new Point();
cc.setX(10); // hashCode = 41
hs.add(cc);
cc.setX(20); // 修改了计算 hash 的成员值。hashCode = 51 此行为导致了内存的泄漏
System.out.println("hs.remove = " + hs.remove(cc)); // 检索不到,返回 false
hs.add(cc);
System.out.println("hs.size = " + hs.size()); // size = 2
System.out.println(hs);
}
}
class Point {
int x;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + x;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Point other = (Point) obj;
if (x != other.x) return false;
return true;
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
'}';
}
}
6 运行结果——内存溢出,并且运行异常
hs.remove = false
hs.size = 2
[Point{x=20}, Point{x=20}]
7 结果分析
七 缓存泄露
1 说明
2 代码——内存泄漏
package chapter03;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
/**
* 演示内存泄漏
*/
public class MapTest {
static Map wMap = new WeakHashMap();
static Map map = new HashMap();
public static void main(String[] args) {
init();
testWeakHashMap();
testHashMap();
}
public static void init() {
String ref1 = new String("obejct1");
String ref2 = new String("obejct2");
String ref3 = new String("obejct3");
String ref4 = new String("obejct4");
wMap.put(ref1, "cacheObject1");
wMap.put(ref2, "cacheObject2");
map.put(ref3, "cacheObject3");
map.put(ref4, "cacheObject4");
System.out.println("String引用ref1,ref2,ref3,ref4 消失");
}
public static void testWeakHashMap() {
System.out.println("WeakHashMap GC之前");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("WeakHashMap GC之后");
for (Object o : wMap.entrySet()) {
System.out.println(o);
}
}
public static void testHashMap() {
System.out.println("HashMap GC之前");
for (Object o : map.entrySet()) {
System.out.println(o);
}
try {
System.gc();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HashMap GC之后");
for (Object o : map.entrySet()) {
System.out.println(o);
}
}
}
3 运行结果
String引用ref1,ref2,ref3,ref4 消失
WeakHashMap GC之前
obejct2=cacheObject2
obejct1=cacheObject1
WeakHashMap GC之后
HashMap GC之前
obejct4=cacheObject4
obejct3=cacheObject3
HashMap GC之后
obejct4=cacheObject4
obejct3=cacheObject3