V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zx9481
V2EX  ›  Java

Java 应用内存不断增长

  •  2
     
  •   zx9481 · 2024-01-12 08:43:15 +08:00 · 9342 次点击
    这是一个创建于 372 天前的主题,其中的信息可能已经有所发展或是发生改变。

    启动参数如下

    java -server -Xms2048m -Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/xxx/heapError -jar xxx.jar --spring.profiles.active=prod --server.port=9551
    

    项目启动一周后内存就已经 2.6g 了。。。也没有出现过 oom 异常,请教大家应该如何排查解决呢?

    第 1 条附言  ·  2024-01-12 20:52:22 +08:00

    jdk版本如下

    java version "1.8.0_361"
    Java(TM) SE Runtime Environment (build 1.8.0_361-b09)
    Java HotSpot(TM) 64-Bit Server VM (build 25.361-b09, mixed mode)
    
    第 2 条附言  ·  2024-01-17 17:48:25 +08:00

    在听取了 @qiubinren 55楼的建议后,我追加了几个jvm参数,目前项目的内存没有出现溢出的情况 新的启动参数如下:

    java -server -Xms1024m -Xmx2048m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx/heapError -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:NewRatio=1 -XX:SurvivorRatio=30 -XX:+UseParallelGC -XX:+UseParallelOldGC -jar /xxx.jar --spring.profiles.active=prod --server.port=1234
    

    -Xms1024m: 设置JVM启动时的初始堆内存大小为1024MB。堆内存用于存储Java对象。

    -Xmx2048m: 设置JVM最大堆内存大小为2048MB。这是JVM可以用于对象存储的最大内存限制。

    -XX:MetaspaceSize=128m: 设置元空间的初始大小为128MB。元空间用于存储类元数据,并且是在Java 8中取代永久代(PermGen)的内存区域。

    -XX:MaxMetaspaceSize=512m: 设置元空间的最大大小为512MB。这是元空间可以增长到的最大内存限制。

    -XX:+HeapDumpOnOutOfMemoryError: 当JVM抛出内存不足错误(OutOfMemoryError)时,自动生成堆转储文件。这有助于事后分析内存问题。

    -XX:HeapDumpPath=/xxx/heapError: 指定堆转储文件的保存路径。如果JVM抛出内存不足错误,转储文件将被保存在这个路径下。

    -XX:+PrintGCDateStamps: 打印垃圾收集日志时附带时间戳,有助于分析垃圾收集发生的时间。

    -XX:+PrintGCDetails: 打印详细的垃圾收集日志。这提供了关于垃圾收集活动的更多信息。

    -XX:NewRatio=1: 设置年轻代(Young Generation)与老年代(Old Generation)的比例。这里设置为1,意味着年轻代和老年代的大小将相等。

    -XX:SurvivorRatio=30: 设置年轻代中Eden区与一个Survivor区的大小比例。这里的设置为30,意味着Eden区将是Survivor区大小的30倍。

    -XX:+UseParallelGC: 启用并行垃圾收集器。这种收集器在多核处理器上表现良好。

    -XX:+UseParallelOldGC: 对老年代使用并行垃圾收集。

    64 条回复    2024-01-22 09:39:08 +08:00
    miniliuke
        1
    miniliuke  
       2024-01-12 08:45:30 +08:00
    堆 dump 看看
    twofox
        2
    twofox  
       2024-01-12 08:46:27 +08:00
    dump 下来去分析内存
    zed1018
        3
    zed1018  
       2024-01-12 08:48:52 +08:00   ❤️ 7
    找算命先生测算一下
    cxshun
        4
    cxshun  
       2024-01-12 08:56:24 +08:00
    超出堆内存大小,应该就是堆外内存的问题了。用上面兄弟的 jmap dump 出来看看。但要注意会触发 FGC 。
    me1onsoda
        5
    me1onsoda  
       2024-01-12 08:59:41 +08:00
    xmx 限制了,那就是堆外泄露了呗
    visper
        6
    visper  
       2024-01-12 09:09:31 +08:00
    没挂能跑就行,管它呢。配置的 Xmx 只是堆内存大小,jvm 自己使用的内存,文件句柄啊,线程本身内存啊的都不在这里。
    dengji85
        7
    dengji85  
       2024-01-12 09:09:40 +08:00
    蹲一下解决方案,我有个开发的应用也是这样,时间越长内存越高,慢慢稳定在约束的最大内存,永远不会自动是释放内存,推测是写代码习惯问题,导致对象得不到释放
    cnzjl
        8
    cnzjl  
       2024-01-12 09:14:19 +08:00
    arthas 在线看一下内存占用情况
    halov
        9
    halov  
       2024-01-12 09:14:34 +08:00
    我们公司现在使用 消息中间件 nats 也有这种问题 😅
    cheng6563
        10
    cheng6563  
       2024-01-12 09:24:13 +08:00   ❤️ 4
    管他呢,搞个定时重启完事
    chenPiMeiHaoChi
        11
    chenPiMeiHaoChi  
       2024-01-12 09:26:18 +08:00
    查查有没有静态的集合,一般这种稳定增长的就是静态 map 或者 list 越来越大。不过我也建议写个定时重启算了。
    timeisweapon
        12
    timeisweapon  
       2024-01-12 09:29:34 +08:00
    这个得看你的项目有多大,小项目内存持续增大,大概率是代码问题,没有及时释放内存,需要看 dump
    wxw752
        13
    wxw752  
       2024-01-12 09:33:56 +08:00
    巧了,我写的其中一个 netty 也是这样,后来实在找不到问题了,每两个月重启一下
    salmon5
        14
    salmon5  
       2024-01-12 09:37:12 +08:00
    每天重启下就好了,java 就这样的
    jorneyr
        15
    jorneyr  
       2024-01-12 09:49:24 +08:00
    @wxw752 巧了,我写的其中一个 netty 也是这样,后来实在找不到问题了,每两个月重启一下

    Netty 大量使用了堆外内存。
    kaedea
        16
    kaedea  
       2024-01-12 09:51:37 +08:00 via Android
    hprof
    salmon5
        17
    salmon5  
       2024-01-12 09:53:45 +08:00
    启动参数加上-XX:NativeMemoryTracking=summary ,然后 jcmd pid VM.native_memory 分析下
    Shinu
        18
    Shinu  
       2024-01-12 09:55:24 +08:00
    巧了, 我也遇到过这个问题, 不过增长的慢, 离职了都没想着去解决这个问题. 蹲下排查方法
    starlin
        19
    starlin  
       2024-01-12 09:57:45 +08:00   ❤️ 1
    别理解错了,xmx 只限制了堆的最大值,还有非堆,线程栈,代码缓存,jvm 本身运行所需要的内存等,如有必要可以配置 NMT 进行分析,但是注意有性能损耗
    676529483
        20
    676529483  
       2024-01-12 09:57:54 +08:00
    mark 等个大佬,公司项目也是,看 arms 堆内存+非堆不到占用的内存,怀疑堆外又没办法排查,最后只能重启
    me1onsoda
        21
    me1onsoda  
       2024-01-12 09:58:56 +08:00
    @wxw752 @jorneyr netty 这种应用内存就是跟着连接数涨的,吃的是堆外内存,一条连接一个读写缓冲区。gc 管不着
    burymme11
        22
    burymme11  
       2024-01-12 10:06:35 +08:00
    @wxw752
    巧了,我们把网关升级到 spring gateway 之后,也出现这问题,就是 netty 申请的堆外内存导致的。
    Scarb
        23
    Scarb  
       2024-01-12 10:08:39 +08:00   ❤️ 1
    2.6G 内存,超过堆内存的上限 2G ,堆外内存泄漏了。先 smaps 看下堆内存占用多少,算出堆外内存占用。堆外内存如果没特殊配置,最大也只能占 2G ,可能很快就 OOM 了。

    堆外内存很难定位泄漏点,给一个参考:
    https://tech.meituan.com/2018/10/18/netty-direct-memory-screening.html

    实在不行只能把内存用 gdb dump 出来,然后强行查看里面的内容,推测可能的泄漏原因。
    salmon5
        24
    salmon5  
       2024-01-12 10:15:25 +08:00
    @Scarb #23 你说的这个,又是 smaps ,又是 gdb ,大部分的 java 开发不懂
    salmon5
        25
    salmon5  
       2024-01-12 10:16:22 +08:00
    @salmon5 不是人均阿里 P7 P8 水平,大部分还是 CRUD
    aLazarus
        26
    aLazarus  
       2024-01-12 10:18:16 +08:00
    @salmon5 #24 不过还是可以学到知识
    LowBi
        27
    LowBi  
       2024-01-12 10:28:05 +08:00 via iPhone
    哈哈哈 我的是内存正常 但是十几天后占用内存变小 随后宕机 问题是内存泄露 高并发的读取数据库导致有些线程超时未能关掉 好烦 不多线程的话 方法执行完需要三分多钟 现在还没想到长期运行的办法
    JYii
        28
    JYii  
       2024-01-12 10:40:55 +08:00
    @salmon5 #24 想问下排查内存泄漏这种问题对于 Java 开发是什么层次。
    曾经解决过公司一个服务的内存泄漏,堆外问题确实很难定位到,从简单的 NativeMemoryTracking ,pmap 查看内存分配,监控栈函数调用,jeprof 对比堆内分配,最终定位到框架的问题,确实在对应版本 issue 中找到,升级后解决。
    trcnkq
        29
    trcnkq  
       2024-01-12 10:41:24 +08:00   ❤️ 6
    * JVM 不是它需要多少内存,才占用多少内存;而是在 -Xmx 允许的前提下,只要你系统还有空余,它就会大方地申请占用;而且即使之前申请了,后续不需要这么多了,它也不会及时退还给 OS ;
    * 你可以通过 jhsdb jmap --heap --pid ${jvm_pid} 来确认你的应用当前实际只 需要/占用 多少内存;
    * 如果你希望让 JVM 需要多少内存,才占用多少内存,多占用了就及时释放,可以通过调整 -XX:MinHeapFreeRatio -XX:MaxHeapFreeRatio 来实现。
    trcnkq
        30
    trcnkq  
       2024-01-12 10:42:41 +08:00
    当然上面所说,前提是你的应用没有内存泄漏。
    JYii
        31
    JYii  
       2024-01-12 10:42:44 +08:00
    @salmon5 #24 对于一些非重要服务,楼上的低频时段重启确实简单暴力,排查内存泄漏问题起码两周起步
    salmon5
        32
    salmon5  
       2024-01-12 10:44:33 +08:00
    @JYii #28 P7 P8 我说的可能有点夸张了,但实际中大部分 Java 开发不会(不想浪费时间或者没有能力)这么排查,大部分就是扩容服务器、容器内存+定期重启解决
    storyxc
        33
    storyxc  
       2024-01-12 10:45:54 +08:00
    @Scarb 学到了
    nothingistrue
        34
    nothingistrue  
       2024-01-12 10:48:10 +08:00
    -Xms -Xmx 限制的是堆内存,JVM 不止有堆内存,JVM 内存大于 Xmx 是正常情况。

    堆内存只是用来存储对象的成员变量的,对象的方法运行期间使用的基本类型变量(包括数组),要占用栈内存,这些是随运行动态申请和释放的。如果是服务器负荷期间内存升高,无需理会,负荷降了就会自己下去。

    Java 类定义,类的成员方法的函数定义,类的静态变量,也是要占用内存的。这些内存通常是一旦加载就不会释放(具体取决于类加载器),这种情况会导致 JVM 启动一段时间后内存就略微增长。但这种情况也请无需理会,首先这是 JVM 的职责,其次就算它真是屎山你也不该去碰。
    JYii
        35
    JYii  
       2024-01-12 10:49:21 +08:00
    @salmon5 #32 我没有大厂经验,想了解下现在的行情。年后要重新找工作(允悲
    ChaYedan666
        36
    ChaYedan666  
       2024-01-12 10:49:45 +08:00
    堆外内存设置 JVM 管不着,项目迭代快就懒得管了(基本一两周就会上线一次),没崩就行
    Arumoh
        37
    Arumoh  
       2024-01-12 10:57:03 +08:00
    换 openj9
    SilenceLL
        38
    SilenceLL  
       2024-01-12 10:57:32 +08:00
    看内存快照,里面看下 dominator_tree 最大对象是不是有啥问题
    joyhub2140
        39
    joyhub2140  
       2024-01-12 11:01:36 +08:00
    很好奇,都说堆外内存泄露的,难不成都是用 jni 开发 Java 项目嘛?
    falsemask
        40
    falsemask  
       2024-01-12 11:09:56 +08:00
    你的 jdk 版本号多少,我遇到过 java8 低版本 bug ,会导致堆外内存一直涨
    diagnostics
        41
    diagnostics  
       2024-01-12 11:20:22 +08:00
    @joyhub2140 哥们,线程不占内存?
    diagnostics
        42
    diagnostics  
       2024-01-12 11:21:44 +08:00
    -XMX 定好了,那大概率是堆外,不确定你这是 RSS ,还是 COMMITED 的 2.6G ,建议开 NMT 然后看一下

    假如没有 native memory leak ,大概率是有线程泄漏了,导致内存泄漏了
    diagnostics
        43
    diagnostics  
       2024-01-12 11:25:34 +08:00   ❤️ 1
    @JYii #28 层次是和别人对比出来的

    例如你觉得自己底层厉害,想找基础框架的,那么和基础框架组的人对比,你的能力是什么范围,懂底层的多少东西,如果只会内存泄漏这一个,其他的不熟悉,那你定级就比较低。假如你 JVM 精通、线程模型精通、数据结构精通,网络精通,那又是另一个层面。

    排查问题是一种能力,根据知识来设计软件架构又是一种能力
    twofox
        44
    twofox  
       2024-01-12 11:28:53 +08:00
    @nothingistrue 我觉得你说的有道理。xmx 确实只限制堆内存。内存的问题就应该交给 JVM 处理。再怎么调优都比不过新的 jdk 带的垃圾收集器

    优先排查代码问题,再考虑 JVM 调优

    知乎的这篇文章可能对 OP 有帮助
    https://zhuanlan.zhihu.com/p/432258798
    JYii
        45
    JYii  
       2024-01-12 11:34:30 +08:00
    @diagnostics #43 OK 了解,多谢回复,学习成长路漫漫。(后面几个精通给我干懵了
    chenfang
        46
    chenfang  
       2024-01-12 11:44:08 +08:00
    2.6G 如果不出现内存报警,要么你的 2.6G 不准,要么有堆外内存(比如 netty 可以创建堆外内存),

    同时也建议看看 gc 次数频繁不频繁

    你也说了是一周之后 2.6G,但是这个并没有什么参考,你需要看这一周的内存变化,来分析情况
    ASpiral
        47
    ASpiral  
       2024-01-12 12:08:58 +08:00
    起个定时任务强制 full gc ,都不用重启了/dog
    nananqujava
        48
    nananqujava  
       2024-01-12 13:13:53 +08:00
    先升级 jdk 版本看看
    iamhucong
        49
    iamhucong  
       2024-01-12 13:40:18 +08:00
    多对比几次 smaps 看堆外内存有没有持续上涨
    Dongxiaohao
        50
    Dongxiaohao  
       2024-01-12 14:17:07 +08:00 via Android
    @JYii 笑死了,当初我们开发的一个项目,根据模板渲染 Excel 表格,里面的图片很多(五六十张吧),最后转成 PDF 导出,导致这个接口但凡调用一次就 fullgc 几次,最后找不出问题就写了个定时任务隔一段时间重启服务😂
    JYii
        51
    JYii  
       2024-01-12 14:29:21 +08:00
    @Dongxiaohao #50 fullGC 频繁,代码不太烂的话,加内存就好了。(省心省力
    ZSeptember
        52
    ZSeptember  
       2024-01-12 14:34:13 +08:00
    没有监控吗。
    一般就是 dump 下来,使用 memory analyzer 分析一下
    qiubinren
        53
    qiubinren  
       2024-01-12 18:22:20 +08:00
    项目里面有自己用 netty 直接内存池进行分配和释放么?用了加-Dio.netty.leakDetection.level=PARANOID 参数,检测下 netty 侧内存有没有泄漏,隔段时间在日志里搜 LEAK 就行了。
    如果没有直接玩 netty 内存池,加 @salmon5 #17 提到的参数分析 NMT ,多收集几次,看下具体哪个区域在不断增大,收集更多的信息,另外最好把 jdk 版本贴出来,遇到过 openjdk 连续几个小版本反射导致内存泄漏,如果 jdk 小版本版本号太小,最好直接升级到小版本较高的版本
    zx9481
        54
    zx9481  
    OP
       2024-01-12 20:56:59 +08:00
    感谢 项目中没有用到 netty 我去加启动参数看看
    qiubinren
        55
    qiubinren  
       2024-01-12 22:47:33 +08:00
    @zx9481 我上面没有细看你的命令,直接断定你的程序存在直接内存泄漏,这其实不太对。又看了眼你的命令,堆内存给了 2G ,一周跑下来 2.6G ,其实感觉很可能是正常的,因为命令里只限制堆内存,其他区域并没有任何限制,至少把 MaxDirectMemorySize 、ReservedCodeCacheSize 、MaxMetaspaceSize 这几个参数填了。如果还限制不住,可以考虑增加-Djdk.nio.maxCachedBufferSize 、-DMALLOC_ARENA_MAX 、-DMALLOC_MMAP_THRESHOLD_这些参数的配置,并且给比较小的值,如果还是不断增长,再考虑内存泄漏。
    duron600
        56
    duron600  
       2024-01-12 22:57:31 +08:00
    而内存的总量保持不变。
    susuper
        57
    susuper  
       2024-01-12 23:44:11 +08:00
    是如何确认内存不断增大呢,通过 top 吗
    hancai
        58
    hancai  
       2024-01-13 00:27:58 +08:00
    老生常谈的问题了, k8s limit pod 的内存限制, 触发 OOM 就释放了,就是这么粗暴。
    Ericcccccccc
        59
    Ericcccccccc  
       2024-01-13 01:08:21 +08:00
    先 dump 简单分析下再看其它问题.
    PDX
        60
    PDX  
       2024-01-13 08:57:41 +08:00
    @wxw752 有 buffer 没释放掉
    dyv9
        61
    dyv9  
       2024-01-13 12:56:47 +08:00 via Android
    7* 24 后台线程不能在 循环里声明变量,否则内存会爆。
    alex8
        62
    alex8  
       2024-01-13 16:08:13 +08:00
    感觉没问题,还有堆外内存呢,Metadata ,Codecache 等。使用中 jit 会把热点字节码编译成机器码放到 codecache 中,这个增长不会一直持续。
    Znemo
        63
    Znemo  
       2024-01-13 21:48:09 +08:00
    @wxw752 Netty 的 buffer 是基于计数器来决定是否释放的,每个 handler 有义务对不再使用的 buffer 做减少引用的操作,继承了 SimpleChannelInboundHandler 的话会自动执行一次减少引用的行为,在 handler 链的末端会再次做一次减少引用的动作,如果 handler 链执行完,buffer 的引用计数为 0 就会被释放,所以有些业务需要会通过 ReferenceCountUtil.retain() 增加引用计数,避免 buffer 被释放,不小心会导致 buffer 泄露,实际上在 netty 中已经内置了泄露分析的工具,可以考虑在测试环境添加 io.netty.leakDetectionLevel 参数来分析是否存在 buffer 泄露。
    zx9481
        64
    zx9481  
    OP
       362 天前
    目前解决了哈 感谢大家😘
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1192 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 18:18 · PVG 02:18 · LAX 10:18 · JFK 13:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.