面试题答案
一键面试常见导致内存泄漏的场景
- 静态集合类导致的内存泄漏:像
static HashMap
、static List
等,如果在其中不断添加对象,且这些对象不再被其他地方需要,但因为静态集合持有其引用,垃圾回收器无法回收这些对象,从而造成内存泄漏。 - 对象之间相互引用导致的内存泄漏:例如类A持有类B的引用,类B又持有类A的引用,且它们的生命周期很长。如果在程序中不再需要这两个对象,但因为相互引用,垃圾回收器无法回收它们,就会造成内存泄漏。
- 资源未关闭导致的内存泄漏:比如数据库连接(
Connection
)、文件流(FileInputStream
等)、网络连接(Socket
)等资源,如果使用完后没有正确关闭,这些对象会一直占用内存,导致内存泄漏。 - 内部类持有外部类引用导致的内存泄漏:如果内部类对象的生命周期比外部类对象长,且内部类持有外部类的引用,那么即使外部类对象不再被使用,由于内部类的存在,外部类对象也无法被垃圾回收,从而导致内存泄漏,常见于Android开发中的匿名内部类。
初步检测内存泄漏的方法
- 使用VisualVM:
- 启动VisualVM:可以在JDK的bin目录下找到
jvisualvm.exe
并启动。 - 连接到目标Java进程:在VisualVM界面中,找到并选中要监控的Java进程。
- 分析堆内存:在 “监视” 选项卡中,可以看到堆内存的使用情况。切换到 “堆Dump”,点击 “生成” 可以获取堆内存快照。通过分析快照,查看对象数量和大小,如果发现某些对象数量异常多或者占用内存过大且不符合程序逻辑,可能存在内存泄漏。
- 启动VisualVM:可以在JDK的bin目录下找到
- 使用JProfiler:
- 启动JProfiler:安装并启动JProfiler工具。
- 连接到Java进程:按照提示连接到要检测的Java进程。
- 监控内存使用:在JProfiler界面中,可以实时监控内存使用情况,包括对象的创建和销毁。使用 “内存视图” 等功能,分析对象的生命周期,如果发现某些对象长时间存在且没有被合理释放,可能存在内存泄漏。
- 代码中添加内存监控代码: 在代码中定时打印内存使用情况,例如:
public class MemoryMonitor {
public static void main(String[] args) {
long totalMemory = Runtime.getRuntime().totalMemory();
long freeMemory = Runtime.getRuntime().freeMemory();
long usedMemory = totalMemory - freeMemory;
System.out.println("Total Memory: " + totalMemory);
System.out.println("Free Memory: " + freeMemory);
System.out.println("Used Memory: " + usedMemory);
}
}
如果在程序运行过程中,发现已使用内存(usedMemory
)持续增长且没有合理的下降趋势,可能存在内存泄漏。