如何在对Docker-java项目进行jvm调优-内存

参考链接

  1. 在docker中 java进程的内存设置
  2. Why does my Java process consume more memory than Xmx?

1. 分析内存使用情况

1.1 进入docker容器

# 查看容器列表
docker ps
# 根据容器name或id进入容器命令行终端
docker exec -it <container_name> bash

1.2 通过jps查看当前java进程列表

三种不同详细程度的进程列表(主要是获取java进程的lvmid)

root@21e4300860c8:/# jps
1 jar
79 Jps

root@21e4300860c8:/# jps -l
1 /knx-organization-service-exec.jar
127 sun.tools.jps.Jps

root@21e4300860c8:/# jps -lv
1 /knx-organization-service-exec.jar -Xmx512m -Xms512m -Dspring.profiles.active=dev,jiewli -Djasypt.encryptor.password= -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=18000
139 sun.tools.jps.Jps -Dapplication.home=/usr/lib/jvm/java-8-openjdk-amd64 -Xms8m

1.3 通过jstat -gccapacity统计java进程的内存池容量

root@21e4300860c8:/# jstat -gccapacity -h 20 1 250
NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC 
174592.0 174592.0 174592.0 7680.0 8704.0 157184.0   349696.0   349696.0   349696.0   349696.0      0.0 1136640.0  98280.0      0.0 1048576.0  11904.0     46     4
174592.0 174592.0 174592.0 7680.0 8704.0 157184.0   349696.0   349696.0   349696.0   349696.0      0.0 1136640.0  98280.0      0.0 1048576.0  11904.0     46     4
174592.0 174592.0 174592.0 7680.0 8704.0 157184.0   349696.0   349696.0   349696.0   349696.0      0.0 1136640.0  98280.0      0.0 1048576.0  11904.0     46     4
174592.0 174592.0 174592.0 7680.0 8704.0 157184.0   349696.0   349696.0   349696.0   349696.0      0.0 1136640.0  98280.0      0.0 1048576.0  11904.0     46     4
174592.0 174592.0 174592.0 7680.0 8704.0 157184.0   349696.0   349696.0   349696.0   349696.0      0.0 1136640.0  98280.0      0.0 1048576.0  11904.0     46     4
174592.0 174592.0 174592.0 7680.0 8704.0 157184.0   349696.0   349696.0   349696.0   349696.0      0.0 1136640.0  98280.0      0.0 1048576.0  11904.0     46     4
174592.0 174592.0 174592.0 7680.0 8704.0 157184.0   349696.0   349696.0   349696.0   349696.0      0.0 1136640.0  98280.0      0.0 1048576.0  11904.0     46     4

说明:

  1. 新生代空间由Survivor0Survivor1Eden(幸存者空间0、幸存者空间1、伊甸园)三部分组成
  2. OGC = sum(all OC),目前老年代只有一个空间,故而OGCOC相等。可以探究一下老年代有多个空间的时候有什么差异。
  3. JVM参数-Xmx512m最大堆内存等于NGCMX + OGCMX
  4. JVM参数-Xms512m最小堆内存等于NGCMN + OGCMN
  5. 将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展。因为JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms-Xmx相等以避免在每次GC后调整堆的大小。对象的堆内存由称为垃圾回收器的自动内存管理系统回收。
  6. Full GC触发条件:
    1. 调用System.gc
    2. 老年代空间不足
    3. 方法区空间不足
    4. 通过Minor GC(新生代GC)后进入老年代的平均大小大于老年代的可用内存
    5. 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
  7. JVM参数-XX:MetaspaceSize元空间初始化内存大小,默认是20.79MB,超过20.79MB后,每次元空间扩增都会触发Full GC,所以这个值设大一点可以减少Full GC次数。
  8. JVM参数-XX:MaxMetaspaceSize元空间最大内存大小,默认是无限(超过宿主机内存视为与宿主机内存一致),设置这个值,可以避免在发生故障时占用大量宿主机内存资源,影响其他服务。
列代码列名容量(kB)容量(MB)
NGCMN初始新生代空间容量174592.0170.5
NGCMX最大新生代空间容量174592.0170.5
NGC当前新生代容量174592.0170.5
S0C当前幸存者空间0容量7680.07.5
S1C当前幸存者空间1容量8704.08.5
EC当前Eden(伊甸园)区空间容量157184.0153.5
OGCMN初始老年代空间容量349696.0341.5
OGCMX最大老年代空间容量349696.0341.5
OGC当前老年代空间容量349696.0341.5
MCMN初始元空间容量0.00
MCMX最大元空间容量1136640.01,110
MC当前元空间容量98280.095.9765625
CCSMN压缩的类空间初始容量0.00
CCSMX压缩的类空间最大容量1048576.01,024
CCSC当前压缩的类空间容量11904.011.625
YGC新生代GC事件数量46
FGC完整GC事件数量4

1.4 也可以通过jstat -gc来统计

root@7f9fbd4518a4:/# jstat -gc -h 20 1 250
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
10240.0 10240.0 2416.0  0.0   154112.0 123601.6  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123601.6  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123601.6  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123893.9  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123893.9  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123893.9  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123893.9  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123893.9  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123893.9  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071
10240.0 10240.0 2416.0  0.0   154112.0 123893.9  349696.0   60088.4   81112.0 75681.7 9432.0 8462.8     38    1.640   3      0.431    2.071

说明:

列代码列名KBMB备注
S0C幸存者空间0容量10240.010
S1C幸存者空间1容量10240.010
S0U幸存者空间1利用率2416.02.35
S1U幸存者空间1利用率0.00.0
ECEden(伊甸园)空间容量154112.0150.5
EUEden(伊甸园)空间利用率123601.0120.70
OC老年代空间容量349696.0341.5
OU老年代空间利用率60088.058.67
MC元空间容量81112.079.21
MU元空间利用率75681.773.90
CCSC压缩类空间容量9432.09.21Compressed class space capacity,对应参数-XX:CompressedClassSpaceSize
CCSU压缩类空间利用率8462.08.26
YGC新生代GC事件次数38
YGCT新生代GC事件时间1.640
FGCFull GC事件次数3
FGCTFull GC事件时间0.431
GCT总GC时间2.071

1.4 通过jstat -gcutil统计java进程的垃圾收集统计信息

不限制采集次数,采集过程中尽量模拟一般情况下的系统调用情况,直到触发gc事件。

触发一次新生代GC事件Minor GC(观察YGC数量+1)

root@21e4300860c8:/# jstat -gcutil -h 20 1 250
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT  
  0.00   8.68  98.93  16.94  92.54  89.70     49    1.761     4    0.908    2.669
  0.00   8.68  98.96  16.94  92.54  89.70     49    1.761     4    0.908    2.669
  0.00   8.68  98.96  16.94  92.54  89.70     49    1.761     4    0.908    2.669
  0.00   8.68  98.96  16.94  92.54  89.70     49    1.761     4    0.908    2.669
  0.00   8.68  98.96  16.94  92.54  89.70     49    1.761     4    0.908    2.669
  6.80   0.00   0.00  16.95  92.52  89.72     50    1.766     4    0.908    2.673
  6.80   0.00   0.74  16.95  92.52  89.72     50    1.766     4    0.908    2.673
  6.80   0.00   1.27  16.95  92.52  89.72     50    1.766     4    0.908    2.673
  6.80   0.00   2.67  16.95  92.52  89.72     50    1.766     4    0.908    2.673
  6.80   0.00   2.67  16.95  92.52  89.72     50    1.766     4    0.908    2.673
  6.80   0.00   2.67  16.95  92.52  89.72     50    1.766     4    0.908    2.673
  6.80   0.00   2.67  16.95  92.52  89.72     50    1.766     4    0.908    2.673

触发一次完整GC事件Full GC,如果空间过大,一般情况下无法人为触发,需要通过时间等待(项目启动时间除以FGC可知多长时间触发一次Full GC),一般是1小时触发一次Full GC。
或许也可以让survivor空间利用率接近100%触发Full GC。

root@21e4300860c8:/# jstat -gcutil -h 20 1 250
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00  99.67  53.34  19.88  93.38  90.50     45    1.680     3    0.627    2.308
  0.00  99.67  55.03  19.88  93.38  90.50     45    1.680     3    0.627    2.308
  0.00  99.67  55.03  19.88  93.38  90.50     45    1.680     3    0.627    2.308
  0.00  99.67  55.03  19.88  93.38  90.50     45    1.680     3    0.627    2.308
  0.00  99.67  56.72  19.88  93.38  90.50     45    1.680     3    0.627    2.308
  0.00  99.67  56.72  19.88  93.38  90.50     45    1.680     3    0.627    2.308
 28.13   0.00   0.00  21.17  93.02  90.13     46    1.729     4    0.627    2.356
  0.00   0.00   0.00  16.94  92.41  89.38     46    1.729     4    0.908    2.637
  0.00   0.00   0.84  16.94  92.41  89.38     46    1.729     4    0.908    2.637
  0.00   0.00   0.84  16.94  92.41  89.38     46    1.729     4    0.908    2.637
  0.00   0.00   0.84  16.94  92.41  89.38     46    1.729     4    0.908    2.637
  0.00   0.00   0.90  16.94  92.41  89.38     46    1.729     4    0.908    2.637
  0.00   0.00   0.92  16.94  92.41  89.38     46    1.729     4    0.908    2.637
  0.00   0.00   0.92  16.94  92.41  89.38     46    1.729     4    0.908    2.637

1.5 查看元空间情况

# 查看java实例的某个参数
# 查看MetaspaceSize的值,默认是-XX:MetaspaceSize=21807104(20.79 MB)
jinfo -flag MetaspaceSize <jvmid>
# 查看MaxMetaspaceSize的值,默认是-XX:MaxMetaspaceSize=18446744073709547520(数值超过宿主机最大内存,视为无限或与宿主机一致)
jinfo -flag MaxMetaspaceSize <jvmid>

1.6 查看docker容器状态

# 在docker容器外部(宿主机)终端
docker stats
# 查看某个docker容器的状态
docker stats <container_name | id>

命令结果如下:

CONTAINER ID   NAME                       CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O     PIDS
38b70395877a   knx-organization-service   0.49%     426.4MiB / 3.682GiB   11.31%    4.88MB / 4.35MB   47.6MB / 0B   57
7f9fbd4518a4   user-account-service       0.38%     479.4MiB / 3.682GiB   12.71%    1.84MB / 2.67MB   72.1MB / 0B   48

说明:

列名描述
CONTAINER ID and Name容器的ID和名称
CPU % and MEM %容器正在使用的主机CPU和内存的百分比
MEM USAGE / LIMIT容器正在使用的总内存以及允许使用的总内存量
NET I/O容器通过其网络接口发送和接收的数据量
BLOCK I/O容器已从主机上的块设备读取和写入的数据量
PIDs容器创建的进程或线程数

I/O设备大致分为两类:块设备和字符设备。块设备将信息存储在固定大小的块中,每个块都有自己的地址。数据块的大小通常在512字节到32768字节之间。块设备的基本特征是每个块都能独立于其它块而读写。磁盘是最常见的块设备

基于结果第一条可知:

  1. docker容器的内存最大限制是3.682GiB,实际上就是宿主机的整个内存,相当于无限制。
  2. docker容器已使用的内存大小是426.4MiB,与11.31% * 3.682GiB一致,实际上就是容器内java进程的实际内存使用大小(很接近),说明docker容器除了服务外没有多少额外内存消耗。
  3. docker容器内的进程或线程数为57,进程只有一个java服务,所以可以视为java服务的线程数量。

1.7 计算性能参数

已知当前老年代空间容量为341.5 MB,再根据新生代GC和Full GC两种事件过程采集的结果可以计算出:

  1. 老年代空间利用率:16.94% ~ 21.17%
  2. 老年代空间利用大小区间:57.8501 MB ~ 72.29555 MB
  3. 默认老年代占堆内存的2/3,可以计算出适合的最小堆大小:72.29555/0.66=109.53871 MB
  4. 将最小堆大小往上推一个最接近的1024整除数:1024/8=128 MB
  5. 非堆内存不会被垃圾收集,可以视为永久代,当前使用了多少,基本上就只需要多少,根据CCSC往上推算最接近的1024整除数:1024/64=16 MB
  6. 当前线程为57个,考虑到当前实例是开发环境,并发率很低,算作最大线程为100个

2. 修改JVM参数-Xmx-Xms-XX:MetaspaceSize-XX:MaxMetaspaceSize

Java8开始已经移除了永久代空间(PermGen或permanent generation)即-XX:PermSize-XX:MaxPermSize两个参数是无效的。

取而代之的是元空间(metaspace):

  1. -XX:MetaspaceSize:最小元空间大小:并非初始化元空间大小,元空间一开始是0,并且不断扩增,直到MetaspaceSize,都不会触发Full GC,而一旦扩增超过MetaspaceSize后,每次扩增都会触发Full GC
  2. -XX:MaxMetaspaceSize:最大元空间大小:扩增的上限,默认是无限,设置一个值,避免一个服务因错误地不断加载类而占用整个服务器的内存,从而影响其他服务的运行。

修改项目镜像的Dockerfilejava命令的JVM参数

java \
-Xmx128m \
-Xms128m \
-XX:MetaspaceSize=128m \
-XX:MaxMetaspaceSize=256m \
......

并且重新构建镜像并运行。

预估Docker容器的内存限制

Max memory = [-Xmx] + [-XX:MaxMetaspaceSize] + number_of_threads * [-Xss]
  • 26
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Docker-compose是一个用于定义和运行多个Docker容器的工具,可以用来部署Java项目。具体步骤如下: 1. 编写Dockerfile文件,用于构建Java应用程序的Docker镜像。 2. 编写docker-compose.yml文件,定义Java应用程序容器的配置和依赖关系。 3. 在本地使用docker-compose命令启动容器,测试Java应用程序是否能够正常运行。 4. 将Docker镜像上传到Docker Hub或私有仓库中。 5. 在目标服务器上安装Dockerdocker-compose。 6. 从Docker仓库中拉取Java应用程序的Docker镜像。 7. 在目标服务器上使用docker-compose命令启动Java应用程序容器。 8. 验证Java应用程序是否能够正常运行。 以上是使用docker-compose部署Java项目的基本步骤,具体实现方式可以根据实际情况进行调整。 ### 回答2: Docker Compose 是一个使用 Docker 容器化部署简化工具,它可以帮助我们快速编排 Docker 容器环境。 准备工作: 1. 安装 Java 8 或以上版本 2. 安装 Docker 3. 安装 Docker Compose 步骤: 1. 在项目根目录下创建一个 docker-compose.yml 文件,文件中的内容类似如下: ``` version: "3" services: myapp: build: context: . dockerfile: Dockerfile ports: - "8080:8080" environment: - DATABASE_URL=jdbc:mysql://mysql/mydb - DATABASE_USER=root - DATABASE_PASSWORD=root depends_on: - mysql mysql: image: mysql:5.7 ports: - "3306:3306" environment: - MYSQL_ROOT_PASSWORD=root - MYSQL_DATABASE=mydb ``` 其中,有两个服务被定义,一个是 myapp 服务和一个 mysql 服务。myapp 使用当前目录中的 Dockerfile 来构建镜像,Dockerfile 是一个用来告诉 Docker 镜像如何构建的文件。在这个 example 中,myapp 镜像需要设置数据库连接信息,需要依赖一个名为 mysql 的服务。 2. 创建 Dockerfile 文件,在项目根目录创建 Dockerfile 文件,文件内容大致如下: ``` FROM openjdk:8-jre-alpine COPY target/myapp.jar myapp.jar CMD ["java", "-jar", "myapp.jar"] ``` 其中,FROM 声明使用基础镜像,COPY 告诉 Docker 将本地的 myapp.jar 文件复制到镜像中,并设置镜像启动时默认执行的命令。 3. 运行 docker-compose up,启动应用: ``` $ docker-compose up ``` 4. 访问应用 在浏览器中访问 `http://localhost:8080`,即可访问部署好的应用。 以上就是部署 Java 项目Docker Compose 部署简述。 ### 回答3: Docker Compose 是 Docker 容器编排工具,可以通过 YAML 文件定义一组容器并进行管理。因此,利用 Docker Compose 部署 Java 项目非常方便且易于管理。 首先,我们需要准备一个 Dockerfile。Dockerfile 指令统计展示如下: FROM openjdk:8-jdk-alpine WORKDIR /app COPY target/demo.jar /app EXPOSE 8080 CMD ["java", "-jar", "demo.jar"] 接下来,我们需要编写一个 docker-compose.yml 文件来定义一个 Docker 服务。docker-compose.yml 指令统计展示如下: version: '3' services: demo: build: context: . dockerfile: Dockerfile ports: - "8080:8080" restart: always 该文件共定义了一个服务,服务名为 demo,使用上述 Dockerfile 构建该服务,并将端口映射至本地主机的 8080 端口,并始终处于重新启动状态。 接下来,我们需要进入项目根目录,执行以下命令来启动服务: docker-compose up 这将会构建并启动服务。之后,你只需要访问本地主机的 8080 端口便可访问项目。 注意,本文提供的示例仅供参考,具体情况需要根据实际情况进行修改。 总之,Docker Compose 可以简化 Java 项目的部署和管理工作,为企业迅速迭代和成功上线提供帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值