我们有一个 Docker 容器,其中包含在 AWS ECS 上运行的 Java 11 应用程序。该服务被配置为一旦使用 1.5GB RAM 就被终止
"memory": 1500,
"memoryReservation": 1137,
问题是我们没有看到仪表板中反映出内存使用量的增加 (ps:grafana 仪表板中的平线是由于 Prometheus 不再接收指标)。
我们已经做了几乎所有可能的事情来限制 Java 允许使用的堆内和堆外内存量,如果我检查 NMT 的输出,它的保留使用量最多为 1.1GB。我们现在的命令行是这样的:
java ‑Xmx600m ‑XX:+UseG1GC ‑XX:MaxMetaspaceSize=220m
‑XX:+PrintFlagsFinal ‑XX:NewRatio=1 ‑XX:MaxDirectMemorySize=40m
‑Xss512k ‑XX:ProfiledCodeHeapSize=70M ‑XX:NonProfiledCodeHeapSize=50M
‑XX:NonNMethodCodeHeapSize=8M ‑XX:+UseCodeCacheFlushing
‑XX:CompressedClassSpaceSize=32M ‑XX:ReservedCodeCacheSize=128M
‑XX:+SegmentedCodeCache ‑javaagent:/opentelemetry‑javaagent.jar
‑Dotel.javaagent.extensions=/opentelemetry‑java‑ecs‑extension‑all.jar
‑Dotel.resource.attributes=xxx,aws.ecs.launchtype=ec2
‑XX:NativeMemoryTracking=summary ‑XX:‑OmitStackTraceInFastThrow
‑Dspring.profiles.active=prd‑ecs ‑Dspring.cloud.consul.host=172.17.42.1
‑Dspring.cloud.consul.config.watch.enabled=false
‑Dcom.sun.management.jmxremote.port=9999
‑Dcom.sun.management.jmxremote.authenticate=false
‑Dcom.sun.management.jmxremote.ssl=false
‑Dlog4j2.formatMsgNoLookups=true ‑Dserver.port=8080
‑Daws.paramstore.enabled=true
‑Daws.paramstore.region=eu‑west‑1
‑Dspring.main.banner‑mode=off ‑jar /1234‑service.jar
我们对我们的容器如何可能突然使用 400MB 内存而 JMX 指标中没有任何迹象表示一无所知。
//编辑:最早运行的容器的 NMT 的输出
Native Memory Tracking:
Total: reserved=1180115KB, committed=700171KB
- Java Heap (reserved=614400KB, committed=260096KB)
(mmap: reserved=614400KB, committed=260096KB)
- Class (reserved=215344KB, committed=208080KB)
(classes #35567)
( instance classes #33401, array classes #2166)
(malloc=8496KB #125896)
(mmap: reserved=206848KB, committed=199584KB)
( Metadata: )
( reserved=174080KB, committed=173536KB)
( used=167363KB)
( free=6173KB)
( waste=0KB =0.00%)
( Class space:)
( reserved=32768KB, committed=26048KB)
( used=22342KB)
( free=3706KB)
( waste=0KB =0.00%)
- Thread (reserved=77858KB, committed=16034KB)
(thread #125)
(stack: reserved=77260KB, committed=15436KB)
(malloc=452KB #752)
(arena=146KB #249)
- Code (reserved=139426KB, committed=96022KB)
(malloc=7330KB #29103)
(mmap: reserved=132096KB, committed=88692KB)
- GC (reserved=70010KB, committed=56862KB)
(malloc=14362KB #61984)
(mmap: reserved=55648KB, committed=42500KB)
- Compiler (reserved=896KB, committed=896KB)
(malloc=764KB #2798)
(arena=133KB #5)
- Internal (reserved=4509KB, committed=4509KB)
(malloc=4477KB #3670)
(mmap: reserved=32KB, committed=32KB)
- Other (reserved=4359KB, committed=4359KB)
(malloc=4359KB #39)
- Symbol (reserved=37775KB, committed=37775KB)
(malloc=34219KB #439593)
(arena=3556KB #1)
- Native Memory Tracking (reserved=10765KB, committed=10765KB)
(malloc=34KB #437)
(tracking overhead=10731KB)
- Arena Chunk (reserved=190KB, committed=190KB)
(malloc=190KB)
- Logging (reserved=4KB, committed=4KB)
(malloc=4KB #194)
- Arguments (reserved=20KB, committed=20KB)
(malloc=20KB #545)
- Module (reserved=3692KB, committed=3692KB)
(malloc=3692KB #14082)
- Synchronizer (reserved=860KB, committed=860KB)
(malloc=860KB #7286)
- Safepoint (reserved=8KB, committed=8KB)
(mmap: reserved=8KB, committed=8KB)
//edit2:我们在 90 多个 Docker 服务上使用的 Docker 镜像
FROM --platform=$TARGETPLATFORM amazoncorretto:11
RUN yum install -y shadow-utils wget && yum clean all
RUN wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar -O /opentelemetry-javaagent.jar
ADD opentelemetry-java-ecs-extension-*-all.jar /opentelemetry-java-ecs-extension-all.jar
RUN adduser docker
RUN if [ -f $JAVA_HOME/conf/security/java.security ]; then sed -i 's/^networkaddress.cache.ttl/#networkaddress.cache.ttl/g' $JAVA_HOME/conf/security/java.security; fi && \
echo "networkaddress.cache.ttl=30" >> $JAVA_HOME/conf/security/java.security
USER docker
//edit3:忘记提及我们还设置了这个环境变量
{
"name": "MALLOC_ARENA_MAX",
"value": "4"
}
Java 不太擅长管理 RAM 资源作为限制。 Java 始于 PC 世界,那里有 RAM 和 SWAP 内存,因此每当您调整这些值时,都是为了使其尽可能保持最佳运行状态。
当docker出现时,java应用程序开始面临一个新问题,没有更多的SWAP可以使用,现在java能够看到的可用内存(主机)与docker分配的内存不相等一个极限。
例如,您的 Java 应用程序可能报告使用 1.1Gb RAM,但是当您为任务定义分配 1.5Gb RAM 时,这并不是 JAVA 本身能够看到的。如果您能够 ssh 进入容器,您将能够看到它报告的 RAM 是运行任务的主机容器实例。假设您有一个带有 2 个 CPU 和 2 GB RAM 的 t4g.small 运行您的任务。这意味着 Java 将遇到这些限制,并且某些组件的计算可能会超过该限制。从那里您需要考虑任何其他进程内存,如操作系统、网络、根进程、内部日志记录等。
您的 Cloudwatch 仪表板实际上显示了所有这些组件作为一个整体的真实使用情况,而您的 JAVA 仪表板仅报告其监控的选定组件。所以你不知道还有什么在消耗内存。
注意:另请记住,本机内存跟踪无法查看所有与 java 相关的内存使用情况。它确实有助于了解内存的主要使用方式,以便您可以改进设置,但只是一个近似的猜测,而不是真正的消耗。
建议:在容器化环境中使用 Java 应用程序时,最好留出更多空间来开始跟踪历史数据。就像给它一个月 8Gb 的内存一样,了解您的应用程序的行为并进行相应的优化。然后,让您的历史数据提示您限制应该在哪里。不要先设置一个小限制,然后尝试让你的 java 应用程序表现得更好。