自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

在Docker中運(yùn)行Java:為了防止失敗,你應(yīng)該知道的

開發(fā)
如果你嘗試在容器中運(yùn)行Java程序,或者專注于Docker,你可能會遇到一些關(guān)于JVM和堆大小的問題。本篇文章將介紹如何解決這些問題。

如果你嘗試在容器中運(yùn)行Java程序,或者專注于Docker,你可能會遇到一些關(guān)于JVM和堆大小的問題。本篇文章將介紹如何解決這些問題。

很多開發(fā)者會(或者應(yīng)該)知道,當(dāng)我們?yōu)檫\(yùn)行在Linux容器(docker, rkt, runC, lxcfs, etc,)中的Java程序去設(shè)置JVM的GC、堆大小和運(yùn)行時編譯器的參數(shù)時并沒有得到預(yù)想的效果。當(dāng)我們通過“java -jar mypplication-fat.jar”的方式而不設(shè)置任何參數(shù)來運(yùn)行一個Java應(yīng)用時,JVM會根據(jù)自身的許多參數(shù)進(jìn)行調(diào)整,以便在執(zhí)行環(huán)境中獲得最優(yōu)的性能。

本篇博客將通過簡單的方式向開發(fā)人員展示在將Java應(yīng)用運(yùn)行在Linux容器內(nèi)時需要了解的內(nèi)容。

我們傾向于認(rèn)為容器可以像虛擬機(jī)一樣可以完整的定義虛擬機(jī)的CPU個數(shù)和虛擬機(jī)的內(nèi)存。容器更像是一個進(jìn)程級別的資源(CPU、內(nèi)存、文件系統(tǒng)、網(wǎng)絡(luò)等)隔離。這種隔離是依賴于Linux內(nèi)核中提供的一個 cgroups 的功能。

然而,一些可以從運(yùn)行時環(huán)境中收集信息的應(yīng)用程序在cgroups功能出現(xiàn)之前已經(jīng)存在。在容器中執(zhí)行命令 ‘top‘, ‘free‘, ‘ps’,也包括沒有經(jīng)過優(yōu)化的JVM是一個會受到高限制的Linux進(jìn)程。讓我們來驗(yàn)證一下。

問題

為了展示遇到的問題,我使用命令“docker-machine create -d virtualbox –virtualbox-memory ‘1024’ docker1024”在虛擬機(jī)中創(chuàng)建了一個具有1GB內(nèi)存的Docker守護(hù)進(jìn)程,接下來在3個Linux容器中執(zhí)行命令“free -h”,使其只有100MB的內(nèi)存和Swap。結(jié)果顯示所有的容器總內(nèi)存是995MB。

在Docker中運(yùn)行Java:為了防止失敗,你應(yīng)該知道的

即使是在 Kubernetes/OpenShift集群中,結(jié)果也是類似的。我在一個內(nèi)存是15G的集群中也執(zhí)行了命令使得Kubernetes Pod有511MB的內(nèi)存限制(命令:“kubectl run mycentos –image=centos -it –limits=’memory=512Mi’”),總內(nèi)存顯示為14GB。

在Docker中運(yùn)行Java:為了防止失敗,你應(yīng)該知道的

想要知道為什么是這樣的結(jié)果,可以去閱讀此篇博客文章 “ Memory inside Linux containers – Or why don’t free and top work in a Linux container? ”

我們需要知道Docker參數(shù) (-m, –memory和–memory-swap)和Kubernetes參數(shù) (–limits)會讓Linux內(nèi)核在一個進(jìn)程的內(nèi)存超出限制時將其Kill掉,但是JVM根本不清楚這個限制的存在,當(dāng)超過這個限制時,不好的事情發(fā)生了!

為了模擬當(dāng)一個進(jìn)程超出內(nèi)存限制時會被殺死的場景,我們可以通過命令“docker run -it –name mywildfly -m=50m jboss/wildfly”在一個容器中運(yùn)行WildFly Application Server并且為其限制內(nèi)存大小為50MB。在這個容器運(yùn)行期間,我們可以執(zhí)行命令“docker stats”來查看容器的限制。

在Docker中運(yùn)行Java:為了防止失敗,你應(yīng)該知道的

但是過了幾秒之后,容器Wildfly將會被中斷并且輸出信息:*** JBossAS process (55) received KILL signal ***

通過命令 “docker inspect mywildfly -f ‘{{json .State}}'”可以查看容器被殺死的原因是發(fā)生了OOM(內(nèi)存不足)。容器中的“state”被記錄為OOMKilled=true 。

在Docker中運(yùn)行Java:為了防止失敗,你應(yīng)該知道的

這將怎樣影響Java應(yīng)用

在Docker宿主機(jī)中創(chuàng)建一個具有1GB內(nèi)存的虛擬機(jī)(在之前使用命令已經(jīng)創(chuàng)建完畢 “docker-machine create -d virtualbox –virtualbox-memory ‘1024’ docker1024”) ,并且限制一個容器的內(nèi)存為150M,看起來已經(jīng)足夠運(yùn)行這個在 Dockerfile中設(shè)置過參數(shù)-XX: PrintFlagsFinal 和 -XX: PrintGCDetails的Spring Boot application了。這些參數(shù)使得我們可以讀取JVM的初始化參數(shù)并且獲得 Garbage Collection (GC)的運(yùn)行詳細(xì)情況。

嘗試一下:

  1. $ docker run -it --rm --name mycontainer150 -p 8080:8080 -m 150M rafabene/java-container:openjdk 

我也提供了一個訪問接口“/api/memory/”來使用String對象加載JVM內(nèi)存,模擬大量的消耗內(nèi)存,可以調(diào)用試試:

  1. $ curl http://X 41X:8080/api/memory 

這個接口將會返回下面的信息 “Allocated more than 80% (219.8 MiB) of the max allowed JVM memory size (241.7 MiB)”

在這里我們至少有2個問題:

  • 為什么JVM會允許241.7MiB的最大內(nèi)容?
  • 如果容器已經(jīng)限制了內(nèi)存為150MB,為什么允許Java分配內(nèi)存到220MB?

首先,我們應(yīng)該重新了解在 JVM ergonomic page 中所描述的 “maximum heap size”的定義,它將會使用1/4的物理內(nèi)存。JVM并不知道它運(yùn)行在一個容器中,所以它將被允許使用260MB的最大堆大小。通過添加容器初始化時的參數(shù)-XX: PrintFlagsFinal,我們可以檢查這個參數(shù)的值。

 

  1. $ docker logs mycontainer150|grep -i MaxHeapSize  
  2. uintx MaxHeapSize := 262144000 {product} 

其次,我們應(yīng)該理解當(dāng)在docker命令行中設(shè)置了 “-m 150M”參數(shù)時,Docker守護(hù)進(jìn)程會限制RAM為150M并且Swap為150M。從結(jié)果上看,一個進(jìn)程可以分配300M的內(nèi)存,解釋了為什么我們的進(jìn)程沒有收到任何從Kernel中發(fā)出的退出信號。

更多的關(guān)于Docker命令中內(nèi)存限制 (–memory)和Swap (–memory-swap)的差別可以參考 這里 。

更多的內(nèi)存是解決方案嗎?

開發(fā)者如果不理解問題可能會認(rèn)為運(yùn)行環(huán)境中沒有為JVM提供足夠的內(nèi)存。通常的解決對策就是為運(yùn)行環(huán)境提供更多的內(nèi)存,但是實(shí)際上,這是一個錯誤的認(rèn)識。

假如我們將Docker Machine的內(nèi)存從1GB提高到8GB(使用命令 “docker-machine create -d virtualbox –virtualbox-memory ‘8192’ docker8192”),并且創(chuàng)建的容器從150M到800M:

  1. $ docker run -it --name mycontainer -p 8080:8080 -m 800M rafabene/java-container:openjdk 

此時使用命令 “curl http://X 58X:8080/api/memory” 還不能返回結(jié)果,因?yàn)樵谝粋€擁有8GB內(nèi)存的JVM環(huán)境中經(jīng)過計算的MaxHeapSize大小是2092957696(~ 2GB)。可以使用命令“docker logs mycontainer|grep -i MaxHeapSize”查看。

在Docker中運(yùn)行Java:為了防止失敗,你應(yīng)該知道的

應(yīng)用將會嘗試分配超過1.6GB的內(nèi)存,當(dāng)超過了容器的限制(800MB的RAM 800MB的Swap),進(jìn)程將會被kill掉。

很明顯當(dāng)在容器中運(yùn)行程序時,通過增加內(nèi)存和設(shè)置JVM的參數(shù)不是一個好的方式。當(dāng)在一個容器中運(yùn)行Java應(yīng)用時,我們應(yīng)該基于應(yīng)用的需要和容器的限制來設(shè)置最大堆大小(參數(shù):-Xmx)。

解決方案是什么?

在Dockerfile中稍作修改,為JVM指定擴(kuò)展的環(huán)境變量。修改內(nèi)容如下:

  1. CMD java -XX: PrintFlagsFinal -XX: PrintGCDetails $JAVA_OPTIONS -jar java-container.jar 

現(xiàn)在我們可以使用JAVA_OPTIONS的環(huán)境變量來設(shè)置JVM Heap的大小。300MB看起來對應(yīng)用足夠了。稍后你可以查看日志,看到Heap的值是 314572800 bytes ( 300MBi)。

Docker下,可以使用“-e”的參數(shù)來設(shè)置環(huán)境變量進(jìn)行切換。

 

  1. $ docker run -d --name mycontainer8g -p 8080:8080 -m 800M -e JAVA_OPTIONS='-Xmx300m' rafabene/java-container:openjdk-env  
  2. $ docker logs mycontainer8g|grep -i MaxHeapSize  
  3. uintx MaxHeapSize := 314572800 {product} 

在Kubernetes中,可以使用“–env=[key=value]”來設(shè)置環(huán)境變量進(jìn)行切換:

 

 

  1. $ kubectl run mycontainer --image=rafabene/java-container:openjdk-env --limits='memory=800Mi' --env="JAVA_OPTIONS='-Xmx300m'"   
  2. $ kubectl get pods   
  3. NAME READY STATUS RESTARTS AGE    
  4. mycontainer-2141389741-b1u0o 1/1 Running 0 6s    
  5. $ kubectl logs mycontainer-2141389741-b1u0o|grep MaxHeapSize    
  6. uintx MaxHeapSize := 314572800 {product}  

還能再改進(jìn)嗎?

有什么辦法可以根據(jù)容器的限制來自動計算Heap的值?

事實(shí)上如果你的基礎(chǔ)Docker鏡像使用的是由Fabric8提供的,那么就可以實(shí)現(xiàn)。鏡像fabric8/java-jboss-openjdk8-jdk使用了腳本來計算容器的內(nèi)存限制,并且使用50%的內(nèi)存作為上限。也就是有50%的內(nèi)存可以寫入。你也可以使用這個鏡像來開/關(guān)調(diào)試、診斷或者其他更多的事情。讓我們看一下一個Spring Boot應(yīng)用的 Dockerfile :

 

  1. FROM fabric8/java-jboss-openjdk8-jdk:1.2.3  
  2. ENV JAVA_APP_JAR java-container.jar  
  3. ENV AB_OFF true  
  4. EXPOSE 8080  
  5. ADD target/$JAVA_APP_JAR /deployments/ 

就這樣!現(xiàn)在,不管容器的內(nèi)存限制如何,我們的Java應(yīng)用將在容器中自動的調(diào)節(jié)Heap大小,而不是再根據(jù)宿主機(jī)來設(shè)置。

 

在Docker中運(yùn)行Java:為了防止失敗,你應(yīng)該知道的

總結(jié)到目前為止,Java JVM還不能意識到其是運(yùn)行在一個容器中 — 某些資源在內(nèi)存和CPU的使用上會受到限制。因此,你不能讓JVM自己來設(shè)置其認(rèn)為的最優(yōu)的最大Heap值。

一個解決對策是使用Fabric8作為基礎(chǔ)鏡像,它可以意識到應(yīng)用程序運(yùn)行在一個受限制的容器中,并且在你沒有做任何事情的情況下,可以自動的調(diào)整最大Heap的值。

在JDK9中已經(jīng)開始進(jìn)行嘗試在容器 (i.e. Docker)環(huán)境中為JVM提供cgroup功能的內(nèi)存限制。

責(zé)任編輯:未麗燕 來源: DockOne
相關(guān)推薦

2017-06-06 11:59:26

Docker工具容器

2017-07-24 14:59:31

ERP軟件連續(xù)性

2024-11-28 08:54:19

GolangGo變量

2010-08-09 13:20:36

Flex

2011-03-25 15:56:58

2019-06-03 08:04:43

Apache服務(wù)器命令

2013-01-09 13:55:43

2020-04-29 14:30:35

HTTPHTTPS前端

2020-11-16 09:15:07

MYSQL

2021-06-07 12:40:34

Python代碼陷阱

2017-10-12 10:20:13

服務(wù)器運(yùn)行壽命

2022-01-04 10:10:34

Garuda LinuArch LinuxLinux

2022-11-04 08:22:14

編譯代碼C語言

2020-10-13 14:15:22

HTTPHTTP請求方法

2013-06-28 14:09:33

PHP庫

2023-05-04 16:10:13

緩存前端

2018-08-23 09:33:12

2016-09-19 13:52:26

Javascript跨域前端

2021-10-25 14:55:38

Linux技巧命令

2020-02-03 09:28:44

UbunturootLinux
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號