阿里面試:NIO為什么會導致CPU 100% ?
在 Java 中總共有三種 IO 類型:BIO(Blocking I/O,阻塞I/O)、NIO(Non-blocking I/O,非阻塞I/O)和 AIO(Asynchronous I/O,異步I/O),它們的區別如下:
- 在 JDK 1.4 之前,只有 BIO 一種模式,其開發過程相對簡單,新來一個連接就會創建一個新的線程處理,但隨著請求并發度的提升,BIO 很快遇到了性能瓶頸。
- 所以在 JDK 1.4 以后開始引入了 NIO 技術,NIO 可以在一個線程中處理多個 IO 操作,提高了資源的利用率和系統的吞吐量。
- 而到了 JDK 1.7 發布了 AIO 模型,它可以實現當線程發起一個 IO 操作后,可以直接返回,無需等待 IO 操作完成。操作系統會在整個 IO 操作完成后,通過回調函數通知應用程序。
1.空輪詢和CPU100%
然而,隨著 NIO 逐漸使用,人們卻發現了 NIO 的一個經典問題,也就是臭名昭著的 Epoll(多路復用實現技術)空輪詢的問題。
空輪詢的問題是指,在 Linux 系統下,使用 Java 中的 NIO 時,即使 Selector(多路復用器)輪詢結果為空,也沒有 wakeup 或新消息要處理時,NIO 依舊會進行空輪詢,導致 CPU 一直上升,最終造成 CPU 使用率 100% 的問題。
該 BUG 相關可以參見以下鏈接:
- https://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719
- https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933
- https://github.com/netty/netty/issues/327
2.空輪詢的原因
空輪詢產生的原因可以在 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6670302 上找到答案,例如以下就是一個經典的 bug 復現場景:
A DESCRIPTION OF THE PROBLEM :
The NIO selector wakes up infinitely in this situation..
0. server waits for connection
1. client connects and write message
2. server accepts and register OP_READ
3. server reads message and remove OP_READ from interest op set
4. client close the connection
5. server write message (without any reading.. surely OP_READ is not set)
6. server's select wakes up infinitely with return value 0
也就說,當連接出現了 RST(強制連接關閉),因為 poll 和 epoll 對于突然中斷的連接 Socket 會對返回的 eventSet 事件集合置為 POLLHUP 或者 POLLERR,eventSet 事件集合發生了變化,這就導致 Selector 會被喚醒,進而導致 CPU 100% 問題,其根本原因就是 JDK 沒有處理好這種情況,比如 SelectionKey 中就沒定義有異常事件的類型,導致異常無法被捕捉和處理,從而一直空輪詢。
3.如何解決空輪詢?
NIO 空輪詢可能會導致 CPU 100% 的解決方案通常有以下兩種:
- 升級 Java 版本:早期的 JDK 版本中(JDK 1.7 之前),這個 bug 較為常見,但后續的 JDK 更新中,Oracle 和 OpenJDK 團隊已經著手解決了這一問題,確保使用最新的 Java 版本可以減少遇到此問題的風險。但網上依然有人發現即使在 JDK 1.8 中,使用原生的 NIO 依然會發生空輪詢的問題,只是發生的概率變低了而已。
- 使用第三方庫:對于無法升級 Java 版本的情況,或擔心新版本的 JDK 中依然存在空輪詢問題的團隊可以考慮使用已經解決了此問題的第三方庫,如 Netty。Netty 通過主動檢測和處理空輪詢情況,當檢測到可能的空輪詢時,會采取措施如臨時增加 Selector 的等待時間,或者重建 Selector,以此來避免 CPU 資源的浪費。