
[圖片源:https://bell-sw.com/announcements/2020/10/28/JVM-in-Linux-containers-surviving-the-isolation/]
介紹
相信很多人都知道,云環(huán)境中,所有服務(wù)都必須作資源限制。內(nèi)存作為一個重要資源當(dāng)然不會例外。限制說得容易,但如何在限制的同時,保證服務(wù)的性能指標(biāo)(SLA)就是個技術(shù)和藝術(shù)活。
為應(yīng)用內(nèi)存設(shè)置上限,從來不是個容易的事。因為設(shè)置上限的理據(jù)是:
- 應(yīng)用程序?qū)?nèi)存的使用和回收邏輯,而這個邏輯一般異常地復(fù)雜
- 現(xiàn)代操作系統(tǒng)復(fù)雜的虛擬內(nèi)存管理、物理內(nèi)存分配、回收機制
如果是 Java ,還要加上:
- JVM 中各類型組件的內(nèi)存管理機制
以上 3 個方面還可以進(jìn)一步細(xì)分。每一個細(xì)分都有它的內(nèi)存機制。而只要我們漏算了其中一個,就有可能讓應(yīng)用總內(nèi)存使用超限。
而讓人揪心的是,當(dāng)應(yīng)用總內(nèi)存使用超限時,操作系統(tǒng)會無情地殺死應(yīng)用進(jìn)程(OOM, Out Of Memory)。而很多人對這一無所覺,只知道容器重啟了。而這可能是連鎖反應(yīng)的開端:
- 如果容器 OOM 的原因只是個偶然,那還好說。如果是個 BUG 引起的,那么這種 OOM 可能會在服務(wù)的所有容器中逐個爆發(fā),最后服務(wù)癱瘓
- 原來服務(wù)容器群的資源就緊張,一個容器 OOM 關(guān)閉了,負(fù)載均衡把流量分到其它容器,于是其它容器也出現(xiàn)同樣的 OOM。最后服務(wù)癱瘓
JVM 是個 Nice 的經(jīng)理,在發(fā)現(xiàn)內(nèi)存緊張時,就不厭其煩地停止應(yīng)用線程和執(zhí)行 GC,而這種內(nèi)存緊張的信號,在設(shè)計界稱為“背壓(Backpressure)”。
但操作系統(tǒng)相反,是個雷厲風(fēng)行的司令,一發(fā)現(xiàn)有進(jìn)程超限,直接一槍 OOM Killed。
或者你深入研究過 cgroup memory,它其實也有一個 Backpressure 的通知機制,不過現(xiàn)在的容器和 JVM 均忽略之。
終上所述,容器進(jìn)程 OOM Kllled 是件應(yīng)該避免,但需要深入研究才能避免的事情。
網(wǎng)路上,我們可以找到很多現(xiàn)實案例和教訓(xùn):

Java 內(nèi)存管理很復(fù)雜。我們對它了解越多,應(yīng)用出現(xiàn) OOM Killed 的可能性就越低。下面我拿一個遇到的測試案例進(jìn)行分析。
分析報告分為兩個部分:
- 研究應(yīng)用實測出的指標(biāo)、內(nèi)存消耗,內(nèi)存限制配置
- 潛在的問題和改進(jìn)建議
測試環(huán)境
主機:裸機(BareMetal)
CPU: 40 cores, 共 80 個超線程
Linux:
Kernel: 5.3.18
glibc: libc-2.26.so
Java: 1.8.0_261-b12
Web/Servlet 容器: Jetty
配置容量
POD 容量配置
resources:
limits:
cpu: "8"
memory: 4Gi
# 4Gi = 4 * 1024Mb = 4*1024*1024k = 4194304k = 4294967296 bytes = 4096Mb
requests:
cpu: "2"
memory: 4Gi
JVM 容量配置
開始說 JVM 容量配置前,我假設(shè)你已經(jīng)對 JVM 內(nèi)存使用情況有個基本印象:

圖片源:https://www.twblogs.net/a/5d80afd1bd9eee541c349550?lang=zh-cn
下面是我在測試環(huán)境收集到的配置:
| 配置 | 實際生效配置(Mbyte) | |
|---|---|---|
| Young Heap + Old Heap |
-Xmx3G -XX:+AlwaysPreTouch |
3072 |
| MaxMetaspaceSize | [默認(rèn)] | Unlimited |
| CompressedClassSpaceSize | [默認(rèn)] | 1024 |
| MaxDirectMemorySize | [默認(rèn)] | 3072 |
| ReservedCodeCacheSize | [默認(rèn)] | 240 |
| ThreadStackSize*maxThreadCount | [默認(rèn)] * 276(實測線程數(shù)) | 276 |
| 匯總 | 7684 + (沒限制 MaxMetaspaceSize) |
神秘的 MaxDirectMemorySize 默認(rèn)值
MaxDirectMemorySize 默認(rèn)值,https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html 如事說:
Sets the maximum total size (in bytes) of the New I/O (the java.nio package) direct-buffer allocations. Append the letter k or K to indicate kilobytes, m or M to indicate megabytes, g or G to indicate gigabytes. By 默認(rèn), the size is set to 0, meaning that the JVM chooses the size for NIO direct-buffer allocations automatically.
意思就是說了等于沒說 ??。
在我的測試環(huán)境中, 我使用 Arthas attached 到 JVM 然后查看內(nèi)部的靜態(tài)變量:
[arthas@112]$ dashboard
ognl -c 30367620 '@io.netty.util.internal.PlatformDependent@maxDirectMemory()'
@Long[3,221,225,472]
ognl '@java.nio.Bits@maxMemory'
@Long[3,221,225,472]
3221225472/1024/1024 = 3072.0 Mb
如果你想深入,請參考資料:
- http://www.mastertheboss.com/java/troubleshooting-outofmemoryerror-direct-buffer-memory/
- https://developer.aliyun.com/article/2948
MaxDirectMemorySize ~= `from -Xmx (Young Heap + Old Heap )` - `Survivor(Young) Capacity` ~= 3G
maxThreadCount 最大線程數(shù)來源
既然上面用了 Arthas , 下面學(xué)是繼續(xù) Arthas 吧:
[arthas@112]$ dashboard
Threads Total: 276
應(yīng)用使用的是 Jetty, 線程池配置 jetty-threadpool.xml
<Configure>
<New id="threadPool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
<Set name="maxThreads" type="int"><Property name="jetty.threadPool.maxThreads" deprecated="threads.max" default="200"/></Set>
...
</New>
</Configure>
因為除了 Jetty,還有其它各種線程。
使用量
Java 的視角看使用量
| 容量配置 | 生效配置(Mbyte) | 實際使用(Mbyte) | |
|---|---|---|---|
| Young Heap + Old Heap |
-Xmx3G -XX:+AlwaysPreTouch |
3072 | 3072 |
| MaxMetaspaceSize | [默認(rèn)] | Unlimited | 128 |
| CompressedClassSpaceSize | [默認(rèn)] | 1024 | 15 |
| MaxDirectMemorySize | [默認(rèn)] | 3072 | 270 |
| ReservedCodeCacheSize | [默認(rèn)] | 240 | 82 |
| ThreadStackSize*maxThreadCount | [默認(rèn)]*276線程 | 276 | 276 |
| Sum | 7684 + (沒限制 MaxMetaspaceSize) | 3843 |
如何采集實際使用量
- ReservedCodeCache
在應(yīng)用經(jīng)過熱身、壓力測試之后,用 Arthas attached:
[arthas@112]$ dashboard
code_cache : 82Mb
- DirectMemory
[arthas@112]$
ognl '@java.nio.Bits@reservedMemory.get()'
@Long[1,524,039]
ognl -c 30367620 '@io.netty.util.internal.PlatformDependent@usedDirectMemory()'
@Long[268,435,456]
- Metaspace
- CompressedClassSpaceSize
$ jcmd $PID GC.heap_info
garbage-first heap total 3145728K, used 1079227K [0x0000000700000000, 0x0000000700106000, 0x00000007c0000000)
region size 1024K, 698 young (714752K), 16 survivors (16384K)
Metaspace used 127,323K, capacity 132,290K, committed 132,864K, reserved 1,167,360K
class space used 14,890K, capacity 15,785K, committed 15,872K, reserved 1,048,576K
原生應(yīng)用的視角看使用量
原生應(yīng)用的視角看使用量,包括下面這個方面:
- *lib.so 動態(tài)庫占用: 16Mb
- *.jar 文件映射占用: 8Mb
- GC 算法消耗: 未調(diào)查
- glibc malloc 空間回收不及時消耗: 158Mb
總的原生應(yīng)用消耗: 16+8+158 = 182Mb
小結(jié)一下:
Java 角度看使用量: 3843Mb
總應(yīng)用使用量 = 3843 + 158 ~= 4001Mb
4001Mb,這里我們沒有算 *lib.so 動態(tài)庫占用 和 *.jar 文件映射占用。為什么?將在下面內(nèi)容中作出解釋。
4001Mb 這個數(shù)字有點可怕,離容器配置的上限 4096Mb 不遠(yuǎn)了。但這個數(shù)字有一定水分。為什么?將在下面內(nèi)容中作出解釋。
以下我嘗試分析每個子項的數(shù)據(jù)來源
*lib.so 動態(tài)庫占用
運行命令:
pmap -X $PID
部分輸出:
Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous Mapping
...
7f281b1b1000 r-xp 00000000 08:03 1243611251 48 48 3 48 0 /lib64/libcrypt-2.26.so
7f281b1bd000 ---p 0000c000 08:03 1243611251 2044 0 0 0 0 /lib64/libcrypt-2.26.so
7f281b3bc000 r--p 0000b000 08:03 1243611251 4 4 4 4 4 /lib64/libcrypt-2.26.so
7f281b3bd000 rw-p 0000c000 08:03 1243611251 4 4 4 4 4 /lib64/libcrypt-2.26.so
...
7f28775a5000 r-xp 00000000 08:03 1243611255 92 92 5 92 0 /lib64/libgcc_s.so.1
7f28775bc000 ---p 00017000 08:03 1243611255 2048 0 0 0 0 /lib64/libgcc_s.so.1
7f28777bc000 r--p 00017000 08:03 1243611255 4 4 4 4 4 /lib64/libgcc_s.so.1
7f28777bd000 rw-p 00018000 08:03 1243611255 4 4 4 4 4 /lib64/libgcc_s.so.1
7f28777be000 r-xp 00000000 08:03 1800445487 224 64 4 64 0 /opt/jdk1.8.0_261/jre/lib/amd64/libsunec.so
7f28777f6000 ---p 00038000 08:03 1800445487 2044 0 0 0 0 /opt/jdk1.8.0_261/jre/lib/amd64/libsunec.so
7f28779f5000 r--p 00037000 08:03 1800445487 20 20 20 20 20 /opt/jdk1.8.0_261/jre/lib/amd64/libsunec.so
7f28779fa000 rw-p 0003c000 08:03 1800445487 8 8 8 8 8 /opt/jdk1.8.0_261/jre/lib/amd64/libsunec.so
...
7f28f43a7000 r-xp 00000000 08:03 1243611284 76 76 3 76 0 /lib64/libresolv-2.26.so
7f28f43ba000 ---p 00013000 08:03 1243611284 2048 0 0 0 0 /lib64/libresolv-2.26.so
7f28f45ba000 r--p 00013000 08:03 1243611284 4 4 4 4 4 /lib64/libresolv-2.26.so
7f28f45bb000 rw-p 00014000 08:03 1243611284 4 4 4 4 4 /lib64/libresolv-2.26.so
7f28f45bc000 rw-p 00000000 00:00 0 8 0 0 0 0
7f28f45be000 r-xp 00000000 08:03 1243611272 20 20 1 20 0 /lib64/libnss_dns-2.26.so
7f28f45c3000 ---p 00005000 08:03 1243611272 2044 0 0 0 0 /lib64/libnss_dns-2.26.so
7f28f47c2000 r--p 00004000 08:03 1243611272 4 4 4 4 4 /lib64/libnss_dns-2.26.so
7f28f47c3000 rw-p 00005000 08:03 1243611272 4 4 4 4 4 /lib64/libnss_dns-2.26.so
7f28f47c4000 r-xp 00000000 08:03 1243611274 48 48 2 48 0 /lib64/libnss_files-2.26.so
7f28f47d0000 ---p 0000c000 08:03 1243611274 2044 0 0 0 0 /lib64/libnss_files-2.26.so
7f28f49cf000 r--p 0000b000 08:03 1243611274 4 4 4 4 4 /lib64/libnss_files-2.26.so
7f28f49d0000 rw-p 0000c000 08:03 1243611274 4 4 4 4 4 /lib64/libnss_files-2.26.so
7f28f49d1000 rw-p 00000000 00:00 0 2072 2048 2048 2048 2048
7f28f4bd7000 r-xp 00000000 08:03 1800445476 88 88 6 88 0 /opt/jdk1.8.0_261/jre/lib/amd64/libnet.so
7f28f4bed000 ---p 00016000 08:03 1800445476 2044 0 0 0 0 /opt/jdk1.8.0_261/jre/lib/amd64/libnet.so
7f28f4dec000 r--p 00015000 08:03 1800445476 4 4 4 4 4 /opt/jdk1.8.0_261/jre/lib/amd64/libnet.so
7f28f4ded000 rw-p 00016000 08:03 1800445476 4 4 4 4 4 /opt/jdk1.8.0_261/jre/lib/amd64/libnet.so
7f28f4dee000 r-xp 00000000 08:03 1800445477 68 64 4 64 0 /opt/jdk1.8.0_261/jre/lib/amd64/libnio.so
7f28f4dff000 ---p 00011000 08:03 1800445477 2044 0 0 0 0 /opt/jdk1.8.0_261/jre/lib/amd64/libnio.so
7f28f4ffe000 r--p 00010000 08:03 1800445477 4 4 4 4 4 /opt/jdk1.8.0_261/jre/lib/amd64/libnio.so
7f28f4fff000 rw-p 00011000 08:03 1800445477 4 4 4 4 4 /opt/jdk1.8.0_261/jre/lib/amd64/libnio.so
?? 如果你不太了解 Linux 的 memory map 和 pmap 的輸出,建議閱讀: https://www.labcorner.de/cheat-sheet-understanding-the-pmap1-output/ 。
如果你懶惰如我,我還是上個圖吧:
大家知道,現(xiàn)代操作系統(tǒng)都有進(jìn)程間共享物理內(nèi)存的機制,以節(jié)省物理內(nèi)存。如果你了解COW(Copy on Write)就更好了。一臺物理機上,運行著多個容器,而容器的鏡像其實是分層的。對于同一個機構(gòu)生成的不同服務(wù)的鏡像,很多時候是會基于同一個基礎(chǔ)層,而這個基礎(chǔ)層包括是 Java 的相關(guān)庫。而所謂的層不過是主機上的目錄。即不同容器可能會共享讀(Mapping)同一文件。
回到我們的主題,內(nèi)存限制。容器通過 cgroup 限制內(nèi)存。而 cgroup 會記賬容器內(nèi)進(jìn)程的每一次內(nèi)存分配。而文件映射共享內(nèi)存的計算方法顯然要特別處理,因為跨了進(jìn)程和容器。現(xiàn)在能查到的資料是說,只有第一個讀/寫這塊 mapping 內(nèi)存的 cgroup 才記賬(https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt 中 [2.3 Shared Page Accounting])。所以這個賬比較難預(yù)計的,一般我們只做再壞情況的保留。
*.jar mapping 占用
pmap -X $PID
記賬原理和上面的 *.so 類似。不過 Java 9 后,就不再做 *.jar mapping 了。就算是 Java 8 ,也只是 mapping 文件中的目錄結(jié)構(gòu)部分。
在我的測試中,只使用了 8Mb 內(nèi)存.
glibc malloc 消耗
Java 在兩種情況下使用 glibc malloc:
- NIO Direct Byte Buffer / Netty Direct Byte Buffer
- JVM 內(nèi)部基礎(chǔ)程序
業(yè)界對 glibc malloc 的浪費頗有微詞. 主要集中在不及時的內(nèi)存歸還(給操作系統(tǒng))。這種浪費和主機的 CPU 數(shù)成比例,可參考:
- https://medium.com/nerds-malt/java-in-k8s-how-weve-reduced-memory-usage-without-changing-any-code-cbef5d740ad
- https://systemadminspro.com/java-in-docker-cpu-limits-server-class-machine/
- https://www.cnblogs.com/seasonsluo/p/java_virt.html
不幸的是,我的測試環(huán)境是祼機,所有 CPU 都給容器看到了。而主機是 80 個 CPU 的。那么問題來了,如何測量浪費了多少?
glibc 提供了一個 malloc_stats(3) 函數(shù),它會輸出堆信息(包括使用和保留)到標(biāo)準(zhǔn)輸出流。那么問題又來了。如果調(diào)用這個函數(shù)?修改代碼,寫JNI嗎?當(dāng)然可以。不過,作為一個 Geek,當(dāng)然要使用 gdb 。
cat <<"EOF" > ~/.gdbinit
handle SIGSEGV nostop noprint pass
handle SIGBUS nostop noprint pass
handle SIGFPE nostop noprint pass
handle SIGPIPE nostop noprint pass
handle SIGILL nostop noprint pass
EOF
export PID=`pgrep java`
gdb --batch --pid $PID --ex 'call malloc_stats()'
輸出:
Arena 0:
system bytes = 135168
in use bytes = 89712
Arena 1:
system bytes = 135168
in use bytes = 2224
Arena 2:
system bytes = 319488
in use bytes = 24960
Arena 3:
system bytes = 249856
in use bytes = 2992
...
Arena 270:
system bytes = 1462272
in use bytes = 583280
Arena 271:
system bytes = 67661824
in use bytes = 61308192
Total (incl. mmap):
system bytes = 638345216
in use bytes = 472750720
max mmap regions = 45
max mmap bytes = 343977984
所以結(jié)果是: 638345216 - 472750720 = 165594496 ~= 158Mb
即浪費了 158Mb。因為我測試場景負(fù)載不大,在負(fù)載大,并發(fā)大的場景下,80個CPU 的浪費遠(yuǎn)不止這樣。
有一點需要指出的,操作系統(tǒng)物理內(nèi)存分配是 Lazy 分配的,即只在實際讀寫內(nèi)存時,才分配,所以,上面的 158Mb 從操作系統(tǒng)的 RSS 來看,可能會變小。
GC 內(nèi)存消耗
未調(diào)查
tmpfs 內(nèi)存消耗
未調(diào)查
操作系統(tǒng) RSS
RSS(pmap -X $PID) = 3920MB。即操作系統(tǒng)認(rèn)為使用了 3920MB 的物理內(nèi)存。
CGroup 限制
cgroup limit 4Gi = 4*1024Mb = 4096Mb
pagecache 可用空間 : 4096 - 3920 = 176Mb
下面看看 cgroup 的 memory.stat 文件
$ cat cgroup `memory.stat` file
rss 3920Mb
cache 272Mb
active_anon 3740Mb
inactive_file 203Mb
active_file 72Mb # bytes of file-backed memory on active LRU list
細(xì)心如你會發(fā)現(xiàn):
3920 + 272 = 4192 > 4096Mb
不對啊,為何還不 OOM killed?
說來話長, pagecache 是塊有彈性的內(nèi)存空間,當(dāng)應(yīng)用需要 anonymous 內(nèi)存時,內(nèi)核可以自動回收 pagecache.
?? 感興趣可參考:
https://engineering.linkedin.com/blog/2016/08/don_t-let-linux-control-groups-uncontrolled
https://github.com/kubernetes/kubernetes/issues/43916
https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/memory.html
潛在問題和推薦解決方法
Native Buffer 限制
默認(rèn) MaxDirectMemorySize ~= -Xmx - survivor size ~= 3G .
這在高并發(fā)時,內(nèi)存得不到及時回收時,會使用大量的 Direct Byte Buffer。所以建議顯式設(shè)置限制:
java ... -XX:MaxDirectMemorySize=350Mb
?? 感興趣可參考:
Cassandra客戶端和Redisson均基于Netty,固均使用了Native Buffer. 注意的是Netty在Unsafe.class基礎(chǔ)上,還有內(nèi)部的內(nèi)存池。
glibc malloc arena 的浪費
在我的測試環(huán)境中,主機有 80 個CPU。glibc 為了減少多線程分配內(nèi)存時的鎖競爭,在高并發(fā)時最多為每個 CPU 保留 8 個內(nèi)存塊(Arena),而 Arena 的空間歸還給操作系統(tǒng)的時機是不可預(yù)期的,和堆中內(nèi)存碎片等情況有關(guān)。
在我的測試環(huán)境中觀察的結(jié)果是:共創(chuàng)建了 271 個Arena。使用了 608Mb 的 RSS。而實際程序用到的內(nèi)存只有 450Mb。浪費了 157 Mb。浪費的情況有隨機性,和內(nèi)存碎片等情況有關(guān)。對于容器,我們不可能分配所有主機的 CPU??梢栽O(shè)置一個顯式上限是合理的,且這個上限和容器的 memory limit、CPU limit 應(yīng)該聯(lián)動。
MALLOC_ARENA_MAX 這個環(huán)境變量就是用于配置這個上限的。
- 和內(nèi)存使用的聯(lián)系:
我們實測中,共使用了 700Mb glibc 堆內(nèi)存. 而每個Arena大小為 64Mb. 所以:
700/64=10 Arena
- 和容器 cpu limit 的聯(lián)系:
8 cpu * (每個cpu 8 arena) = 64 Arena.
我們保守地使用大的保留空間:
export MALLOC_ARENA_MAX=64
?? 感興趣可參考:
https://www.gnu.org/software/libc/manual/html_node/Memory-Allocation-Tunables.html
Jetty 線程池
經(jīng)調(diào)查,每 API 的調(diào)用用時大約 100 ms。而現(xiàn)有配置指定了最大 200 個線程。所以:
200 thread / 0.1s = 2000 TPS
在我們的測試中,單容器的 TPS 不出 1000。所以 100 個線程足以。減少線程數(shù)的好處是,可以同時可以減少過度的線程上下文切換、cgroup CPU 限流(cpu throttling)、線程堆棧內(nèi)存、Native Buffer 內(nèi)存。讓請求堆在 Request Queue,而不是內(nèi)核的 Runnale Queue。
<!-- jetty-threadpool.xml -->
<Configure>
<New id="threadPool" class="org.eclipse.jetty.util.thread.QueuedThreadPool">
...
<Set name="maxThreads" type="int"><Property name="jetty.threadPool.maxThreads" deprecated="threads.max" default="100"/></Set>
...
</New>
</Configure>
Java code cache 慢漲
在我們測試中,在經(jīng)過系統(tǒng)預(yù)熱后,Java code cache 仍然會慢漲。Java 8 的 code cache 最大值是 240Mb。 如果 code cache 消耗了大量的內(nèi)存,可能會觸發(fā) OOM killed。 所以還是要作顯式限制的。 從測試環(huán)境的觀察,100Mb 的空間已經(jīng)足夠。
java ... -XX:ReservedCodeCacheSize=100M -XX:UseCodeCacheFlushing=true
?? 感興趣可參考:
https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
容器的內(nèi)存限制
從上面的調(diào)查可知, 3G java heap + JVM overhead + DirectByteBuffer 已經(jīng)很接近 4Gi 的容器內(nèi)存上限了。在高并發(fā)情況下,OOM killed 風(fēng)險還是很高的。而且這個問題在測試環(huán)境不一定能出現(xiàn),有它的隨機性。
cgroup 對容器接近 OOM 的次數(shù)是有記錄(memory.failcnt)的,在測試時發(fā)現(xiàn)這個數(shù)字在慢張。在內(nèi)存緊張的時候,內(nèi)核通過丟棄文件緩存(pagecache)來優(yōu)先滿足應(yīng)用對內(nèi)存的需求。而丟棄文件緩存意味什么?更慢的讀,更頻繁和慢的寫硬盤。如果應(yīng)用有讀寫IO壓力,如果讀 *.jar,寫日志,那么 IO 慢問題會隨之而來。
watch cat ./memory.failcnt
19369
?? 感興趣可參考:
https://engineering.linkedin.com/blog/2016/08/don_t-let-linux-control-groups-uncontrolled
https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt
https://srvaroa.github.io/jvm/kubernetes/memory/docker/oomkiller/2019/05/29/k8s-and-java.html
對于我的應(yīng)用,我建議是放寬內(nèi)存限制:
resources:
limits:
memory: 4.5Gi
requests:
memory: 4.5Gi
展望
不全面地說,從服務(wù)運維者的角度看, 服務(wù)的資源分配基于這些系數(shù):
- 容器的 SLA
- 目標(biāo)容器的呑吐量
如我把上面系數(shù)作為一個工具程序的 輸入, 那么 輸出 應(yīng)該是:
- 應(yīng)該部署多少個容器
- 每個容器的資源配置應(yīng)該如何
- CPU
- 容器 CPU limit
- 應(yīng)用線程池 limit
- Memory
- 容器 memory limit
- 應(yīng)用線程池d limit:
- java: 堆內(nèi)/堆外
- CPU
?? 有一個開源工具可參考:
https://github.com/cloudfoundry/java-buildpack-memory-calculator
免責(zé)聲明
Every coin has two sides, 應(yīng)用調(diào)優(yōu)更是,每種調(diào)優(yōu)方法均有其所需要的環(huán)境前提,不然就不叫調(diào)優(yōu),直接上開源項目的默認(rèn)配置 Pull Request 了。大師常說,不要簡單 copy 調(diào)參就用。要考慮自己的實際情況,然后作充分測試方可使用。
體會
2016 年開始,各大公司開始追趕時尚,把應(yīng)該的應(yīng)用放入容器。而由于很多舊項目和組件在設(shè)計時,沒考慮在一個受限容器中運行,說白了,就是非 contaier aware。時隔數(shù)年,情況有所好轉(zhuǎn),但還是有不少坑。而作為一個合格的架構(gòu)師,除了 PPT 和遠(yuǎn)方外,我們還得有個玻璃心。
以上是對一個 Java 容器內(nèi)存的分析,如果你對 Java 容器 CPU和線程參數(shù)有興趣,請移步:Java 容器化的歷史坑(史坑) - 資源限制篇。
用一個漫畫了結(jié)本文:

