我有一個(gè)運(yùn)行嵌入式Tomcat服務(wù)器的Spring Boot應(yīng)用程序忍法,非常簡(jiǎn)單...
它運(yùn)行在一個(gè)Docker容器中讽营。 若我用$ docker run
運(yùn)行鏡像(沒有內(nèi)存限制)蜗顽,它的資源占用情況是這樣:
docker stats
一個(gè)相當(dāng)簡(jiǎn)單的spring boot應(yīng)用的容器就占了677MB坞嘀? are you kidding me砚哆?開始挖掘真相吧...
首先,我需要看到容器中實(shí)際運(yùn)行的進(jìn)程混狠。
docker exec my-app top -m
啊哈岸霹! 有一個(gè)Gradle進(jìn)程正在運(yùn)行,它報(bào)告的RSS( Resident Set Size )與我們的Spring Boot應(yīng)用程序一樣大将饺。 看看Dockerfile贡避,很容易看出原因:
FROM anapsix/alpine-java:8_jdk
# Create app directory
RUN mkdir -p /app
WORKDIR /app
# Bundle app source
COPY . /app
# Build the solution (using the gradle task)
RUN ./gradlew build
EXPOSE 8080
CMD ["./gradlew", "bootRun"]
CMD使用gradle任務(wù)來運(yùn)行應(yīng)用程序。 gradle啟動(dòng)應(yīng)用程序后沒有退出予弧,它掛在了那里贸桶。
1 第一次優(yōu)化
所以,第一次內(nèi)存優(yōu)化的目的很簡(jiǎn)單 - 干掉Gradle過程桌肴! 我只需要從使用Gradle任務(wù)引導(dǎo)應(yīng)用程序改為直接使用Java執(zhí)行.jar文件皇筛。
CMD ["java", "-jar", "build/libs/{app name}.jar"]
現(xiàn)在讓我們看看容器中的top
Gradle進(jìn)程被干掉了! 我們削減了50%內(nèi)存消耗坠七。
2 第二次優(yōu)化
即使我對(duì)Spring和Java runtime不甚了解水醋,我仍然認(rèn)為我們可以做得更好。 一個(gè)沒有流量的簡(jiǎn)單API居然要382MB內(nèi)存彪置,...我們肯定錯(cuò)過了什么拄踪。
使用-Xmx56m指定運(yùn)行時(shí)的 heap size limit(堆棧大小) 拳魁。 我想每個(gè)Java開發(fā)人員都知道這一點(diǎn)惶桐。 將這個(gè)參數(shù)添加到我們的$ java -jar
命令將會(huì)限制Java堆棧大小為56MB。 運(yùn)行時(shí)將嘗試通過運(yùn)行垃圾回收來保持它低于這個(gè)數(shù)字潘懊。要設(shè)置堆棧大小姚糊,我們可以在JAVA_OPTS環(huán)境變量設(shè)置Xmx參數(shù):
docker run -e "JAVA_OPTS=-Xmx52m" app-image
這就OK了嗎? 非也授舟!Java實(shí)際上忽略了我們的變量救恨,并啟用了默認(rèn)值。
其原因在于Spring Boot释树。 Spring Boot會(huì)將任何環(huán)境變量傳遞給應(yīng)用程序 - 但是我們的JAVA_OPTS并非是針對(duì)應(yīng)用程序的肠槽,而是針對(duì)Java runtime本身的。 所以我們需要使用$ JAVA_OPTS變量來 `exec java`奢啥。 這需要對(duì)Dockerfile進(jìn)行一些小改動(dòng):
ENTRYPOINT exec java $JAVA_OPTS -jar build/libs/{app name}.jar
我在命令中使用'exec'秸仙,以便它創(chuàng)建的子進(jìn)程替換主進(jìn)程。 保持容器整潔桩盲。
現(xiàn)在寂纪,當(dāng)我們運(yùn)行容器時(shí),$JAVA_OPTS被傳遞給runtime正驻,正如我們所料弊攘。 我們?cè)O(shè)置的其他任何環(huán)境變量當(dāng)然也可以被Spring Boot應(yīng)用拿到。
這使我們能夠按照自己的意思調(diào)優(yōu)Java姑曙。 以下是應(yīng)用-Xmx56m參數(shù)后的結(jié)果:
得到的教訓(xùn)
構(gòu)建工具
不要使用Gradle運(yùn)行應(yīng)用程序襟交。 它在開發(fā)過程中非常棒,但是Gradle進(jìn)程掛在那伤靠,占用了容器不少的內(nèi)存捣域。
JAVA_OPTS
我不太明白為什么我需要這樣做。 Spring Boot本身不是應(yīng)該優(yōu)化了效率和性能并且拆箱即用嗎宴合? 為什么我必須通過在堆棧上添加內(nèi)存限制來增加這個(gè)風(fēng)險(xiǎn) 焕梅? 或者更好的問題是:為什么默認(rèn)值想要這么多內(nèi)存? 我對(duì)JVM經(jīng)驗(yàn)缺乏阻止我回答這個(gè)問題卦洽!
英文原文:
Memory analysis of a Spring Boot application in docker - lessons learnt