文章目录

  • 简介
  • 何为内存泄漏
  • 内存泄漏带来的问题
  • 导致内存泄漏的原因
  • 内存泄漏分析常用手段
  • 内存泄漏分析与实践
  • 静态字段导致的内存泄漏
  • 不正确的hashCode和equals实现
  • 内部类导致的内存泄漏
  • 小节面试分析


简介

何为内存泄漏

动态分配的内存空间,在使用完毕后未得到释放,结果导致一直占据该内存单元,直到程序结束。这个现象称之为内存泄漏。因此良好的代码规范,可以有效地避免这些错误。

内存泄漏带来的问题

1)长时间运行,程序会变卡,性能严重下降。
2)OutOfMemoryError错误,系统直接挂掉。

导致内存泄漏的原因

1)大量使用静态变量(静态变量与程序生命周期一样)
2)IO/连接资源用完没关闭(记得执行close操作)
3)内部类的使用方式存在问题(实力内部类或默认引用外部类对象)
4)缓存(Cache)应用不当(尽量不要使用强引用)
5)ThreadLocal应用不当(用完记得执行remove操作)

内存泄漏分析常用手段

  1. 应用内存分析工具 JProfiler, YourKit, Java VisualVM等。
  2. 在开发阶段时或者在测试环节,增加压力测试。
  3. 认真对待开发工具给出的告警提示,该关闭的资源尽早关闭。
  4. 选择合适的时机进行代码 review。

通俗地说,我们可以将内存泄漏视为一种疾病,如果不治愈,随着时间的推移,它可能导致致命的应用程序崩溃。内存泄漏很难解决,发现它们需要对 Java 语言的复杂掌握和掌握。在处理内存泄漏时,没有一种万能的解决方案,因为泄漏可能通过各种不同的事件发生。

但是,如果我们采用最佳实践并定期执行严格的代码排查和分析,那么我们可以将应用程序中内存泄漏的风险降到最低。

内存泄漏分析与实践

静态字段导致的内存泄漏

演示通过静态字段造成的内存泄漏,代码如下:

package com.java.jvm.leak;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class StaticMemoryLeakTests {
    // 使用静态变量去存储大量的数据
    public  static List<byte[]> bytes = new ArrayList<>();
    public void makeBytes() {
        for (int i = 0; i < 1000 * 200; i++) {
            bytes.add(new byte[1024]);
        }
        System.out.println("Debug point 2");
    }
    public static void main(String[] args)throws Exception {
        // 给时间我打开 visualVM 监控工具
        TimeUnit.SECONDS.sleep(10);
        System.out.println("Debug point 1");
        new StaticMemoryLeakTests().makeBytes();
        System.out.println("Debug point 3");
        for (; ; ) {

        }
    }
}

运行代码,此时打开jdk/bin目录的jvisualvm,基于VisualVM 分析这段程序执行期间堆内存的使用情况。当程序运行从Debug point 1 到 Debug point 2 的时候,如预期的那样,堆内存增加了。 但当我们从Debug point 3 离开 makeBytes() 方法时,此时 JVM 不一定达到了触发 GC 的条件, 可以手动执行 Perform GC, 发现占用的Heap 内存并没有得到释放。

java内存泄漏 日志 java内存泄漏分析_System


演示非static属性值的存储以及内存分析,代码如下:

package com.java.jvm.leak;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class NoStaticMemoryLeakTests {
    // 使用非静态变量去存储大量的数据
    public List<byte[]> bytes = new ArrayList<>();
    public void makeBytes() {
        for (int i = 0; i < 1000 * 200; i++) {
            bytes.add(new byte[1024]);
        }
        System.out.println("Debug point 2");
    }
    public static void main(String[] args)throws Exception {
        // 给时间我打开 visualVM 监控工具
        TimeUnit.SECONDS.sleep(10);
        System.out.println("Debug point 1");
        new NoStaticMemoryLeakTests().makeBytes();
        System.out.println("Debug point 3");
        for (; ; ) {
        }
    }
}

运行代码,此时打开jdk/bin目录的jvisualvm,基于VisualVM 分析这段程序执行期间堆内存的使用情况。当程序运行从Debug point 1 到 Debug point 2 的时候,如预期的那样,堆内存增加了。 但当我们从Debug point 3 离开 makeBytes() 方法时,此时 JVM 不一定达到了触发 GC 的条件, 可以手动执行 Perform GC, 发现占用的Heap 内存被释放了。

java内存泄漏 日志 java内存泄漏分析_java内存泄漏 日志_02

不正确的hashCode和equals实现

当我们将一些pojo对象作为HashMap的key或者直接将pojo对象存储到HashSet集合时,假如没有正确重写equals() 和 hashCode()方法,可能就会出现内存泄漏问题。例如:
当我们将一些pojo对象作为HashMap的key或者直接将pojo对象存储到HashSet集合时,假如没有正确重写equals() 和 hashCode()方法,可能就会出现内存泄漏问题。例如:

package com.java.jvm.leak;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

class Pig {
    private String name;
    public Pig(String name) {
        this.name = name;
    }

}
public class IncorrectHashAndEqualsTests {
    public static void main(String[] args) throws Exception{
  TimeUnit.SECONDS.sleep(10);
        Map<Pig, Integer> pigs = new HashMap<>();
        for (int i = 0; i < 10000 * 1000; i++) {
            pigs.put(new Pig("佩奇"), i);
            TimeUnit.MILLISECONDS.sleep(2);
        }
        System.out.println(pigs.size());
    }
}

一般存储到缓存的对象,都要重写hashCode()和 equals()方法。假设我们没有覆写,后果真的严重。

在Pig类中重写HashCode和equals方法,例如:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Pig pig = (Pig) o;
    return Objects.equals(name, pig.name);
}

@Override
public int hashCode() {
    return Objects.hash(name);
}

内部类导致的内存泄漏

在应用程序中使用这个内部类的对象不当,导致的内存泄漏,例如:

package com.java.jvm.leak;

class OuterClass {
    private int o;
    private byte[] bigObject = new byte[1024 * 10]; 
    class InnerClass {
        private int i;
        int add() {
            return i++;
        }
    }
}
public class InnerLeakTests {
    public static void main(String[] args) {
            OuterClass o = new OuterClass();
            OuterClass.InnerClass innerClass = o.new InnerClass();
            innerClass.add();
            o=null;
            System.gc();
    }
}

小节面试分析

1)何为内存泄漏?
2) 内存泄漏可能会带来什么问题?
3)导致内存泄漏的原因有哪些?
4) 内存泄漏分析常用的手段有哪些?