JVM 11:CPU占用过高排查

1. CPU占用过高排查思路

  1. Top命令分析进程
  2. 定位CPU占用高的进程
  3. 用jstack输出线程信息
  4. 定位占用CPU高的线程
  5. 解读线程信息,定位具体代码

2. 代码

在这里插入图片描述

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * VM参数: -XX:+PrintGC -Xms200M -Xmx200M
 *  GC调优---生产服务器推荐开启(默认是关闭的)
 *  -XX:+HeapDumpOnOutOfMemoryError
 */

public class FullGCProblem {
    //线程池
    private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(50,
            new ThreadPoolExecutor.DiscardOldestPolicy());

    public static void main(String[] args) throws Exception {
        //50个线程
        executor.setMaximumPoolSize(50);
        while (true){
            calc();
            Thread.sleep(100);
        }
    }
    //多线程执行任务计算
    private static void calc(){
        List<UserInfo> taskList = getAllCardInfo();
        taskList.forEach(userInfo -> {
            executor.scheduleWithFixedDelay(() -> {
                userInfo.user();
            }, 2, 3, TimeUnit.SECONDS);
        });
    }
    //模拟从数据库读取数据,返回
    private static List<UserInfo> getAllCardInfo(){
        List<UserInfo> taskList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            UserInfo userInfo = new UserInfo();
            taskList.add(userInfo);
        }
        return taskList;
    }
    private static class UserInfo {
        String name = "fisher";
        int age = 18;
        BigDecimal money = new BigDecimal(999999.99);

        public void user() {
            //
        }
    }
}

3. 运行程序

执行

# FullGC.jar里的FullGCProblem方法,如果不是src目录下,前面需要加包名,例如com.csdn.FullGCProblem
java -cp FullGC.jar -XX:+PrintGC -Xms200M -Xmx200M FullGCProblem

或者

java -jar -XX:+PrintGC -Xms200M -Xmx200M FullGC.jar

指定堆大小为200M,方便快速达到cpu占用100%。

4. 先通过 top 命令找到消耗 cpu 很高的进程 id

运行程序,cpu占用慢慢升高,当程序运行到10分钟左右后,cpu占用超过100%。
在这里插入图片描述

5. 执行 top -p 27579 单独监控该进程

在这里插入图片描述

6. 在第 5 步的监控界面输入 H,获取当前进程下的所有线程信息

在这里插入图片描述

7. 找到消耗 cpu 特别高的线程编号,27581和27582

8. 将线程号 27581 转成 16 进制是 6bbd

在这里插入图片描述

9. 执行 jstack 27579|grep 6bbd 输出与线程 6bbd 相关的线程

在这里插入图片描述

  • 发现 GC 的线程占用过高,开启的参数中,有垃圾回收的日志显示,是垃圾回收的导致的cpu占用过高,不是业务线程导致的。
    在这里插入图片描述

10. 查看gc信息

jstat -gc 27579 5000 20| awk '{print $13,$14,$15,$16,$17}'

在这里插入图片描述

  • 出现大量的FullGC,JVM 中默认的垃圾回收器是多线程的,这里JVM疯狂的垃圾回收,导致 CPU 占用过高。

11. 查看空间占用情况,jmap -histo 27579|head -20

很多个70万个对象
在这里插入图片描述

12. 问题总结

  • 一般来说,前面这几行,就可以看出,到底是哪些对象占用了内存。
  • 这些对象回收不掉,导致了 FullGC,里面还有 OutOfMemory。
  • 任务数多于线程数,那么任务会进入阻塞队列,就是一个队列,你进去,排队,有机会了,你就上来跑。
  • 因为代码中任务数一直多于线程数,所以每 0.1S,就会有 50 个任务进入阻塞对象,50 个任务底下有对象,至少对象送进去了,但是没执行。 所以导致对象一直都在,同时还回收不了。
    在这里插入图片描述
  • 所以堆中,就会有对象 70 万个,阻塞队列中 70 万个任务,futureTask。并且这些对象还回收不了。
  • 总结:
    在 JVM 出现性能问题的时候。(表现上是 CPU100%,内存一直占用)
    1、 如果 CPU 的 100%,要从两个角度出发,一个有可能是业务线程疯狂运行,比如说想很多死循环。还有一种可能性,就是 GC 线程在疯狂的回收,因 为 JVM 中垃圾回收器主流也是多线程的,所以很容易导致 CPU 的 100%
    2、 在遇到内存溢出的问题的时候,一般情况下我们要查看系统中哪些对象占用得比较多,这里使用的是一个很简单的代码,在实际的业务代码中,找到对应的对象,分析对应的类,找到为什么这些对象不能回收的原因。

13. 常见问题分析

  • 超大对象
    代码中创建了很多大对象 , 且一直因为被引用不能被回收,这些大对象会进入老年代,导致内存一直被占用,很容易引发 GC 甚至是 OOM
  • 超过预期访问量
    通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。 比如如果一个系统高峰期的内存需求需要 2 个 G 的堆空间,但是堆空间设置比较小,导致内存不够,导致 JVM 发起频繁的 GC 甚至 OOM。
  • 过多使用 Finalizer
    过度使用终结器(Finalizer),对象没有立即被 GC,Finalizer 线程会和我们的主线程进行竞争,不过由于它的优先级较低,获取到的 CPU 时间较少,因此它永远也赶不上主线程的步伐,程序消耗了所有的可用资源,最后抛出 OutOfMemoryError 异常。
  • 内存泄漏
    大量对象引用没有释放,JVM 无法对其自动回收。程序在申请内存后,无法释放已申请的内存空间。
  1. 长生命周期的对象持有短生命周期对象的引用
    例如将 ArrayList 设置为静态变量,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏
  2. 连接未关闭
    如数据库连接、网络连接和 IO 连接等,只有连接被关闭后,垃圾回收器才会回收对应的对象。
  3. 变量作用域不合理
    例如,1.一个变量的定义的作用范围大于其使用范围,2.如果没有及时地把对象设置为 null
  4. 内部类持有外部类
    Java 的非静态内部类的这种创建方式,会隐式地持有外部类的引用,而且默认情况下这个引用是强引用,因此,如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏
    如果内部类的生命周期长于外部类的生命周期,程序很容易就产生内存泄漏(垃圾回收器会回收掉外部类的实例,但由于内部类持有外部类的引用,导致垃圾回收器不能正常工作)
    解决方法:你可以在内部类的内部显示持有一个外部类的软引用(或弱引用),并通过构造方法的方式传递进来,在内部类的使用过程中,先判断一下外部类是否被回收。
  5. Hash 值改变
    在集合中,如果修改了对象中的那些参与计算哈希值的字段,会导致无法从集合中单独删除当前对象,造成内存泄露。
    示例
    在这里插入图片描述
  • 代码问题
    代码问题和内存泄漏很大的关系,如果观察一个系统,每次进行 FullGC 发现堆空间回收的比例比较小,尤其是老年代,同时对象越来越多,这个时候可 以判断是有可能发生内存泄漏。

14. 内存泄漏和内存溢出辨析

内存溢出:实实在在的内存空间不足导致;
内存泄漏:该释放的对象没有释放,常见于使用容器保存元素的情况下。
如何避免:
内存溢出:检查代码以及设置足够的空间;
内存泄漏:一定是代码有问题。
很多情况下,内存溢出往往是内存泄漏造成的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 调优 JVM 的方法有很多,常用的方法如下: 1. 调整堆内存大小。可以使用 `-Xms` 和 `-Xmx` 参数来调整堆内存的初始大小和最大大小。如果堆内存过小,会导致频繁的 GC,从而导致 CPU 占用过高。 2. 调整 GC 策略。JVM 中有很多不同的 GC 算法,如 Serial GC、Parallel GC、CMS GC 等。每种算法都有自己的优缺点。可以使用 `-XX:+UseSerialGC` 或者 `-XX:+UseParallelGC` 等参数来选择不同的 GC 算法。 3. 调整 JVM 参数。JVM 还有很多其他的参数,比如 `-XX:+PrintGC` 可以在控制台输出 GC 的日志,`-XX:+PrintGCDetails` 可以输出详细的 GC 日志,方便我们分析问题。 4. 使用 profiler 分析 CPU 占用情况。有很多 profiler 工具可以帮助我们分析 CPU 占用情况。比如 Java Flight Recorder、JProfiler 等。使用这些工具可以帮助我们找到 CPU 占用过高的原因,从而调优 JVM。 ### 回答2: 要优化JVM来降低CPU占用过高的问题,可以从以下几个方面进行优化: 1. 调整内存设置:通过适当调整JVM的内存设置来避免内存压力过大,导致频繁的垃圾回收。可以通过修改-Xms和-Xmx参数来调整JVM的初始堆和最大堆大小,需要根据实际情况进行调整。 2. 减少对象创建:过多的对象创建会导致频繁的垃圾回收,从而造成CPU占用过高。可以通过优化代码,避免不必要的对象创建,使用对象池等方式减少对象的产生。 3. 优化算法和数据结构:选择合适的算法和数据结构可以减少CPU的使用率。对于一些耗时的操作,可以考虑使用更高效的算法来替代。 4. 多线程优化:合理的使用多线程可以充分利用多核CPU的性能,减少单个线程的CPU占用率。可以将一些独立、并行的任务放入不同的线程中进行处理。 5. 使用性能分析工具:可以使用性能分析工具来检测和分析CPU占用过高的原因,如JProfiler、VisualVM等。通过定位到具体的问题,可以有针对性地进行优化。 总之,JVM的优化需要综合考虑内存设置、对象创建、算法数据结构、多线程等多个因素。通过适当的调整和优化,可以降低CPU占用过高的问题,提高系统的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值