千家信息网

如何排查JVM内存过高的问题

发表于:2026-11-04 作者:千家信息网编辑
千家信息网最后更新 2026年11月04日,这篇文章主要讲解了"如何排查JVM内存过高的问题",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何排查JVM内存过高的问题"吧!内存过高一般有两种情况
千家信息网最后更新 2026年11月04日如何排查JVM内存过高的问题

这篇文章主要讲解了"如何排查JVM内存过高的问题",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"如何排查JVM内存过高的问题"吧!

内存过高一般有两种情况:内存溢出和内存泄露

  • 内存溢出: 程序分配的内存超过物理机的内存大小,导致无法继续分配内存,出现OOM报错

  • 内存泄露: 不再使用的对象一直占据着内存不释放,导致这块内存浪费掉,久而久之,内存泄露的对象堆积起来,也会导致物理机的内存被耗尽,出现OOM报错

具体操作

如何监控JVM,我们可以通过一个案例在了解一些实际当中的操作,大家可以看到下面的代码,下面的代码只是模拟了当中的一个场景,一个风险控制的场景,一般银行或者第三方公司在向一个人发放贷款的时候,会调用这个人的征信已经还款能力,给出响应的评级。

import java.math.BigDecimal;import java.util.ArrayList;import java.util.Date;import java.util.List;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class FullGCTest {    //模拟银行卡的类    private static class CardInfo {        //小农的银行卡信息记录        BigDecimal price = new BigDecimal(10000000.0);        String name = "牧小农";        int age = 18;        Date birthdate = new Date();        public void m() {}    }    //线程池 定时线程池    //50个,然后设置 拒绝策略    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,            new ThreadPoolExecutor.DiscardOldestPolicy());    public static void main(String[] args) throws Exception {        executor.setMaximumPoolSize(50);        for (;;){            modelFit();            Thread.sleep(100);        }    }    /**     * 对银行卡进行风险评估     */    private static void modelFit(){        List taskList = getAllCardInfo();        //拿出每一个信息出来        taskList.forEach(info -> {            // do something            executor.scheduleWithFixedDelay(() -> {                //调用M方法                info.m();            }, 2, 3, TimeUnit.SECONDS);        });    }    private static List getAllCardInfo(){        List taskList = new ArrayList<>();        //每次查询100张卡出来        for (int i = 0; i < 100; i++) {            CardInfo ci = new CardInfo();            taskList.add(ci);        }        return taskList;    }}

程序的设计其实比较简单,就是我们用信用卡的案例来进行说明,比如CardInfo就是信用卡类,我们把这个人对应的信用卡的记录都调用出来,之后做一些自己响应的业务处理方法来对它进行处理和计算,来看看我们这个模型是否符合modelFit,具体怎么做呢,在应用程序中有一个类是CardInfo,有一个方法叫做getAllCardInfo,每次都是拿100个出来,拿100个之后用线程池做计算,线程池用的是ScheduledThreadPoolExecutor (定时任务),new出来线程池之后,50个线程池,然后做对应的业务逻辑处理,会调用 modelFit(),使用100毫秒模拟业务的停顿。

首先我们需要使用javac 命令将Java文件进行编译 javac FullGCTest.java进行编译,然后打印GC日志,进行风险监控

打印GC日志:java -Xms200M -Xmx200M -XX:+PrintGC FullGCTest

怎么知道JVM内存过高?

在公司里面,如果遇到了JVM内存过高的情况,那么一般是运维团队首先受到报警信息,然后通知对应的开发人员去查看,那么开发人员应该如何查看,或者怎么样去排查呢?

1、top 查看进程

受到报警信息后,拿top命令去查询

[root@root ~]# top

查看内存不断增长,CPU占用率居高不下的。top后你会看到它的PID(31061)。它占比比较高。

2、top -Hp 查看线程

找到CPU占用比较高的进程PID,这里我们以 java 的进程为例 使用命令 top -Hp 31061,这个时候它会把这个进程里面所有的线程全部线程都罗列出来吗,这些都是Java这个进程里面内部的一些线程,如下图所示:

我们会看到每个线程的占比都差不多,偶尔会有某一个线程比较高,在某些线程占得比较高的时候,这个小例子最终会是垃圾回收的线程占得比较高,因为垃圾回收不过来了,所以需要不停的来回回收,每次都回收一点点,实际这种例子里面非常有可能是你业务逻辑线程,那一块的业务逻辑线程占比非常高,这是时候就需要用到另外的命令--jstack

3、jstack

当我们使用 top -Hp 知道了是哪个线程后,我们下一步就可以使用 jstack 命令,比如我们要查看31083这个线程号,31061是我们的进程PID,我们要定位某一个线程cpu的占比会比其他cpu高很多,那么我们就要定位这个线程里面到底是什么样的问题的时候,就需要把这个线程号(31083)记下来。

因为 jstack 用到的线程号是16进制的,所以我们需要把31083的10进制转换成16进制才可以

特点:

  • 每个线程有自己的线程号码,里面有线程的状态,可以观察线程是否阻塞,如果长时间的wait和block说明这个线程是有问题的

4、转换16进制

因为Java线程文件中的线程ID是16进制,所以需要将线程ID从十进制转换成十六进制 命令:echo "obase=16;31083" | bc

5、jstack用法解析
[root@root ~]# jstackUsage:    jstack [-l]         (to connect to running process)    jstack -F [-m] [-l]         (to connect to a hung process)    jstack [-m] [-l]          (to connect to a core file)    jstack [-m] [-l] [server_id@]        (to connect to a remote debug server)Options:    -F  to force a thread dump. Use when jstack  does not respond (process is hung)    -m  to print both java and native frames (mixed mode)    -l  long listing. Prints additional information about locks    -h or -help to print this help message
6、jstack查看输出

我们也可以用 jps或者java ps -ef| java来查看Java进程,这里我们用jps来查看

[root@root ~]# jps

[root@root ~]# jstack 31061

"pool-1-thread-3" #10 prio=5 os_prio=0 tid=0x00007f3568105800 nid=0x7961 waiting on condition [0x00007f35455cf000]   java.lang.Thread.State: WAITING (parking)        at sun.misc.Unsafe.park(Native Method)        - parking to wait for  <0x00000000f8a81148> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)        at java.lang.Thread.run(Thread.java:748)"pool-1-thread-2" #9 prio=5 os_prio=0 tid=0x00007f3568103800 nid=0x7960 waiting on condition [0x00007f35456d0000]   java.lang.Thread.State: WAITING (parking)        at sun.misc.Unsafe.park(Native Method)        - parking to wait for  <0x00000000f8a81148> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)        at java.lang.Thread.run(Thread.java:748)"pool-1-thread-1" #8 prio=5 os_prio=0 tid=0x00007f3568102000 nid=0x795f waiting on condition [0x00007f35457d1000]   java.lang.Thread.State: WAITING (parking)        at sun.misc.Unsafe.park(Native Method)        - parking to wait for  <0x00000000f8a81148> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1088)        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)        at java.lang.Thread.run(Thread.java:748)"Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007f35680b4000 nid=0x795d runnable [0x0000000000000000]   java.lang.Thread.State: RUNNABLE"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f35680b1000 nid=0x795c waiting on condition [0x0000000000000000]   java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f35680af000 nid=0x795b waiting on condition [0x0000000000000000]   java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f35680ad800 nid=0x795a runnable [0x0000000000000000]   java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f356807c800 nid=0x7959 in Object.wait() [0x00007f3558301000]   java.lang.Thread.State: WAITING (on object monitor)        at java.lang.Object.wait(Native Method)        - waiting on <0x00000000f8a86b38> (a java.lang.ref.ReferenceQueue$Lock)        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)        - locked <0x00000000f8a86b38> (a java.lang.ref.ReferenceQueue$Lock)        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f3568077800 nid=0x7958 in Object.wait() [0x00007f3558402000]   java.lang.Thread.State: WAITING (on object monitor)        at java.lang.Object.wait(Native Method)        - waiting on <0x00000000f8a86cf0> (a java.lang.ref.Reference$Lock)        at java.lang.Object.wait(Object.java:502)        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)        - locked <0x00000000f8a86cf0> (a java.lang.ref.Reference$Lock)        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"main" #1 prio=5 os_prio=0 tid=0x00007f3568009800 nid=0x7956 waiting on condition [0x00007f356ed59000]   java.lang.Thread.State: TIMED_WAITING (sleeping)        at java.lang.Thread.sleep(Native Method)        at FullGCTest.main(FullGCTest.java:35)"VM Thread" os_prio=0 tid=0x00007f356806e000 nid=0x7957 runnable "VM Periodic Task Thread" os_prio=0 tid=0x00007f35680b7000 nid=0x795e waiting on condition JNI global references: 205

通过thread dump分析线程状态

大多数情况下会基于thead dump 分析当前各个线程的运行情况,如是否存在死锁,是否存在一个线程长时间持有锁不释放等等

在dump中,线程一般存在如下几种状态: 1、RUNNABLE,线程处于执行中 2、BLOCKED,线程被阻塞 3、WAITING,线程正在等待

locked <0x000000076bf62208>说明线程 对地址为0x000000076bf62208对象进行了加锁; waiting to lock <0x000000076bf62208> 说明线程 在等待地址为0x000000076bf62208对象上的锁; waiting for monitor entry [0x000000001e21f000]说明线程 是通过synchronized关键字进入了监视器的临界区,并处于"Entry Set"队列,等待monitor; waiting on <0x0000000088ca3310> (a java.lang.Object)等待锁的释放

假如有一个进程中有100个线程,很多线程都在waiting on 某一把锁,然后线程不该阻塞的被阻塞了,该结束的没结束掉,一定要找到哪个线程持有这把锁 ,我们可以搜索 jstack dump 的信息,找到<0X...>的信息,看哪个线程只有了这把锁,一般这个线程状态是RUNNABLE,表示这个线程正在运行但是一直持有这把锁不释放,那么就会导致整个线程的死锁

7、jstack分析死锁
public class TestDeadLock {    private static Object obj1 = new Object();    private static Object obj2 = new Object();    public static void main(String[] args) {        new Thread(new Thread1()).start();        new Thread(new Thread2()).start();    }    private static class Thread1 implements Runnable {        @Override        public void run() {            synchronized (obj1) {                System.out.println("Thread1 拿到了 obj1 的锁!");                try {// 停顿2秒的意义在于,让Thread2线程拿到obj2的锁                    Thread.sleep(2000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                synchronized (obj2) {                    System.out.println("Thread1 拿到了 obj2 的锁!");                }            }        }    }    private static class Thread2 implements Runnable {        @Override        public void run() {            synchronized (obj2) {                System.out.println("Thread2 拿到了 obj2 的锁!");                try {                    // 停顿2秒的意义在于,让Thread1线程拿到obj1的锁                    Thread.sleep(2000);                } catch (Exception e) {                    e.printStackTrace();                }                synchronized (obj1) {                    System.out.println("Thread2 拿到了obj1的锁!");                }            }        }    }}

通过命令查看分析日志

[root@root fuccGC]# jps485 Bootstrap9877 Jps10629 QuorumPeerMain9846 TestDeadLock[root@root fuccGC]# jstack 9846

内存监控工具的使用

我们可以使用jvm自带的命令去进行监控GC的信息: jinfo pid: 这个命令就是把这个进程的一些详细信息列出来 [root@root ~]# jinfo 9846 这个只是有帮助,但是帮助不是特别大,大家只要记住有这个命令就行,不做深入了解 jstat -gc pid 1000: 这个就是每一秒钟将GC的日志打印出来,动态 观察GC情况/阅读GC日志发现频繁GC等等,但是这个信息看起来不是很直观,能够分析出来的东西也不多,所以一般使用的也不是很多

我们用的最多的还是通过工具去查看,比如 jconsole/jvisualvm

1、 jconsole

这两个是JDK自带的一个工具,也是 一个图形界面的工具,只要你装了JDK就有这两个工具,可以从本机去跟踪远程服务器上的一个进程,作为Linux服务器,很少有人会装图形界面,如下图所示:

在我们程序启动的时候要加入参数:

  java -Djava.rmi.server.hostname=101.XX.XXX.XX -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8080 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.rmi.port=8080 FullGCTest

基本情况如下,我们就可以监控到我们远程服务器上面的内存信息

2、 jvisualvm

双击 jvisualvm

选择远程-点击文件按钮

选择添加JMX连接 输入我们的地址和账号密码就可以登录了

这样就可以查看我们远程服务的内存信息了

在这里我们就知道怎么定位问题了,哪一个类占用了多少内存,就会显示出来,点击抽样器,内存,之后就会对远程的那台机器的JAVA进程内存,在图形化界面中显示,有多少个类,占用了多少个字节,这样我们就可以具体定位到是哪个类有问题了

面试中如何回答定位内存溢出(OOM)

如果面试官我们如何定位OOM的问题,但是我们不能回答用图形界面,因为作为一个服务来讲,在不断的运行,当我们开一个JMX服务的时候,会形象服务本来的运行效率,那我们已经上线的系统不用图形化用什么?还有一个叫Jprofiler是最好用的图形界面,但是这个是收费的,所以一般用不到,

如果不用图形界面那我们用什么,我们可以用 cmdline、arthas这两个 图形界面用在什么地方呢?用在测试上,测试的时候进行监控~

如果已经上线的项目,我们不用图形界面可以用什么呢?我们可以用Jmap

jmap

[root@root ~]# jmap -histo 19086 | head -20

它就会把我们的内存信息打印出来,虽然没有图形化界面方便,但是里面的信息也足够我们去观察和定位问题了

线上系统,内存特别大,jmap执行期间会对进程产生很大影响,甚至卡顿(电商不适合)

  1. 设定了参数HeapDump,OOM的时候会自动产生堆转储文件(java -Xms20M -Xmx20M -XX:+UseParallelGC -XX:+HeapDumpOnOutOfMemoryError FullGCTest)

  2. 很多服务器备份(高可用),停掉这台服务器对其他服务器不影响

感谢各位的阅读,以上就是"如何排查JVM内存过高的问题"的内容了,经过本文的学习后,相信大家对如何排查JVM内存过高的问题这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

线程 内存 信息 进程 问题 命令 图形 服务 时候 界面 情况 定位 服务器 监控 业务 就是 工具 日志 进制 分析 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 百单网互联网科技有限公司怎么样 华服互联网科技怎么样 重庆互联网络科技 唐信互联网科技 大连 大白互联网科技 互联网金融科技委员会 盛创互联网科技有限公司 广西拼得多互联网科技有限公司 互联网之后的科技是什么 互联网科技热点资讯 南山科技园互联网公司有哪些 湖南一路畅行互联网科技 互联网科技研发水平 河南住房之家互联网科技 工业互联网山东科技大学 怎样加入科技互联网 智帮互联网科技公司最近动向 雨阳互联网卡科技有限公司 互联网与数字科技关系 科技互联网专业务虚会材料 河南云网互联网络科技有限公司 疫情对互联网科技公司影响 互联网科技排名 科技创新互联网企业 碳银互联网科技有限公司 上海亿帐通互联网科技 佰商互联网科技有限公司 互联网 科技服务平台 广东鹅鹅鹅互联网科技有限公司 山东亿川互联网科技有限责任公司
0