案例
一個網站為了實現客戶端實時從服務端接收數據,使用了 CometD 1.1.1 作為服務端推送框架,服務器是 Jetty7.1.4,CPU i5,內存 4G,操作系統 32位Windows。
服務端常常拋出內存溢出異常,管理員把堆開到最大(32位系統最多到1.6G),但問題依舊。
開著 jstat 觀察,GC并不頻繁,eden、survivor、老年代、永久代的內存都很正常,沒有壓力。
查看日志中有異常信息:
...
java.lang.OutOfMemoryError:null
...
at java.nio.DirectByteBuffer ...
原因
這是由 Direct Memory 不足引起的。
操作系統對每個進程能管理的內存是有限制的,32位windows的限制是2G,其中1.6G給了Java堆,但 Direct Memory 是不計入這 1.6G的,因此 Direct Memory 最大就是 0.4G。
垃圾回收時,雖然會對 Direct Memory 進行回收,但不像新生代、老年代那樣,發現空間不足時就觸發回收,只能等老年代滿了之后 full gc 時,隨便把 Direct Memory 清理一下,否則只能等拋出異常后進行catch,調用 System.gc()。
這個案例中使用了 CometD,有大量的 NIO 使用 Direct Memory,所以產生了這個問題。
總結
我們平時對堆比較關注,但一定要記得,除了堆之外,下面這些區域也會占用較多的內存,需要注意:
Direct Memory
可以通過 -XX:MaxDirectMemorySize調整大小,內存不足時拋出 OutOfMemoryError或者OutOfMemoryError:Direct buffer memory
線程堆棧
可通過 -Xss調整大小,內存不足時拋出 StackOverflowError(縱向無法分配,即無法分配新的棧幀)或者OutOfMemoryError:unable to create new native thread(橫向無法分配,即無法建立新的線程)
Socket 緩存區
每個socket連接都有 receive 和 send 兩個緩存區,分別占大約 37K 和 25K,連接多時占用的內存也很可觀,如果無法分配,拋出異常 IOException: Too many open files 。