Java内存泄漏
Posted Tim_Bergling
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java内存泄漏相关的知识,希望对你有一定的参考价值。
Java内存泄漏
解释
内存泄漏: 存在一些被分配的对象,满足两个特点:
- 对象是可达的:在有向图中,存在通路与之相连.
- 对象是无用的:程序以后不再使用这些对象.
结果:消耗越来越多的内存资源,最终导致OutOfMemoryError
。
与C++的区别:
- C++:对象被分配内存空间,却不可达.
- Java:不可达的对象由GC回收.
示例:
Vector v = new Vector(10);
for(int i = 0; i < 10;i++){
Object o = new Object();
v.add(o);
o = null;
}
对象可以分为两种:有引用的对象和无引用的对象。
其中无引用的对象由GC进行回收。
有引用的对象不会被GC回收,即使这些对象不会再被使用。
Java堆泄漏
注:通过-Xms<size>
和-Xmx<size>
设置堆的最小值和最大值,使得容易的重现堆泄漏。
几种常见的泄漏情景:
存储对象引用的静态域
示例:
// JVM参数设定:-Xms10m -Xmx10m
// 会出现OutOfMemoryError
private Random random = new Random();
public static final ArrayList<Double> list = new ArrayList<>(1000000);
@Test
public void test() throws InterruptedException {
for (int i = 0; i < 1000000; i++) { // 向static域加入对象
list.add(random.nextDouble());
}
System.gc();
Thread.sleep(10000); // 使得GC得以执行
}
使用非static域存放对象:
// 不会出现OutOfMemoryError
@Test
public void test() throws InterruptedException {
addelement();
System.gc();
Thread.sleep(10000);
}
private void addelement(){
ArrayList<Double> list = new ArrayList<>(1000000); // 使用非static域存储对象
for (int i = 0; i < 1000000; i++) {
list.add(random.nextDouble());
}
}
解决对策:
- 谨慎使用
static
变量,其生命周期与JVM的生命周期相同。 - 谨慎使用集合类,其常常存储大量不会再被使用到的对象引用。
对长字符串使用String.intern()
示例:
@Test
public void test() throws InterruptedException, FileNotFoundException {
Thread.sleep(15000);
String str = new Scanner(new File("scr/test.txt"),"UTF-8").useDelimiter("\A").next();
str.intern(); // 向字符串常量池中加入长字符串
System.gc();
Thread.sleep(16000);
}
解决对策:
String.intern()
方法将字符串存储在永久代空间中,若需要将大量长字符串存入字符串常量池,可通过最大永久代大小来避免异常:-XX:MaxPermSize=<size>
。- 使用Java 8:其中永久代被元空间取代,使用
String.intern()
不会导致异常。
未关闭的流
// 流在使用后没有被关闭
@Test
public void test() throws IOException {
String str = "";
URLConnection conn = new URL("http://norvig.com/big.txt").openConnection();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
while (br.readLine() != null)
str += br.readLine();
}
注:未关闭的流会导致两种类型的泄漏
- 底层资源泄漏:文件描述符(file descriptors),打开的连接(open connections)。
- JVM内存泄漏
解决对策:
- 使用完流后手动关闭。
- 使用Java 8中的自动关闭特性。
// 无需在finally中手动关闭
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
// ...
} catch (IOException e) {
e.printStackTrace();
}
未关闭的连接
与未关闭的流类似。
一般是连接数据库或FTP后未关闭。
// 连接FTP后未关闭
@Test
public void test() throws IOException {
URL url = new URL("ftp://speedtest.tele2.net");
URLConnection urlc = url.openConnection();
InputStream is = urlc.getInputStream();
String str = " ";
}
解决对策:
- 使用完连接后手动将连接关闭。
将没有hashCode()
和equal()
的对象加入到Hashset
中
当一个对象没有重写hashCode()
和equals()
方法时,在向HashSet
集合类中添加重复的对象时,其不会忽略到重复的对象。
@Test
public void test(){
Map<Object, Object> map = System.getProperties();
while (true){
map.put(new newObject("key"),"value");
}
}
// 没有重写equals和hashCode的自定义类
class newObject{
private String key;
public newObject(String key){
this.key = key;
}
}
解决方法
- 重写
equals()
和hashCode()
方法。 - 或者使用插件lombok。
查找泄漏源
- 打开垃圾收集器的详细日志信息。JVM参数中添加
-verbose:gc
。 - 使用监控工具(Visual VM)。
- 检查代码。
参考:
以上是关于Java内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章