如何设置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
...
目录
相关文章
|
5天前
|
存储 监控 Serverless
容器应用与集群管理
小陈计划使用ACK Serverless搭建WordPress企业网站,他向大刘请教核心工作。大刘指出主要涉及三个步骤:1) 创建ACK Serverless集群并预备资源,如数据库、网站镜像和持久存储(如NAS);2) 在集群上部署应用,确保应用无状态,设置副本数量以适应访问量,并使用PV和PVC连接NAS;3) 部署后进行集群和应用管理,包括监控和告警设置,关注容器日志和监控信息。
|
5天前
|
存储 Kubernetes API
使用Kubernetes管理容器化应用的深度解析
【5月更文挑战第20天】本文深度解析Kubernetes在管理容器化应用中的作用。Kubernetes是一个开源平台,用于自动化部署、扩展和管理容器,提供API对象描述应用资源并维持其期望状态。核心组件包括负责集群控制的Master节点(含API Server、Scheduler、Controller Manager和Etcd)和运行Pod的工作节点Node(含Kubelet、Kube-Proxy和容器运行时环境)。
|
16小时前
|
存储 Java 编译器
Java | 如何从内存解析的角度理解“数组名实质是一个地址”?
这篇文章讨论了Java内存的简化结构以及如何解析一维和二维数组的内存分配。在Java中,内存分为栈和堆,栈存储局部变量,堆存储通过`new`关键字创建的对象和数组。方法区包含静态域和常量池。文章通过示例代码解释了一维数组的创建过程,分为声明数组、分配空间和赋值三个步骤,并提供了内存解析图。接着,介绍了二维数组的内存解析,强调二维数组是“数组的数组”,其内存结构中,外层元素存储内层数组的地址。最后,文章提到了默认初始化方式对初始值的影响,并给出了相关测试代码。
6 0
|
2天前
|
存储 Java
【JAVA学习之路 | 进阶篇】ArrayList,Vector,LinkedList内存解析
【JAVA学习之路 | 进阶篇】ArrayList,Vector,LinkedList内存解析
|
4天前
|
存储 缓存 监控
Java一分钟之-Apache Ignite:分布式内存计算平台
【5月更文挑战第21天】Apache Ignite是一款开源的分布式内存计算平台,涉及内存数据网格、流处理和计算服务。本文关注其常见问题,如数据丢失、分区不均、内存管理和网络延迟。为保证数据一致性,建议使用适当的數據模式和备份策略,实现数据持久化。优化内存配置和监控网络可提升性能与稳定性。提供的Java代码示例展示了如何创建分区缓存并设置备份。正确配置和管理Ignite是构建高可用、高性能应用的关键,持续监控集群状态至关重要。
18 0
|
4天前
|
缓存 监控 Java
Java一分钟之-Apache Geode:分布式内存数据平台
【5月更文挑战第21天】Apache Geode是低延迟的分布式内存数据平台,用于构建实时应用,提供缓存、数据库和消息传递功能。本文聚焦于Geode的常见问题,如数据一致性(数据同步延迟和分区冲突)和性能瓶颈(网络延迟和资源管理不当),并提出解决方案。确保数据一致性可通过选择合适的数据策略和利用`InterestPolicy`、`CacheListener`;提升性能则需优化网络和合理配置资源。通过示例代码展示了如何创建和操作Geode的Region。正确配置和调优Geode对于实现高可用、高性能应用至关重要。
20 1
|
7天前
|
移动开发 前端开发 JavaScript
Java和web前端,IT新人该如何选择?,2024年最新Web前端内存优化面试
Java和web前端,IT新人该如何选择?,2024年最新Web前端内存优化面试
|
8天前
|
Java 数据库连接 Spring
K8S+Docker理论与实践深度集成java面试jvm原理
K8S+Docker理论与实践深度集成java面试jvm原理
|
8天前
|
存储 算法 Java
Java一分钟之-Java内存模型与垃圾回收机制概览
【5月更文挑战第16天】本文简述Java内存模型(JMM)和垃圾回收(GC)机制。JMM包括栈、堆、方法区、程序计数器和本地方法栈。GC负责回收不再使用的对象内存,常用算法有新生代、老年代和全堆GC。文章讨论了内存溢出、死锁和GC性能等问题,提出了解决方案,如调整JVM参数和优化GC策略。此外,还强调了避免内存泄漏、大对象管理及正确释放资源的重要性。理解这些概念有助于提升Java应用的性能和稳定性。
18 1
|
7天前
|
存储 算法 关系型数据库
实时计算 Flink版产品使用合集之在Flink Stream API中,可以在任务启动时初始化一些静态的参数并将其存储在内存中吗
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
21 4