Netty运行时CPU占用率达到100%

14
我已经看到了其他关于这个问题的参考资料,比如这里这里,虽然它们引用了不同版本的Netty。我尝试使用4.0分支中的最新版本(4.0.29)和5.0 alpha分支(5.0-Alpha3)来解决问题。在本地(非linux)jdk 1.8.040上没有问题,在远程(Linux)java jdk 1.8.025-b17上得到100%的cpu占用率。 Linux内核版本为2.6.32。
尝试使用EpollEventLoopGroup(); 尝试调用
workerGroup = new NioEventLoopGroup();
workerGroup.rebuildSelectors();

有人能提供任何建议吗?我看到了关于这个bug的不同版本的Netty的参考。是JDK bug? Netty bug? 进程在启动时立即达到100%并保持在那里。

更新:升级到java 1.8.045,没有任何改变。

所有可运行线程的JStack输出(其中包含一些rabbitmq内容,仅出于完整性考虑 - 那对其他应用程序很常见,并不是问题的原因)。


2
如果你能运行 top -H -p <jvm pid>jstack <jvm pid>,你就可以看到消耗 CPU 的线程以及这些线程的堆栈。我还建议运行 jstat -gc <jvm pid> 来确保不是内存问题。 - K Erlandsson
感谢您的建议。top -H -p <jvm pid> 显示有1个子进程正在使用所有的cpi。jstack显示唯一没有等待的是epollWait和java.net.SocketInputStream.socketRead0(本机方法)。 - Steve B.
请点击链接查看相关的编程内容。 - Steve B.
这次运行没有出现这种情况,但是像你之前提到的那样使用 top -H -p 命令可以显示一个子线程正在消耗所有的 CPU。在堆栈输出中没有对该子线程 ID 的相应引用,因此我认为无法匹配。如果您知道其他匹配方法,我可以重新创建。 - Steve B.
1
你可以通过将 top 中进程的 pid 转换成十六进制来匹配线程。该十六进制数字与 jstack 输出中的 nid=0x<hexNumber> 匹配。这样我们就能够确定消耗 CPU 的确切线程。 - K Erlandsson
显示剩余5条评论
1个回答

12

正如我们在评论中所指出的,占用CPU的线程在以下堆栈中处于繁忙状态:

"pool-9-thread-1" #49 prio=5 os_prio=0 tid=0x00007ffd508e8000 nid=0x3a0c runnable [0x00007ffd188b6000]
   java.lang.Thread.State: RUNNABLE
    at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.poll(ScheduledThreadPoolExecutor.java:809)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1066)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1127)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

我成功地通过创建一个 ScheduledThreadPoolExecutor,将其配置为允许核心线程超时,并调度大量重复任务以短延迟来复现类似的行为。在我的机器上会产生大量的 CPU 占用,并且 jstack 输出类似(有时更深入到 poll 方法中)。此代码可复现:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, r -> new Thread(r, "scheduler"));
executor.setMaximumPoolSize(1);
executor.setKeepAliveTime(10_000, TimeUnit.MILLISECONDS);
executor.allowCoreThreadTimeOut(true);

AtomicInteger count = new AtomicInteger();

Runnable task = () -> {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    System.out.println(count.incrementAndGet() + " executed by " + Thread.currentThread());
};

executor.scheduleAtFixedRate(task, 0, 100, TimeUnit.MILLISECONDS);
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
executor.setKeepAliveTime(1, TimeUnit.MINUTES);
executor.allowCoreThreadTimeOut(true);
for (long i = 0; i < 1000; i++) {
    executor.scheduleAtFixedRate(new Runnable() {

        @Override
        public void run() {
        }
    }, 0, 1, TimeUnit.NANOSECONDS);
}

现在我们只需要确定哪一段代码设置了有问题的 ScheduledThreadPoolExecutor。我在 RabbitMQ 和 Netty 的源代码中查找,但没有发现明显的问题。这可能是您自己代码中的问题吗?

编辑:如评论中所述,根本原因是使用0初始化的 ScheduledThreadPoolExecutor 在某些平台上可能会导致CPU旋转。这是在提问者的代码中完成的。


3
遗憾的是,虽然我想说是别人的错,但实际上这与我的代码有关。原来,如果错误地使用0初始化scheduledThreadPool,就会导致CPU占用率达到100%。为什么会有人这样做呢?只有上帝知道,但绝对不是我会干的事情。无论如何,Java文档规定使用小于0会抛出IllegalArgumentException异常,但没有提到0。他们应该在0时抛出异常。感谢您的帮助,并享受额外的奖励。我要去申请汉堡王的工作了。 - Steve B.
@SteveB。很高兴我能帮忙。有趣的原因! - K Erlandsson
1
有人这样做的原因是因为他们想要每30分钟安排一次任务,但不想一直分配一个线程来等待30分钟的时间(只是为了等待30分钟的时间),所以你可以允许核心线程超时或甚至将核心线程设置为0...可惜它会出现错误。 - john16384

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接