最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Docker環(huán)境下SpringBoot內(nèi)存飆升與解決方案

 更新時間:2025年11月17日 09:41:07   作者:螢火AI百寶箱  
文章介紹了SpringBoot應(yīng)用內(nèi)存飆升的原因,以及默認(rèn)的JVM內(nèi)存配置策略,通過設(shè)置JVM內(nèi)存參數(shù),可以有效地優(yōu)化內(nèi)存使用,文章還提供了優(yōu)化Dockerfile的建議,包括使用OpenJ9、GraalVM和Fabric8等方案

SpringBoot應(yīng)用內(nèi)存飆升

一個簡單的Spring Boot應(yīng)用,幾乎只有一個用戶在用,內(nèi)存竟然達(dá)到1.2G, 可怕

服務(wù)現(xiàn)狀

由于之前服務(wù)比較少,服務(wù)器資源充足,許多服務(wù)啟動時都未添加JVM參數(shù)(遺留問題)。

結(jié)果就是每個服務(wù)啟動都占用了1.2G-2G的內(nèi)存,有些服務(wù)的體量根本用不了這么多。那么,在Spring Boot中如果未設(shè)置JVM內(nèi)存參數(shù)時,JVM內(nèi)存是如何配置的呢?

JVM默認(rèn)內(nèi)存設(shè)置

當(dāng)運行一個Spring Boot項目時,如果未設(shè)置JVM內(nèi)存參數(shù),Spring Boot默認(rèn)會采用JVM自身默認(rèn)的配置策略。

在資源比較充足的情況下,開發(fā)者倒是不太用關(guān)心內(nèi)存的設(shè)置。但一旦涉及到資源不足,JVM優(yōu)化,那么就需要了解默認(rèn)的JVM內(nèi)存配置策略。

關(guān)于JVM內(nèi)存最常見的設(shè)置為初始堆大小(-Xms)和最大堆內(nèi)存(-Xmx)。很多人懶得去設(shè)置,而是采用JVM的默認(rèn)值。特別是在開發(fā)環(huán)境下,如果啟動的微服務(wù)比較多,內(nèi)存會被撐爆。

而JVM默認(rèn)內(nèi)存配置策略分兩種場景,大內(nèi)存空間場景和小內(nèi)存空間場景(小于192M)。

以4GB內(nèi)存為例,初始堆內(nèi)存大小和最大堆內(nèi)存大小如下圖:

默認(rèn)情況下,最大堆內(nèi)存占用物理內(nèi)存的1/4,如果應(yīng)用程序超過該上限,則會拋出OutOfMemoryError異常。初始堆內(nèi)存大小為物理內(nèi)存的1/64。

如果應(yīng)用程序運行在手機上或物理內(nèi)存小于192M時,JVM默認(rèn)的初始堆內(nèi)存大小和最大堆內(nèi)存大小如下圖:

最大堆內(nèi)存為物理內(nèi)存的1/2,初始堆內(nèi)存大小為物理內(nèi)存的1/64,但當(dāng)初始堆內(nèi)存最小為8MB,則為8MB。

默認(rèn)空余堆內(nèi)存小于40%時,JVM就會增大堆直到-Xmx的最大限制;空余堆內(nèi)存大于70%時,JVM會減少堆直到 -Xms的最小限制。

因此,服務(wù)器一般設(shè)置-Xms、-Xmx相等以避免在每次GC后調(diào)整堆的大小。對象的堆內(nèi)存由稱為垃圾回收器的自動內(nèi)存管理系統(tǒng)回收。

其中最大堆內(nèi)存是JVM使用內(nèi)存的上限,實際運行過程中使用多少便是多少。默認(rèn),分配給年輕代的最大空間量是堆總大小的三分之一。

針對最開始的問題,如果每個程序都按照默認(rèn)配置啟動,一臺服務(wù)器上部署多個應(yīng)用時,就會出現(xiàn)內(nèi)存吃緊的情況,造成一定的浪費。最簡單的操作就是在執(zhí)行java -jar啟動時添加上對應(yīng)的jvm內(nèi)存設(shè)置參數(shù)。

java -Xms64m -Xmx128m -jar xxx.jar

項目使用的是Docker部署, 我們先來查看 原來的Dockerfile文件

確實沒有設(shè)置-Xms、-Xmx

#設(shè)置鏡像基礎(chǔ),jdk8
FROM java:8
#維護人員信息
MAINTAINER FLY
#設(shè)置鏡像對外暴露端口
EXPOSE 8061
#將當(dāng)前 target 目錄下的 jar 放置在根目錄下,命名為 app.jar,推薦使用絕對路徑。
ADD target/certif-system-2.1.0.jar /certif-system-2.1.0.jar
# 時區(qū)設(shè)置
RUN echo "Asia/shanghai" > /etc/timezone
#執(zhí)行啟動命令
ENTRYPOINT ["java", "-jar","/certif-system-2.1.0.jar"]

優(yōu)化

限制JVM內(nèi)存

#設(shè)置變量 JAVA_OPTS

ENV JAVA_OPTS=""#這樣寫會以shell方式執(zhí)行,會替換變量

ENTRYPOINT java ${JAVA_OPTS}-Djava.security.egd=file:/dev/./urandom -jar /app.jar

#下面這樣寫法不行,他只是拼接不會識別變量

#ENTRYPOINT ["java","${JAVA_OPTS}","-Djava.security.egd=file:/dev/./urandom","-jar","app.jar"]

Spring Boot會將任何環(huán)境變量傳遞給應(yīng)用程序 - 但是我們的JAVA_OPTS并非是針對應(yīng)用程序的,而是針對Java runtime本身的。 所以我們需要使用$ JAVA_OPTS變量來 exec java。

這需要對Dockerfile進行一些小改動:

ENTRYPOINT exec java $JAVA_OPTS -jar app.jar

運行docker run命令

意思是運行時通過-e重置覆蓋環(huán)境變量中JAVA_OPTS參數(shù)信息。

docker run  -e  JAVA_OPTS='-Xmx1344M -Xms1344M -Xmn448M -XX:MaxMetaspaceSize=192M -XX:MetaspaceSize=192M'

參數(shù)解釋

JVM常見參數(shù)

  • 可通過JAVA_OPTS設(shè)置:
參數(shù)說明:
 
-server:一定要作為第一個參數(shù),在多個CPU時性能佳
 
-Xms:初始Heap大小,使用的最小內(nèi)存,cpu性能高時此值應(yīng)設(shè)的大一些
 
-Xmx:java heap最大值,使用的最大內(nèi)存
 
-XX:PermSize:設(shè)定內(nèi)存的永久保存區(qū)域
 
-XX:MaxPermSize:設(shè)定最大內(nèi)存的永久保存區(qū)域
 
-XX:MaxNewSize:
 
+XX:AggressiveHeap 會使得 Xms沒有意義。這個參數(shù)讓jvm忽略Xmx參數(shù),瘋狂地吃完一個G物理內(nèi)存,再吃盡一個G的swap。
 
-Xss:每個線程的Stack大小
 
-verbose:gc 現(xiàn)實垃圾收集信息
 
-Xloggc:gc.log 指定垃圾收集日志文件
 
-Xmn:young generation的heap大小,一般設(shè)置為Xmx的3、4分之一
 
-XX:+UseParNewGC :縮短minor收集的時間
 
-XX:+UseConcMarkSweepGC :縮短major收集的時間
 
提示:此選項在Heap Size 比較大而且Major收集時間較長的情況下使用更合適。

java.security.egd 作用

SecureRandom在java各種組件中使用廣泛,可以可靠的產(chǎn)生隨機數(shù)。但在大量產(chǎn)生隨機數(shù)的場景下,性能會較低。這時可以使用"-Djava.security.egd=file:/dev/./urandom"加快隨機數(shù)產(chǎn)生過程。

建議在大量使用隨機數(shù)的時候,將隨機數(shù)發(fā)生器指定為/dev/./urandom。

bug產(chǎn)生的原因請注意下面第四行源碼,如果java.security.egd參數(shù)指定的是file:/dev/random或者file:/dev/urandom,則調(diào)用了無參的NativeSeedGenerator構(gòu)造函數(shù),而無參的構(gòu)造函數(shù)將默認(rèn)使用file:/dev/random 。openjdk的代碼和hotspot的代碼已經(jīng)不同,openjdk在后續(xù)產(chǎn)生隨機數(shù)的時候沒有使用這個變量。

abstract class SeedGenerator {
......
    static {
        String egdSource = SunEntries.getSeedSource();
        if (egdSource.equals(URL_DEV_RANDOM) || egdSource.equals(URL_DEV_URANDOM)) {
            try {
                instance = new NativeSeedGenerator();
                if (debug != null) {
                    debug.println("Using operating system seed generator");
                }
            } catch (IOException e) {
                if (debug != null) {
                    debug.println("Failed to use operating system seed "
                                  + "generator: " + e.toString());
                }
            }
        } else if (egdSource.length() != 0) {
            try {
                instance = new URLSeedGenerator(egdSource);
                if (debug != null) {
                    debug.println("Using URL seed generator reading from "
                                  + egdSource);
                }
            } catch (IOException e) {
                if (debug != null)
                    debug.println("Failed to create seed generator with "
                                  + egdSource + ": " + e.toString());
            }
        }
......
    }

優(yōu)化后的Dockerfile文件

#設(shè)置基礎(chǔ)鏡像jdk8
FROM java:8
#維護人員信息
MAINTAINER FLY
#設(shè)置鏡像對外暴露端口
EXPOSE 8061
#將當(dāng)前 target 目錄下的 jar 放置在根目錄下,命名為 app.jar,推薦使用絕對路徑。
ADD target/certif-system-2.1.0.jar /certif-system-2.1.0.jar
# 設(shè)置環(huán)境變量
ENV JAVA_OPTS="-server -Xms512m -Xmx512m"
# 時區(qū)設(shè)置
RUN echo "Asia/shanghai" > /etc/timezone
#執(zhí)行啟動命令
#ENTRYPOINT ["java", "-jar","/certif-system-2.1.0.jar"]
ENTRYPOINT exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /certif-system-2.1.0.jar

優(yōu)化后的效果

JVM參數(shù)設(shè)置是否生效

通過 docker exec -it 5a8ff3925974 ps -ef | grep java 查看

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT   MEM %     NET I/O           BLOCK I/O         PIDS
5a8ff3925974   certif-system    0.74%     493.3MiB / 800MiB   61.66%    272kB / 304kB     7.54MB / 0B       97


[root@localhost certif]# docker exec -it 5a8ff3925974 ps -ef | grep java
root           1       0  5 12:13 ?        00:01:02 java -server -Xms512m -Xmx51

基礎(chǔ)鏡像優(yōu)化

減少Spring Boot減少JVM占用的三種Dockerfile鏡像配置:

OpenJ9

OpenJ9:取代Hotspot的IBM Eclipse項目。它已經(jīng)被開發(fā)很長一段時間,看起來已經(jīng)足夠成熟,可以用于生產(chǎn)。

您可以立即輕松地獲益,替換一些基本鏡像和一些參數(shù)可能足以為您的應(yīng)用程序提供巨大的推動力 - 我已經(jīng)通過更改 Dockerfile基本映像替換了一些應(yīng)用程序,節(jié)約了大約 1/3的內(nèi)存占用,增強了吞吐量。

FROM adoptopenjdk/openjdk8-openj9:alpine-slim
COPY target/app.jar /my-app/app.jar
ENTRYPOINT java $JAVA_OPTS -Xshareclasses -Xquickstart -jar /my-app/app.jar

GraalVM

GraalVM:圍繞這個由Oracle實驗室開發(fā)的有前途的虛擬機進行了大量宣傳。它為您提供了將應(yīng)用程序編譯為本機鏡像的選項,生成鏡像非常非??烨覂?nèi)存消耗很少,吸引人眼球的另一個功能是能夠與多種語言(如Javascript,Ruby,Python和Java)進行交互操作。

FROM oracle/graalvm-ce:1.0.0-rc15
COPY target/app.jar /my-app/app.jar
ENTRYPOINT java $JAVA_OPTS -jar /my-app/app.jar

Fabric8

Fabric8 shell:一個bash腳本,可根據(jù)應(yīng)用程序當(dāng)前運行環(huán)境自動為您配置JVM參數(shù)。它可以在這里下載,是這個研究項目的產(chǎn)物。它降低了不少內(nèi)存:

FROM java:openjdk-8-alpine
COPY target/app.jar /my-app/app.jar
COPY run-java.sh /my-app/run-java.sh
ENTRYPOINT JAVA_OPTIONS=${JAVA_OPTS} JAVA_APP_JAR=/my-app/app.jar /my-app/run-java.sh

雖然我們在應(yīng)用解決方案時總是需要考慮上下文,但對我來說,獲勝者是OpenJ9,從而以最少的配置實現(xiàn)了生產(chǎn)就緒的性能和內(nèi)存占用。

雖然仍然沒有找到使用不合適的情況,但這并不意味著它將成為一個銀彈解決方案,請記住,最好是測試替代品,看看哪種更適合您的需求。

優(yōu)化后的Dockerfile文件

#設(shè)置鏡像基礎(chǔ),jdk8
FROM adoptopenjdk/openjdk8-openj9:alpine-slim
#維護人員信息
MAINTAINER FLY
#設(shè)置鏡像對外暴露端口
EXPOSE 8061
#將當(dāng)前 target 目錄下的 jar 放置在根目錄下,命名為 app.jar,推薦使用絕對路徑。
ADD target/certif-system-2.1.0.jar /certif-system-2.1.0.jar
# 設(shè)置環(huán)境變量
ENV JAVA_OPTS="-server -Xms512m -Xmx512m"
# 時區(qū)設(shè)置
RUN echo "Asia/shanghai" > /etc/timezone
#執(zhí)行啟動命令
#ENTRYPOINT ["java", "-jar","/certif-system-2.1.0.jar"]
#ENTRYPOINT exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar /certif-system-2.1.0.jar
#ENTRYPOINT java $JAVA_OPTS -Xshareclasses -Xquickstart -jar /certif-system-2.1.0.jar
ENTRYPOINT java $JAVA_OPTS -Xshareclasses -Xquickstart -jar /certif-system-2.1.0.jar

優(yōu)化后的效果

備注

Xmx < limit

docker鏡像的內(nèi)存上限,不能全部給“-Xmx”。因為JVM消耗的內(nèi)存不僅僅是Heap,如下圖:

JVM基礎(chǔ)結(jié)構(gòu)如下:棧、堆。

棧:

  • JVM中的棧主要是指線程里面的棧,里面有方法棧、native方法棧、PC寄存器等等;每個方法棧是由棧幀組成的;每個棧幀是由局部變量表、操作數(shù)棧等組成。
  • 每個棧幀其實就代表一個方法

堆:

  • java中所有對象都在堆中分配;堆中對象又分為年輕代、老年代等等,不同代的對象使用不同垃圾回收算法。
  • -XMs:啟動虛擬機預(yù)留的內(nèi)存 -Xmx:最大的堆內(nèi)存

因此

 JVM = Heap + Method Area + Constant Pool + Thread Stack * num of thread

所以Xmx的值要小于鏡像上限內(nèi)存。

支持springboot多環(huán)境和jvm動態(tài)配置的Dockerfile

假設(shè)springboot項目 myboot-api , 在其根目錄下創(chuàng)建文件Dockerfile

內(nèi)容如下:

FROM java:8
MAINTAINER xxx
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
ENV LANG=zh_CN.UTF-8 \
	JAVA_OPTS="-server -Xms512m -Xmx512m" \
    SPRING_PROFILES_ACTIVE="dev"
#ARG JAR_FILE
#ADD ${JAR_FILE} app.jar
ADD target/myboot-api.jar app.jar
ENTRYPOINT exec java ${JAVA_OPTS} -Djava.security.egd=file:/dev/./urandom -jar -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} /app.jar

 其中ENV 環(huán)境變量

  • JAVA_OPTS JVM堆內(nèi)存起始最大值配置
  • SPRING_PROFILES_ACTIVE application.yml環(huán)境

Linux 命令行創(chuàng)建鏡像 啟動容器

echo "===============動態(tài)參數(shù)配置 begin===============>"
APPLICATION_NAME=xxx-srm-api
echo "image and container name is $APPLICATION_NAME"

# springboot啟動的端口號
BootPort=8082
echo "the spring boot ($APPLICATION_NAME) port is $BootPort"

# docker中的springboot啟動的端口號
DockerBootPort=8082

echo "===============動態(tài)參數(shù)配置 end===============>"
echo "build docker image"
# mvn dockerfile:build
docker build -f Dockerfile -t $APPLICATION_NAME:latest .

echo "current docker images:"
docker images | grep $APPLICATION_NAME

echo "start container ===============> "
docker run -d -p $BootPort:$DockerBootPort -e JAVA_OPTS="-server -Xms512m -Xmx512m" -e SPRING_PROFILES_ACTIVE="test"  --name $APPLICATION_NAME $APPLICATION_NAME:latest

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Docker容器傳輸文件的實現(xiàn)方式

    Docker容器傳輸文件的實現(xiàn)方式

    文章介紹了Docker容器與主機之間文件傳輸?shù)乃姆N常用方式:dockercp命令、Docker卷掛載、Dockerfile集成和網(wǎng)絡(luò)工具,分別適用于臨時傳輸、持久共享、鏡像集成及遠(yuǎn)程場景
    2025-10-10
  • Docker部署多種容器的詳細(xì)過程

    Docker部署多種容器的詳細(xì)過程

    本文介紹了在Windows和Linux系統(tǒng)上使用Docker部署MySQL和RabbitMQ容器的方法,對于MySQL使用docker?run命令,配置了端口映射、權(quán)限、時區(qū)和數(shù)據(jù)持久化等選項,本文給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2025-01-01
  • 利用Dockerfile優(yōu)化Nestjs構(gòu)建鏡像大小詳情

    利用Dockerfile優(yōu)化Nestjs構(gòu)建鏡像大小詳情

    這篇文章主要介紹了利用Dockerfile優(yōu)化Nestjs構(gòu)建鏡像大小詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • mac使用docker搭建kafka集群方式

    mac使用docker搭建kafka集群方式

    本文介紹了在MacOS上使用Docker部署Kafka集群的全過程,包括環(huán)境準(zhǔn)備、刪除歷史配置、編寫Dockerfile、啟動容器、配置Kafka以及生產(chǎn)者消費者測試等內(nèi)容
    2026-05-05
  • Docker搭建svn的方法步驟

    Docker搭建svn的方法步驟

    本文主要介紹了Docker搭建svn的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Docker容器日志查看和清理的圖文教程

    Docker容器日志查看和清理的圖文教程

    docker容器在運行過程中會產(chǎn)生日志,日志的量跟具體的容器有關(guān),下面這篇文章主要給大家介紹了關(guān)于Docker容器日志查看和清理的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • 搭建一個私有的Docker registry教程

    搭建一個私有的Docker registry教程

    這篇文章提供了一個非常務(wù)實的方法來處理搭建私有Docker registry時出現(xiàn)的各種錯綜復(fù)雜的情況。我們將會使用一個運行于DigitalOcean(之后簡稱為DO)的非常小巧的512MB VPS 實例
    2016-09-09
  • Docker部署Django的詳細(xì)指南

    Docker部署Django的詳細(xì)指南

    部署Django應(yīng)用到Docker中的詳細(xì)步驟是一個較為復(fù)雜的過程,涉及到許多技術(shù)和工具的結(jié)合使用,本文將詳細(xì)介紹如何從零開始使用Docker部署Django應(yīng)用,感興趣的小伙伴跟著小編一起來看看吧
    2024-12-12
  • docker-compose build使用參數(shù)args方式

    docker-compose build使用參數(shù)args方式

    這篇文章主要介紹了docker-compose build使用參數(shù)args方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Docker部署Nginx并修改配置文件的兩種方式

    Docker部署Nginx并修改配置文件的兩種方式

    這篇文章主要給大家介紹了關(guān)于Docker部署Nginx并修改配置文件的兩種方式,文中通過實例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2023-01-01

最新評論

普洱| 汉源县| 宜川县| 固阳县| 财经| 马关县| 平度市| 光山县| 江西省| 湖口县| 临沭县| 安庆市| 麦盖提县| 榕江县| 丹东市| 松滋市| 襄垣县| 和田县| 白朗县| 霸州市| 深州市| 徐汇区| 清徐县| 砚山县| 白水县| 铁岭县| 邵武市| 宁海县| 龙口市| 湖州市| 永善县| 南郑县| 微山县| 南汇区| 建平县| 壶关县| 临澧县| 台北县| 五寨县| 大竹县| 汉源县|