如何设置Docker容器中Java应用的内存限制

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 最近在和阿里的一些同事谈起使用Docker部署Java应用的场景,其中一个大家普遍关心的问题就是如何设置容器中JVM的内存限制。本文介绍了一种方法能够让Docker容器中的Java应用自动适配容器的内存限制。

最近在和阿里的一些同事谈起使用Docker部署Java应用的场景,其中一个大家普遍关心的问题就是如何设置容器中JVM的内存限制。

14596085983505

如果使用官方的Java镜像,或者基于Java镜像构建的Docker镜像,都可以通过传递 JAVA_OPTS 环境变量来轻松地设置JVM的内存参数。比如,对于官方Tomcat 镜像,我们可以执行下面命令来启动一个最大内存为512M的tomcat实例

docker run --rm -e JAVA_OPTS='-Xmx512m' tomcat:8

在日志中,我们可以清楚地发现设置已经生效 “Command line argument: -Xmx512m”

02-Apr-2016 12:46:26.970 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version:        Apache Tomcat/8.0.32
02-Apr-2016 12:46:26.974 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server built:          Feb 2 2016 19:34:53 UTC
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server number:         8.0.32.0
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Name:               Linux
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log OS Version:            4.1.19-boot2docker
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Architecture:          amd64
02-Apr-2016 12:46:26.975 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Java Home:             /usr/lib/jvm/java-7-openjdk-amd64/jre
02-Apr-2016 12:46:26.976 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Version:           1.7.0_95-b00
02-Apr-2016 12:46:26.976 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log JVM Vendor:            Oracle Corporation
02-Apr-2016 12:46:26.977 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE:         /usr/local/tomcat
02-Apr-2016 12:46:26.977 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME:         /usr/local/tomcat
02-Apr-2016 12:46:26.978 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties
02-Apr-2016 12:46:26.978 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager
02-Apr-2016 12:46:26.978 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx512m
...

然而在Docker集群上部署运行Java容器应用的时候,仅仅对JVM的heap参数设置是不够的,我们还需要对Docker容器的内存资源进行限制:

1. 限制容器使用的内存的最大量,防止对系统或其他应用造成伤害
2. 能够将Docker容器调度到拥有足够空余的内存的节点,从而保证应用的所需运行资源

关于容器的资源分配约束,Docker提供了相应的启动参数

对内存而言,最基本的就是通过 -m参数来约束容器使用内存的大小

-m, --memory=""
Memory limit (format: <number>[<unit>]). Number is a positive integer. Unit can be one of b, k, m, or g. Minimum is 4M.

那么问题就来了,为了正确设置Docker容器内存的大小,难道我们需要同时传递容器的内存限制和JAVA_OPTS环境变量吗? 如下所示:

docker run --rm -m 512m -e JAVA_OPTS='-Xmx512m' tomcat:8

这个方法有两个问题

  1. 需要管理员保证容器内存和JVM内存设置匹配,否则可能引发错误
  2. 当对容器内存限制调整时,环境变量也需要重新设定,这就需要重建一个新的容器

是否有一个方法,可以让容器内部的JVM自动适配容器的内存限制?这样可以采用更加统一的方法来进行资源管理,简化配置工作。

大家知道Docker是通过CGroup来实现资源约束的,自从1.7版本之后,Docker把容器的local cgroups以只读方式挂载到容器内部的文件系统上,这样我们就可以在容器内部,通过cgroups信息来获取系统对当前容器的资源限制了。

我创建了一个示例镜像 registry.aliyuncs.com/denverdino/tomcat:8-autoheap
,其源代码可以从Github 获得。它基于Docker官方Tomcat镜像创建,它的启动脚本会检查CGroup中内存限置,并计算JVM最大Heap size来传递给Tomcat。其代码如下

#!/bin/bash
limit_in_bytes=$(cat /sys/fs/cgroup/memory/memory.limit_in_bytes)

# If not default limit_in_bytes in cgroup
if [ "$limit_in_bytes" -ne "9223372036854771712" ]
then
    limit_in_megabytes=$(expr $limit_in_bytes \/ 1048576)
    heap_size=$(expr $limit_in_megabytes - $RESERVED_MEGABYTES)
    export JAVA_OPTS="-Xmx${heap_size}m $JAVA_OPTS"
    echo JAVA_OPTS=$JAVA_OPTS
fi

exec catalina.sh run

说明:

  • 为了JVM自身的Non-Heap内存,以及监控,故障排查等场景,我们预留了部分内存(缺省256M),其余容器内存我们都分配给JVM的堆。
  • 这里没有对边界情况做进一步处理。在生产系统中需要根据情况做相应的设定,比如最大的堆大小等等。

现在我们启动一个tomcat运行在512兆的容器中

docker run -d --name test -m 512m registry.aliyuncs.com/denverdino/tomcat:8-autoheap

通过下列命令,从日志中我们可以检测到相应的JVM参数已经被设置成 256MB (512-256)

docker logs test

...
02-Apr-2016 14:18:09.870 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx256m
...

我们也可以方便的调整Java应用的内存.

Docker 1.10提供了对容器资源限制的动态修改能力。但是由于JVM无法感知容器资源修改,我们依然需要重启tomcat来变更JVM的内存设置,例如,我们可以通过下面命令把容器内存限制调整到1GB

docker update -m 1024m test
docker restart test

再次检查日志,相应的JVM Heap Size最大值已被设置为768MB

docker logs test

...
02-Apr-2016 14:21:07.644 INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Command line argument: -Xmx768MB
...
目录
相关文章
|
27天前
|
人工智能 算法 Java
Java与AI驱动区块链:构建智能合约与去中心化AI应用
区块链技术和人工智能的融合正在开创去中心化智能应用的新纪元。本文深入探讨如何使用Java构建AI驱动的区块链应用,涵盖智能合约开发、去中心化AI模型训练与推理、数据隐私保护以及通证经济激励等核心主题。我们将完整展示从区块链基础集成、智能合约编写、AI模型上链到去中心化应用(DApp)开发的全流程,为构建下一代可信、透明的智能去中心化系统提供完整技术方案。
169 3
|
30天前
|
消息中间件 缓存 Java
Spring框架优化:提高Java应用的性能与适应性
以上方法均旨在综合考虑Java Spring 应该程序设计原则, 数据库交互, 编码实践和系统架构布局等多角度因素, 旨在达到高效稳定运转目标同时也易于未来扩展.
96 8
|
2月前
|
人工智能 Java API
Java与大模型集成实战:构建智能Java应用的新范式
随着大型语言模型(LLM)的API化,将其强大的自然语言处理能力集成到现有Java应用中已成为提升应用智能水平的关键路径。本文旨在为Java开发者提供一份实用的集成指南。我们将深入探讨如何使用Spring Boot 3框架,通过HTTP客户端与OpenAI GPT(或兼容API)进行高效、安全的交互。内容涵盖项目依赖配置、异步非阻塞的API调用、请求与响应的结构化处理、异常管理以及一些面向生产环境的最佳实践,并附带完整的代码示例,助您快速将AI能力融入Java生态。
347 12
|
2月前
|
安全 Java API
Java SE 与 Java EE 区别解析及应用场景对比
在Java编程世界中,Java SE(Java Standard Edition)和Java EE(Java Enterprise Edition)是两个重要的平台版本,它们各自有着独特的定位和应用场景。理解它们之间的差异,对于开发者选择合适的技术栈进行项目开发至关重要。
263 1
|
3月前
|
设计模式 XML 安全
Java枚举(Enum)与设计模式应用
Java枚举不仅是类型安全的常量,还具备面向对象能力,可添加属性与方法,实现接口。通过枚举能优雅实现单例、策略、状态等设计模式,具备线程安全、序列化安全等特性,是编写高效、安全代码的利器。
|
4月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
1239 0
|
4月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
349 1
|
4月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
337 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
878 0