SpringBoot之系统优化

标签: 无 分类: 未分类 创建时间:2022-08-02 01:34:22 更新时间:2025-01-17 10:39:22

前言

使用springboot开发项目,一个普通的项目,简单的项目,启动起来轻轻松松就到了500m以上,在本地开发不成问题,本地开发有16个G的内存,可是部署到服务器上,服务器上的内存很贵啊,特别是阿里云的服务器,只有4核8G内存,部署几个java程序就直接把内存撑满了,系统都启动不了了。

参考文章:
1.Spring Boot 这样优化,让你的项目飞起来! 需要考虑:线程数、超时时间、jvm优化。
2.优化spring boot应用后6s内启动内存减半 1.关于SPRING DATA REPOSITORY SCANNING;2.关于WEBAPPLICATIONCONTEXT;3.关于SERVLET容器;4.关于ARCHAIUS配置组件;
3.记spring boot线上项目内存优化 通过设定Xmx(程序运行期间最大可占用的内存大小)、Xss(jvm启动的每个线程分配的内存大小)、XX:ParallelGCThreads(GC线程数)以及关闭了JIT功能,达成了降低内存占用的目的,java -Xmx128m -Xss256k -XX:ParallelGCThreads=2 -Djava.compiler=NONE -jar build/libs/XXX.jar
4.为何 Java 内存占用比.Net 内存占用高这么多? 这里有进行相关的讨论。
5.spring-boot内存占用过高 除了设置Xmx,还设置了 export MALLOC_ARENA_MAX=8

1.监测工具

springboot应用占用很大的内存,内存这个问题,在单机开发其实显示不出来,但是部署到服务器上,就会有了显著的限制,一个空的 springboot 项目,也会占用很大的内存,

参考文章:
1.jvisualvm 工具使用
2.惊呆了,Spring Boot居然这么耗内存!
3.减少Spring boot启动内存(开发环境) 1.修改tomcat的启动线程数,-XX:ParallelGCThreads调整GC线程的大小。2.修改jvm大小。这里要注意的是,register项目不需要太多资源(我这里设置128M),Gateway和config服务可以比register多一点(256M)。其它业务性服务才需要更多的资源(512M)。
4.Spring Boot Memory Performance 这里还提到了一些查看内存使用的工具

2.jvm参数优化

比较典型的配置如下:

1
JAVA_OPTS="-Xms512m -Xmx2048m -Xss256k"

我尝试了修改Xss为256k,结果内存占用减少的并不明显,好像也没有怎么减少。其他的一些说明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
## 初始Heap大小,使用的最小内存,cpu性能高时此值应设的大一些
-Xms512m

##Java heap最大值,使用的最大内存。
-Xmx512m

##上面两个值是分配JVM的最小和最大内存,取决于硬件物理内存的大小,建议均设为物理内存的一半。

## young generation的heap大小,一般设置为Xmx的3/4分之一
-Xmn256m

## 设定内存的永久保存区域
-XX:PermSize
## 设定最大内存的永久保存区域
-XX:MaxPermSize

-XX:MaxNewSize:
## 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。这个不能指定为128k,否则就会出现:“The stack size specified is too small, Specify at least 228k” 错误。
-Xss256K

## 会使得 Xms没有意义,这个参数让jvm忽略Xmx参数,疯狂地吃完一个G物理内存,再吃尽一个G的swap。
+XX:AggressiveHeap

## 现实垃圾收集信息
-verbose:gc
## 指定垃圾收集日志文件
-Xloggc:gc.log

## 缩短minor收集的时间
-XX:+UseParNewGC
## 缩短major收集的时间,此选项在Heap Size 比较大而且Major收集时间较长的情况下使用更合适。
-XX:+UseConcMarkSweepGC

参考文章:
1.轻松永远记住经典jvm参数 这里也不是说明了全部的jvm参数。
2.Spring cloud开发内存占用过高解决方法 这里也是针对每一个应用程序添加了jvm参数进行修改。
4.springboot降低内存占用 这里就是上面的配置
5.springboot项目启动内存占用过高问题如何解决 和上面的配置差不多
6.The stack size specified is too small, Specify at least 228k
7.一定要记住的14个JVM内存配置参数 对每一个参数都进行了相应的说明
8.Linux系统线程的Xss设置不生效原因

3.关闭JIT

至于在线上环境到底要不要关闭JIT功能,我现在还没有一个确定的概念,但是下面的方法可以关闭jit。

1
java -Djava.compiler=NONE your_main_class_file_name
参考文章:
1.如何关闭JIT 在启动JVM的时候,只需增加-Xint或者-Djava.compiler=NONE选项即可
2.禁用 JIT
3.看了这篇【JIT编译器】,你也能说你会java性能优化了!
4.SpringBoot应用刚启动时服务报大量超时的问题 有了JIT技术之后,JVM还是通过解释器进行解释执行。但是,当JVM发现某个方法或代码块运行时执行的特别频繁的时候,就会认为这是“热点代码”(Hot Spot Code)。然后JIT会把部分“热点代码”翻译成本地机器相关的机器码,并进行优化,然后再把翻译后的机器码缓存起来,以备下次使用。
5.为什么 SpringBoot 应用刚启动的时候比较卡,过一会就好了? 1.提升JIT优化的效率,在提升JIT优化效率的设计上,大家可以了解一下阿里研发的JDK——Dragonwell。2.降低瞬时请求量,就是说在应用刚刚启动的时候,通过调节负载均衡,不要很快的把大流量分发给他,而是先分给他一小部分流量,通过这部分流量来触发JIT优化,等优化好了之后,再把流量调大。

4.GraalVM

这个另外开一篇文章单独进行学习和介绍吧,目前(2022年08月04日)来说可能还有很多的问题。SpringBoot之GraalVM

5.OpenJ9

除了上面的GraalVM之外,还有一种技术就是使用OpenJ9替换默认的OpenJDK,也可以显著的减少内存占用。我尝试了将基础镜像 openjdk:8-jre 换成 adoptopenjdk/openjdk8-openj9:alpine-slim,确实好像有一些提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 基础镜像
FROM adoptopenjdk/openjdk8-openj9:alpine-slim
# author
MAINTAINER ruoyi

# 挂载目录
VOLUME /home/ruoyi
# 创建目录
RUN mkdir -p /home/ruoyi
# 时区设置
RUN echo "Asia/shanghai" > /etc/timezone
# 安装字体
# RUN apk add --update ttf-dejavu fontconfig && rm -rf /var/cache/apk/*
RUN echo -e 'https://mirrors.aliyun.com/alpine/v3.6/main/\nhttps://mirrors.aliyun.com/alpine/v3.6/community/' > /etc/apk/repositories \
&& apk update \
&& apk upgrade \
&& apk --no-cache add ttf-dejavu fontconfig
# 设置环境变量
ENV JAVA_OPTS="-server -Xms512m -Xmx512m -Xss256K"
# 指定路径
WORKDIR /home/ruoyi
# 复制jar文件到路径
COPY ./jar/ruoyi-system.jar /home/ruoyi/ruoyi-system.jar
## 启动系统服务,限制内存使用量
ENTRYPOINT exec java ${JAVA_OPTS} -jar ruoyi-system.jar

使用openj9之后的内存占用

使用openjdk:8-jre之前的内存占用

可以看到,上面的对比,同样的代码,确实好像占用的内存不一样。

参考文章:
1.JDK 的另一个选择:OpenJDK with Eclipse OpenJ9 这里做了一个简单的测试,对比了使用 Hotspot 和 OpenJ9 的区别,这里看出来,OpenJ9确实可以减少内存占用,虽然不是很多,但也是30%的样子。
2.Docker环境下Spring Boot应用内存飙升分析与解决 这里讲了关于jvm内存的初始分配内存原理,并使用了 exec java 限制了内存的大小。同时提供了三种docker镜像的优化,OpenJ9、GraalVM和Fabric8。

问题

(1) java.lang.NoClassDefFoundError: sun.awt.X11FontManager
使用了openj9之后,可能一些字体就被阉割掉了,这个时候就需要重新进行安装字体。在dockerfile文件中加入下面的内容。

1
RUN apk add --update ttf-dejavu fontconfig && rm -rf /var/cache/apk/*

这里还有一个问题,就是安装的时候会非常的慢,甚至有时候会卡在 ttf-dejavu 的部分

参考文章:
1.遇到 Could not initialize class sun.awt.X11FontManager 不要怕
2.如何解决Alpine Docker镜像字体的问题 这里解决了问题,我没有执行 COPY ./simhei.ttf /usr/share/fonts/simhei.ttf
3.如何在Docker容器中安装Linux软件包? 这里是ubuntu类的docker镜像安装软件
4./bin/sh: apt-get: not found
5.解决docker alpine缺少字体的问题 缺点是每次打包都要安装 ttf-dejavu 比较慢 , 因此该方法不推荐
6.记录一次解决openjdk8-openj9:alpine-slim缺少字体的经历

(2) OCI runtime exec failed: exec failed: unable to start container process: exec: “/bin/bash”: stat /bin/bash: no such file or directory: unknown
在使用OpenJ9镜像的时候,进入容器的时候,总是无法进入,因为没有/bin/bash命令行,需要使用 /bin/sh 命令行

1
2
3
4
## 错误
docker exec -it 9179fba23a4b /bin/bash
## 正确
docker exec -it 9179fba23a4b /bin/sh

(3) java获取的时区不正确
在使用docker镜像进行部署的时候,出现了容器中java获取到的系统当前时间和宿主机系统时间相差八个小时的问题,我在dockerfile中进行了设置,但是不知道是不是因为我使用的是 FROM adoptopenjdk/openjdk8-openj9:alpine-slim 镜像,所以下面的语句不生效,使用 date -R 获取到的时间还是不对。

1
2
# 设置时区
RUN echo "Asia/shanghai" > /etc/timezone

【尝试】

我发现/etc/配置文件中没有localtime文件,只有/et/timezone文件,应该是我上面输出的内容,后来我改成了

1
2
3
4
# 时区设置
RUN apk add -U tzdata
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone

结果出现了问题: unable to select packages: tzdata (no such package):The command ‘/bin/sh -c apk add -U tzdata’ returned a non-zero code,后来我改了dockerfile文件。

1
2
3
4
5
# 时区设置
RUN apk update && \
apk add --no-cache tzdata
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone

结果还是出现 :tzdata (no such package) 。

【解决】
最后通过修改alpine的镜像改为阿里云的镜像解决了这个问题

1
2
3
4
5
6
7
# 时区设置
RUN echo -e 'https://mirrors.aliyun.com/alpine/v3.6/main/\nhttps://mirrors.aliyun.com/alpine/v3.6/community/' > /etc/apk/repositories \
&& apk update \
&& apk upgrade \
&& apk add --no-cache tzdata
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo 'Asia/Shanghai' >/etc/timezone
参考文章:
1.Docker镜像时区不对问题的解决【ubuntu】【centos】
2.解决docker容器内时区无法调整
3.centos7查看修改时区 这个好像没什么参考价值,因为我的镜像并不是基于centos的
4.Docker openjdk-8-jdk-alpine 容器时间与jdk时区不同修改方法 RUN echo “Asia/Shanghai” > /etc/timezone 这个没有什么用,我已经在dockerfile中配置了相关的内容,但是没有起作用
5.alpine镜像修改docker的时区 转
6.docker 时区调整 这里有 Alpine、Debian、Ubuntu和CentOS不同系统的时区设置。
7.修改nginx镜像时区 这里镜像是基于nginx:alpine的
8.如何查看docker容器里的操作系统 使用 cat /etc/issue 查看系统版本
9.docker: Apline configure timezone RUN apk update && apk add –no-cache tzdata
10.Alpine Docker images no longer have tzdata package in 3.0
11.校正alpine镜像的时区 RUN apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo “Asia/Shanghai” > /etc/timezone && apk del tzdata
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。