前言
每一種該語言在某些極限情況下的表現(xiàn)一般都不太一樣,那么我常用的Java語言,在達(dá)到100萬個并發(fā)連接情況下,會怎么樣呢,有些好奇,更有些期盼。
這次使用經(jīng)常使用的順手的netty NIO框架(netty-3.6.5.Final),封裝的很好,接口很全面,就像它現(xiàn)在的域名 netty.io,專注于網(wǎng)絡(luò)IO。
整個過程沒有什么技術(shù)含量,淺顯分析過就更顯得有些枯燥無聊,準(zhǔn)備好,硬著頭皮吧。
測試服務(wù)器配置
運(yùn)行在VMWare Workstation 9中,64位Centos 6.2系統(tǒng),分配14.9G內(nèi)存左右,4核。
已安裝有Java7版本:
java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode)
在/etc/sysctl.conf中添加如下配置:
fs.file-max = 1048576
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_mem = 786432 2097152 3145728
net.ipv4.tcp_rmem = 4096 4096 16777216
net.ipv4.tcp_wmem = 4096 4096 16777216
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
在/etc/security/limits.conf中添加如下配置:
* soft nofile 1048576
* hard nofile 1048576
測試端
測試端無論是配置還是程序和以前一樣,翻看前幾篇博客就可以看到client5.c的源碼,以及相關(guān)的配置信息等。
服務(wù)器程序
這次也是很簡單吶,沒有業(yè)務(wù)功能,客戶端HTTP請求,服務(wù)端輸出chunked編碼內(nèi)容。
入口HttpChunkedServer.java:
唯一的自定義處理器HttpChunkedServerHandler.java:
啟動腳本start.sh
達(dá)到100萬并發(fā)連接時的一些信息
每次服務(wù)器端達(dá)到一百萬個并發(fā)持久連接之后,然后關(guān)掉測試端程序,斷開所有的連接,等到服務(wù)器端日志輸出在線用戶為0時,再次重復(fù)以上步驟。在這反反復(fù)復(fù)的情況下,觀察內(nèi)存等信息的一些情況。以某次斷開所有測試端為例后,當(dāng)前系統(tǒng)占用為(設(shè)置為list_free_1
):
total used free shared buffers cached
Mem: 15189 7736 7453 0 18 120
-/+ buffers/cache: 7597 7592
Swap: 4095 948 3147
通過top觀察,其進(jìn)程相關(guān)信息
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4925 root 20 0 8206m 4.3g 2776 S 0.3 28.8 50:18.66 java
在啟動腳本start.sh中,我們設(shè)置堆內(nèi)存為6G。
ps aux|grep java命令獲得信息:
root 4925 38.0 28.8 8403444 4484764 ? Sl 15:26 50:18 java -server...HttpChunkedServer 8000
RSS占用內(nèi)存為4484764K/1024K=4379M
然后再次啟動測試端,在服務(wù)器接收到online user 1023749時,ps aux|grep java
內(nèi)容為:
root 4925 43.6 28.4 8403444 4422824 ? Sl 15:26 62:53 java -server...
查看當(dāng)前網(wǎng)絡(luò)信息統(tǒng)計(jì)
ss -s
Total: 1024050 (kernel 1024084)
TCP: 1023769 (estab 1023754, closed 2, orphaned 0, synrecv 0, timewait 0/0), ports 12
Transport Total IP IPv6
* 1024084 - -
RAW 0 0 0
UDP 7 6 1
TCP 1023767 12 1023755
INET 1023774 18 1023756
FRAG 0 0 0
通過top查看一下
top -p 4925
top - 17:51:30 up 3:02, 4 users, load average: 1.03, 1.80, 1.19
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
Cpu0 : 0.9%us, 2.6%sy, 0.0%ni, 52.9%id, 1.0%wa, 13.6%hi, 29.0%si, 0.0%st
Cpu1 : 1.4%us, 4.5%sy, 0.0%ni, 80.1%id, 1.9%wa, 0.0%hi, 12.0%si, 0.0%st
Cpu2 : 1.5%us, 4.4%sy, 0.0%ni, 80.5%id, 4.3%wa, 0.0%hi, 9.3%si, 0.0%st
Cpu3 : 1.9%us, 4.4%sy, 0.0%ni, 84.4%id, 3.2%wa, 0.0%hi, 6.2%si, 0.0%st
Mem: 15554336k total, 15268728k used, 285608k free, 3904k buffers
Swap: 4194296k total, 1082592k used, 3111704k free, 37968k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4925 root 20 0 8206m 4.2g 2220 S 3.3 28.4 62:53.66 java
四核都被占用了,每一個核心不太平均。這是在虛擬機(jī)中得到結(jié)果,可能真實(shí)服務(wù)器會更好一些。 因?yàn)椴皇荂PU密集型應(yīng)用,CPU不是問題,無須多加關(guān)注。
系統(tǒng)內(nèi)存狀況
free -m
total used free shared buffers cached
Mem: 15189 14926 263 0 5 56
-/+ buffers/cache: 14864 324
Swap: 4095 1057 3038
物理內(nèi)存已經(jīng)無法滿足要求了,占用了1057M虛擬內(nèi)存。
查看一下堆內(nèi)存情況
jmap -heap 4925
Attaching to process ID 4925, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 23.21-b01
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 6442450944 (6144.0MB)
NewSize = 629145600 (600.0MB)
MaxNewSize = 629145600 (600.0MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 1
PermSize = 52428800 (50.0MB)
MaxPermSize = 52428800 (50.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 419430400 (400.0MB)
used = 308798864 (294.49354553222656MB)
free = 110631536 (105.50645446777344MB)
73.62338638305664% used
Eden Space:
capacity = 209715200 (200.0MB)
used = 103375232 (98.5863037109375MB)
free = 106339968 (101.4136962890625MB)
49.29315185546875% used
From Space:
capacity = 209715200 (200.0MB)
used = 205423632 (195.90724182128906MB)
free = 4291568 (4.0927581787109375MB)
97.95362091064453% used
To Space:
capacity = 209715200 (200.0MB)
used = 0 (0.0MB)
free = 209715200 (200.0MB)
0.0% used
concurrent mark-sweep generation:
capacity = 5813305344 (5544.0MB)
used = 4213515472 (4018.321487426758MB)
free = 1599789872 (1525.6785125732422MB)
72.48054631000646% used
Perm Generation:
capacity = 52428800 (50.0MB)
used = 5505696 (5.250640869140625MB)
free = 46923104 (44.749359130859375MB)
10.50128173828125% used
1439 interned Strings occupying 110936 bytes.
老生代占用內(nèi)存為72%,較為合理,畢竟系統(tǒng)已經(jīng)處理100萬個連接。
再次斷開所有測試端,看看系統(tǒng)內(nèi)存(free -m)
total used free shared buffers cached
Mem: 15189 7723 7466 0 13 120
-/+ buffers/cache: 7589 7599
Swap: 4095 950 3145
記為list_free_2
。
list_free_1
和list_free_2
兩次都釋放后的內(nèi)存比較結(jié)果,系統(tǒng)可用物理已經(jīng)內(nèi)存已經(jīng)降到7589M,先前可是7597M物理內(nèi)存。
總之,我們的JAVA測試程序在內(nèi)存占用方面已經(jīng),最低需要7589 + 950 = 8.6G內(nèi)存為最低需求內(nèi)存吧。
GC日志
我們在啟動腳本處設(shè)置的一大串參數(shù),到底是否達(dá)到目標(biāo),還得從gc日志處獲得具體效果,推薦使用GCViewer。
GC事件概覽:

其它:

總之:
- 只進(jìn)行了一次Full GC,代價太高,停頓了12秒。
- PartNew成為了停頓大戶,導(dǎo)致整個系統(tǒng)停頓了41秒之久,不可接受。
- 當(dāng)前JVM調(diào)優(yōu)喜憂參半,還得繼續(xù)努力等
小結(jié)
Java與與Erlang、C相比,比較麻煩的事情,需要在程序一開始就得準(zhǔn)備好它的堆棧到底需要多大空間,換個說法就是JVM啟動參數(shù)設(shè)置堆內(nèi)存大小,設(shè)置合適的垃圾回收機(jī)制,若以后程序需要更多內(nèi)存,需停止程序,編輯啟動參數(shù),然后再次啟動。總之一句話,就是麻煩。單單JVM的調(diào)優(yōu),就得持續(xù)不斷的根據(jù)檢測、信息、日志等進(jìn)行適當(dāng)微調(diào)。
- JVM需要提前指定堆大小,相比Erlang/C,這可能是個麻煩
- GC(垃圾回收),相對比麻煩,需要持續(xù)不斷的根據(jù)日志、JVM堆棧信息、運(yùn)行時情況進(jìn)行JVM參數(shù)微調(diào)
- 設(shè)置一個最大連接目標(biāo),多次測試達(dá)到頂峰,然后釋放所有連接,反復(fù)觀察內(nèi)存占用,獲得一個較為合適的系統(tǒng)運(yùn)行內(nèi)存值
- Eclipse Memory Analyzer結(jié)合jmap導(dǎo)出堆棧DUMP文件,分析內(nèi)存泄漏,還是很方便的
- 想修改運(yùn)行時內(nèi)容,或者稱之為熱加載,默認(rèn)不可能
- 真實(shí)機(jī)器上會有更好的反映
吐槽一下:
JAVA OSGI,相對比Erlang來說,需要人轉(zhuǎn)換思路,不是那么原生的東西,總是有些別扭,社區(qū)或商業(yè)公司對此的修修補(bǔ)補(bǔ),不過是實(shí)現(xiàn)一些面向?qū)ο笏痪邆涞臒峒虞d的企業(yè)特性。
測試源代碼,下載just_test。