iis服务器助手广告
返回顶部
首页 > 资讯 > 精选 >Java和Docker限制的那些事儿
  • 521
分享到

Java和Docker限制的那些事儿

2023-06-04 16:06:47 521人浏览 安东尼
摘要

作者:kelvinjin2009 来源:程序师原文链接:http://www.techug.com/post/java-and-docker-memory-limits.html Java和Docker不是天然的朋友。 Docker可以设置

作者:kelvinjin2009 来源:程序师

原文链接:http://www.techug.com/post/java-and-docker-memory-limits.html

Java和Docker不是天然的朋友。 Docker可以设置内存和CPU限制,而Java不能自动检测到。使用Java的Xmx标识(繁琐/重复)或新的实验性JVM标识,我们可以解决这个问题。

虚拟化中的不匹配

Java和Docker的结合并不是完美匹配的,最初的时候离完美匹配有相当大的距离。对于初学者来说,JVM的全部设想就是,虚拟机可以让程序与底层硬件无关。

那么,把我们的Java应用打包到JVM中,然后整个再塞进Docker容器中,能给我们带来什么好处呢?大多数情况下,你只是在复制JVMs和linux容器,除了浪费更多的内存,没任何好处。感觉这样子挺傻的。

Java和Docker限制的那些事儿

不过,Docker可以把你的程序,设置,特定的jdk,Linux设置和应用服务器,还有其他工具打包在一起,当做一个东西。站在devops/Cloud的角度来看,这样一个完整的容器有着更高层次的封装。

问题一:内存

时至今日,绝大多数产品级应用仍然在使用Java 8(或者更旧的版本),而这可能会带来问题。Java 8(update 131之前的版本)跟Docker无法很好地一起工作。问题是在你的机器上,JVM的可用内存和CPU数量并不是Docker允许你使用的可用内存和CPU数量。

比如,如果你限制了你的Docker容器只能使用100MB内存,但是呢,旧版本的Java并不能识别这个限制。Java看不到这个限制。JVM会要求更多内存,而且远超这个限制。如果使用太多内存,Docker将采取行动并杀死容器内的进程!JAVA进程被干掉了,很明显,这并不是我们想要的。

为了解决这个问题,你需要给Java指定一个最大内存限制。在旧版本的Java(8u131之前),你需要在容器中通过设置-Xmx来限制堆大小。这感觉不太对,你可不想定义这些限制两次,也不太想在你的容器中来定义。

幸运的是我们现在有了更好的方式来解决这个问题。从Java 9之后(8u131+),JVM增加了如下标志:

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

这些标志强制JVM检查Linux的cgroup配置,Docker是通过cgroup来实现最大内存设置的。现在,如果你的应用到达了Docker设置的限制(比如500MB),JVM是可以看到这个限制的。JVM将会尝试GC操作。如果仍然超过内存限制,JVM就会做它该做的事情,抛出OutOfMemoryException。也就是说,JVM能够看到Docker的这些设置。

从Java 10之后(参考下面的测试),这些体验标志位是默认开启的,也可以使用-XX:+UseContainerSupport来使能(你可以通过设置-XX:-UseContainerSupport来禁止这些行为)。

问题二:CPU

第二个问题是类似的,但它与CPU有关。简而言之,JVM将查看硬件并检测CPU的数量。它会优化你的runtime以使用这些CPUs。但是同样的情况,这里还有另一个不匹配,Docker可能不允许你使用所有这些CPUs。可惜的是,这在Java 8或Java 9中并没有修复,但是在Java 10中得到了解决。

从Java 10开始,可用的CPUs的计算将采用以不同的方式(默认情况下)解决此问题(同样是通过UseContainerSupport)。

Java和Docker的内存处理测试

作为一个有趣的练习,让我们验证并测试Docker如何使用几个不同的JVM版本/标志甚至不同的JVM来处理内存不足。

首先,我们创建一个测试应用程序,它只是简单地“吃”内存并且不释放它。

javaimport java.util.ArrayList;import java.util.List;public class MemEat {    public static void main(String[] args) {        List l = new ArrayList<>();        while (true) {            byte b[] = new byte[1048576];            l.add(b);            Runtime rt = Runtime.getRuntime();            System.out.println( "free memory: " + rt.freeMemory() );        }    }}

我们可以启动Docker容器并运行这个应用程序来查看会发生什么。

测试一:Java 8u111

首先,我们将从具有旧版本Java 8的容器开始(update 111)。

shelldocker run -m 100m -it java:openjdk-8u111 /bin/bash

我们编译并运行MemEat.java文件:

shelljavac MemEat.javajava MemEat...free memory: 67194416free memory: 66145824free memory: 65097232Killed

正如所料,Docker已经杀死了我们的Java进程。不是我们想要的(!)。你也可以看到输出,Java认为它仍然有大量的内存需要分配。

我们可以通过使用-Xmx标志为Java提供最大内存来解决此问题:

shelljavac MemEat.javajava -Xmx100m MemEat...free memory: 1155664free memory: 1679936free memory: 2204208free memory: 1315752Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    at MemEat.main(MemEat.java:8)

在提供了我们自己的内存限制之后,进程正常停止,JVM理解它正在运行的限制。然而,问题在于你现在将这些内存限制设置了两次,Docker一次,JVM一次。

测试二:Java 8u144

如前所述,随着增加新标志来修复问题,JVM现在可以遵循Docker所提供的设置。我们可以使用版本新一点的JVM来测试它。

shelldocker run -m 100m -it adoptopenjdk/openjdk8 /bin/bash

(在撰写本文时,此OpenJDK Java镜像的版本是Java 8u144)

接下来,我们再次编译并运行MemEat.java文件,不带任何标志:

shelljavac MemEat.javajava MemEat...free memory: 67194416free memory: 66145824free memory: 65097232Killed

依然存在同样的问题。但是我们现在可以提供上面提到的实验性标志来试试看:

shell
javac MemEat.java
java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap MemEat
...
free memory: 1679936
free memory: 2204208
free memory: 1155616
free memory: 1155600
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at MemEat.main(MemEat.java:8)

这一次我们没有告诉JVM限制的是什么,我们只是告诉JVM去检查正确的限制设置!现在感觉好多了。

测试三:Java 10u23

有些人在评论和Reddit上提到Java 10通过使实验标志成为新的默认值来解决所有问题。这种行为可以通过禁用此标志来关闭:-XX:-UseContainerSupport。

当我测试它时,它最初不起作用。在撰写本文时,AdoptAJDK OpenJDK10镜像与jdk-10+23一起打包。这个JVM显然还是不理解UseContainerSupport标志,该进程仍然被Docker杀死。

shell
docker run -m 100m -it adoptopenjdk/openjdk10 /bin/bash

测试了代码(甚至手动提供需要的标志):

shelljavac MemEat.javajava MemEat...free memory: 96262112free memory: 94164960free memory: 92067808free memory: 89970656Killedjava -XX:+UseContainerSupport MemEatUnrecognized VM option 'UseContainerSupport'Error: Could not create the Java Virtual Machine.Error: A fatal exception has occurred. Program will exit.

测试四:Java 10u46(Nightly)

我决定尝试AdoptAJDK OpenJDK 10的最新nightly构建。它包含的版本是Java 10+46,而不是Java 10+23。

shelldocker run -m 100m -it adoptopenjdk/openjdk10:nightly /bin/bash

然而,在这个ngithly构建中有一个问题,导出的PATH指向旧的Java 10+23目录,而不是10+46,我们需要修复这个问题。

shellexport PATH=$PATH:/opt/java/openjdk/jdk-10+46/bin/javac MemEat.javajava MemEat...free memory: 3566824free memory: 2796008free memory: 1480320Exception in thread "main" java.lang.OutOfMemoryError: Java heap space  at MemEat.main(MemEat.java:8)

成功!不提供任何标志,Java 10依然可以正确检测到Dockers内存限制。

测试五:OpenJ9

我最近也在试用OpenJ9,这个免费的替代JVM已经从IBM J9开源,现在由Eclipse维护。

请在我的下一篇博文(Http://royvanrijn.com/blog/2018/05/openj9-jvm-shootout/)中阅读关于OpenJ9的更多信息。

它运行速度快,内存管理非常好,性能卓越,经常可以为我们的微服务节省多达30-50%的内存。这几乎可以将Spring Boot应用程序定义为’micro’了,其运行时间只有100-200mb,而不是300mb+。我打算尽快就此写一篇关于这方面的文章。

但令我惊讶的是,OpenJ9还没有类似于Java 8/9/10+中针对cgroup内存限制的标志(backported)的选项。如果我们将以前的测试用例应用到最新的AdoptAJDK OpenJDK 9 + OpenJ9 build:

shelldocker run -m 100m -it adoptopenjdk/openjdk9-openj9 /bin/bash

我们添加OpenJDK标志(OpenJ9会忽略的标志):

shelljava -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap MemEat...free memory: 83988984free memory: 82940400free memory: 81891816Killed

Oops,JVM再次被Docker杀死。

我真的希望类似的选项将很快添加到OpenJ9中,因为我希望在生产环境中运行这个选项,而不必指定最大内存两次。 Eclipse/IBM正在努力修复这个问题,已经提了issues,甚至已经针对issues提交了PR。

更新:(不推荐Hack)

一个稍微丑陋/hacky的方式来解决这个问题是使用下面的组合标志:

shelljava -Xmx`cat /sys/fs/cgroup/memory/memory.limit_in_bytes` MemEat...free memory: 3171536free memory: 2127048free memory: 2397632free memory: 1344952JVMDUMP039I Processing dump event "systhrow", detail "java/lang/OutOfMemoryError" at 2018/05/15 14:04:26 - please wait.JVMDUMP032I JVM requested System dump using '//core.20180515.140426.125.0001.dmp' in response to an eventJVMDUMP010I System dump written to //core.20180515.140426.125.0001.dmpJVMDUMP032I JVM requested Heap dump using '//heapdump.20180515.140426.125.0002.phd' in response to an eventJVMDUMP010I Heap dump written to //heapdump.20180515.140426.125.0002.phdJVMDUMP032I JVM requested Java dump using '//javacore.20180515.140426.125.0003.txt' in response to an eventJVMDUMP010I Java dump written to //javacore.20180515.140426.125.0003.txtJVMDUMP032I JVM requested Snap dump using '//Snap.20180515.140426.125.0004.trc' in response to an eventJVMDUMP010I Snap dump written to //Snap.20180515.140426.125.0004.trcJVMDUMP013I Processed dump event "systhrow", detail "java/lang/OutOfMemoryError".Exception in thread "main" java.lang.OutOfMemoryError: Java heap space  at MemEat.main(MemEat.java:8)

在这种情况下,堆大小受限于分配给Docker实例的内存,这适用于较旧的JVM和OpenJ9。这当然是错误的,因为容器本身和堆外的JVM的其他部分也使用内存。但它似乎工作,显然Docker在这种情况下是宽松的。也许某些bash大神会做出更好的版本,从其他进程的字节中减去一部分。

无论如何,不要这样做,它可能无法正常工作。

测试六:OpenJ9(Nightly)

有人建议使用OpenJ9的最新nightly版本。

shelldocker run -m 100m -it adoptopenjdk/openjdk9-openj9:nightly /bin/bash

最新的OpenJ9夜间版本,它有两个东西:

另一个有问题的PATH参数,需要先解决这个问题

JVM支持新标志UseContainerSupport(就像Java 10一样)

shellexport PATH=$PATH:/opt/java/openjdk/jdk-9.0.4+12/bin/javac MemEat.javajava -XX:+UseContainerSupport MemEat...free memory: 5864464free memory: 4815880free memory: 3443712free memory: 2391032JVMDUMP039I Processing dump event "systhrow", detail "java/lang/OutOfMemoryError" at 2018/05/15 21:32:07 - please wait.JVMDUMP032I JVM requested System dump using '//core.20180515.213207.62.0001.dmp' in response to an eventJVMDUMP010I System dump written to //core.20180515.213207.62.0001.dmpJVMDUMP032I JVM requested Heap dump using '//heapdump.20180515.213207.62.0002.phd' in response to an eventJVMDUMP010I Heap dump written to //heapdump.20180515.213207.62.0002.phdJVMDUMP032I JVM requested Java dump using '//javacore.20180515.213207.62.0003.txt' in response to an eventJVMDUMP010I Java dump written to //javacore.20180515.213207.62.0003.txtJVMDUMP032I JVM requested Snap dump using '//Snap.20180515.213207.62.0004.trc' in response to an eventJVMDUMP010I Snap dump written to //Snap.20180515.213207.62.0004.trcJVMDUMP013I Processed dump event "systhrow", detail "java/lang/OutOfMemoryError".Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

TADAAA,正在修复中!

奇怪的是,这个标志在OpenJ9中默认没有启用,就像它在Java 10中一样。再说一次:确保你测试了这是你想在一个Docker容器中运行Java。

结论

简言之:注意资源限制的不匹配。测试你的内存设置和JVM标志,不要假设任何东西。

如果您在Docker容器中运行Java,请确保你设置了Docker内存限制和在JVM中也做了限制,或者你的JVM能够理解这些限制。

如果您无法升级您的Java版本,请使用-Xmx设置您自己的限制。

对于Java 8和Java 9,请更新到最新版本并使用:

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap

对于Java 10,确保它支持’UseContainerSupport’(更新到最新版本)。

对于OpenJ9(我强烈建议使用,可以在生产环境中有效减少内存占用量),现在使用-Xmx设置限制,但很快会出现一个支持UseContainerSupport标志的版本。

--结束END--

本文标题: Java和Docker限制的那些事儿

本文链接: https://www.lsjlt.com/news/238622.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Java和Docker限制的那些事儿
    作者:kelvinjin2009 来源:程序师原文链接:http://www.techug.com/post/java-and-docker-memory-limits.html Java和Docker不是天然的朋友。 Docker可以设置...
    99+
    2023-06-04
  • 关于 sudo 的那些事儿
    觉得你已经了解了 sudo 的所有知识了吗?再想想。大家都知道 sudo,对吗?默认情况下,该工具已安装在大多数 Linux 系统上,并且可用于大多数 BSD 和商业 Unix 变体。不过,在与数百名 ...
    99+
    2023-06-05
  • C# 解析Excel中的那些事儿
    在C#中解析Excel文件是一个常见的任务,可以使用一些类库来实现。1. 使用Microsoft Office Interop Ex...
    99+
    2023-09-09
    C#
  • Java基础详解之面向对象的那些事儿
    目录一、面向对象的理解二、三大特性2.1 继承(extends):2.2 封装(private):2.3 多态:三、自动装箱与自动拆箱3.1 自动装箱3.2 自动拆箱四、小结一、面向...
    99+
    2024-04-02
  • 关于mybatis的一级缓存和二级缓存的那些事儿
    目录一、缓存是什么二、为什么需要缓存三、哪些数据会放到缓存四、mybatis一级缓存五、二级缓存六、注意事项一、缓存是什么 缓存其实就是存储在内存中的临时数据,这里的数据量会比较小,...
    99+
    2024-04-02
  • Java基础7:关于Java类和包的那些事
    更多内容请关注微信公众号【Java技术江湖】这是一位阿里 Java 工程师的技术小站,作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、EL...
    99+
    2023-06-02
  • Java那点儿事之Map集合不为人知的秘密有哪些
    目录前言一、Map二、HashMap的基本概念三、HashMap集合的基本方法与使用四、HashMap集合的遍历五、HashMap集合的综合案例六、总结前言 上期我们说到了Colle...
    99+
    2024-04-02
  • 详细聊聊关于Mysql联合查询的那些事儿
    目录联合查询之union 1. 查询中国各省的ID以及省份名称 2. 湖南省所有地级市ID、名字 3. 用union将他们合并 联合查询之union a...
    99+
    2024-04-02
  • 2020将至,谈谈中小企业创业的那些事儿
    2019正迎来尾声,在创业这场轰轰烈烈的旅途中,有的人名利双收,有的人饱受非议。一入创业深似海,在创业赛道上,很多公司完成了从0到1,却死在从1到100的路上。而小程序的出现,让移动互联网创业走向低门槛。正...
    99+
    2024-04-02
  • Vue3和Vite不得不说的那些事
    目录1.创建一个vite项目2.vite简介3.第一个疑问3.1挖掘vite运行原理为什么这里需要@modules3.2文件请求4.hmr热更新总结1.创建一个vite项目 npm ...
    99+
    2024-04-02
  • docker限制内存的坏处有哪些
    使用Docker限制内存的主要坏处有以下几点: 性能问题:如果容器被限制在较小的内存空间内运行,可能会导致容器的性能下降。当容器...
    99+
    2023-10-21
    docker
  • 谈谈C语言中位运算你要知道的那些事儿
    目录一、概念说明1.概念1.1位运算1.2位运算符2.举例及补充2.1位运算2.2位运算符二、问题实战1.问题描述(开放题)2.输入输出三、源码实现(+详细注释)1.注释版2.纯源码...
    99+
    2024-04-02
  • Redis事务操作限制和注意事项有哪些
    在使用Redis事务操作时,有一些限制和注意事项需要注意: Redis事务是一组命令的集合,通过MULTI和EXEC命令来开启和...
    99+
    2024-05-07
    Redis
  • 父子聊通讯:揭秘 VUE 父子组件通信的那些事儿
    父子组件通信是VUE中非常重要的一个概念,它允许父子组件之间进行数据传递和事件触发。在VUE中,父子组件通信可以通过以下几种方式实现: Props: Props是VUE中实现父子组件通信最常用的方式。它允许父组件将数据传递给子组件。 ...
    99+
    2024-02-07
    文章 VUE 父子组件通信 props $emit ref $children $parent
  • 关于pt-archiver和自增主键的那些事
    目录前言分析解析结论本文Percona Blog 的译文,原文移步文章末尾的 阅读原文。 前言 pt-archiver 是一款常见的 表清理或者归档工具。 MySQL 中删除大表之前...
    99+
    2024-04-02
  • Aurora数据库有哪些限制和注意事项
    Aurora数据库具有以下限制和注意事项: 存储容量限制:Aurora数据库的存储容量有最小值和最大值限制,具体取决于所选择的实...
    99+
    2024-04-02
  • C++ 函数重载的限制和注意事项有哪些?
    函数重载的限制包括:参数类型和顺序必须不同(相同参数个数时),不能使用默认参数区分重载。此外,模板函数和非模板函数不能重载,不同模板规范的模板函数可以重载。值得注意的是,过度使用函数重载...
    99+
    2024-04-13
    c++ 函数重载 限制 编译错误
  • Java排序的那些事之sort方法的使用详解
    目录引言升序数组集合降序声明一个类实现接口匿名内部类实现接口Lambda表达式实现接口自定义数据类型的排序总结:引言 在学习Java过程中,排序sort是我们常用的功能;在Java里...
    99+
    2024-04-02
  • Java基础学习之关键字和变量数据类型的那些事
    目录一. 关键字二. 变量2.1 变量的定义2.2 变量的分类1. 按照数据类型分类三. 字符编码补充:变量的声明和初始化总结一. 关键字 Java中的关键字是由特定的单词组成,单词...
    99+
    2024-04-02
  • Java面向对象之抽象类,接口的那些事
    目录一、抽象类1.抽象类概述1.1 为什么要有抽象类?(抽象类的作用)1.2 抽象类的定义2. 抽象类特点3.抽象类成员特点4.抽象类案例二、接口1.接口概述2.接口特点3.接口成员...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作