侧边栏壁纸
博主头像
搞钱拒绝ICU

行动起来,活在当下

  • 累计撰写 26 篇文章
  • 累计创建 8 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

内存泄漏与内存溢出

admin
2024-02-12 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

一、内存溢出

俗称 OOM,是指当程序请求分配内存时,由于没有足够的内存空间,从而抛出 OutOfMemoryError。

List<String> list = new ArrayList<>();
while (true) {
    list.add("OutOfMemory".repeat(1000)); // 无限增加内存
}

可能是因为堆、元空间、栈或直接内存不足导致的。可以通过优化内存配置、减少对象分配来解决。

二、内存泄漏

是指程序在使用完内存后,未能及时释放,导致占用的内存无法再被使用。随着时间的推移,内存泄漏会导致可用内存逐渐减少,最终导致内存溢出。

内存泄漏通常是因为长期存活的对象持有短期存活对象的引用,又没有及时释放,从而导致短期存活对象无法被回收而导致的。

class MemoryLeakExample {
    private static List<Object> staticList = new ArrayList<>();
    public void addObject() {
        staticList.add(new Object()); // 对象不会被回收
    }
}

三、内存泄漏原因

①、静态的集合中添加的对象越来越多,但却没有及时清理;静态变量的生命周期与应用程序相同,如果静态变量持有对象的引用,这些对象将无法被 GC 回收。

②、单例模式下对象持有的外部引用无法及时释放;单例对象在整个应用程序的生命周期中存活,如果单例对象持有其他对象的引用,这些对象将无法被回收。

③、数据库、IO、Socket 等连接资源没有及时关闭;

④、 ThreadLocal 的引用未被清理,线程退出后仍然持有对象引用;在线程执行完后,要调用 ThreadLocal 的 remove 方法进行清理。

四、内存泄漏排查

1、使用 jps -l 查看运行的 Java 进程 ID。

2、使用top -p [pid] 查看进程使用 CPU 和内存占用情况。

3、使用 top -Hp [pid] 查看进程下的所有线程占用 CPU 和内存情况。

4、抓取线程栈:jstack -F 666 > 666.txt,可以多抓几次做个对比。

666 为 pid,顺带作为文件名。

看看有没有线程死锁、死循环或长时间等待这些问题。

5、可以使用jstat -gcutil [pid] 5000 10 每隔 5 秒输出 GC 信息,输出 10 次,查看 YGCFull GC 次数。

通常会出现 YGC 不增加或增加缓慢,而 Full GC 增加很快。

或使用 jstat -gccause [pid] 5000 输出 GC 摘要信息。

或使用 jmap -heap [pid] 查看堆的摘要信息,关注老年代内存使用是否达到阀值,若达到阀值就会执行 Full GC。

如果发现 Full GC 次数太多,就很大概率存在内存泄漏了。

6、生成 dump 文件,然后借助可视化工具分析哪个对象非常多,基本就能定位到问题根源了。

执行命令 jmap -dump:format=b,file=heap.hprof 666 会输出进程 666的堆快照信息,保存到文件 heap.hprof 中。

7、使用图形化工具分析,如 JDK 自带的 VisualVM商业软件Jprofilemat等等

然后在结果观察内存占用最多的对象,找到内存泄漏的源头。

五、内存溢出问题排查

使用 jmap 命令手动生成 Heap Dump 文件:

jmap -dump:format=b,file=heap.hprof <pid>

然后使用 MAT、JProfiler 等工具进行分析,查看内存中的对象占用情况。

六、栈溢出

栈溢出发生在程序调用栈的深度超过 JVM 允许的最大深度时。

栈溢出的本质是因为线程的栈空间不足,导致无法再为新的栈帧分配内存。

当一个方法被调用时,JVM 会在栈中分配一个栈帧,用于存储该方法的执行信息。如果方法调用嵌套太深,栈帧不断压入栈中,最终会导致栈空间耗尽,抛出 StackOverflowError。

栈溢出场景

  1. 递归调用,尤其是没有正确的终止条件下,会导致递归无限进行。

  2. 如果方法中定义了特别大的局部变量,栈帧会变得很大,导致栈空间更容易耗尽。

0

评论区