编者的话在过去的几年中, Docker 一直是非常受欢迎的容器技术, 而原因也很简单将基于 JVM 的应用程序容器化部署, 可以为应用程序提供一致的开发部署环境以及零耦合的环境隔离但是不幸的是, 目前的 JVM 在 Linux 容器内运行事务并不那么简单因此, 为了优化一些问题, Java 9 和 10 做了很多非常必要的改进, 这里我们重点说三点
堆 (Heap) 大小
默认 情况下, 在 64 位的服务器中, JVM 通常将最大堆大小设定为物理内存的 1/4 而在容器化环境中, 这确实没有什么意义, 因为你通常拥有很多可以运行多个 JVM 的大内存的服务器如果你在不同的容器中运行 10 个 JVM, 并且每个 JVM 最终都使用了 1/4 的 RAM, 那么你将面临过度使用机器 RAM 的窘境, 并且有可能最终导致虚拟内存耗尽 - 结局就是用户将离你而去
这也是容器化的优势之一无差异, 即生产和测试的容器图像在生产中将表现相同在较小的物理主机上的镜像环境中, 一个容器可以很容易地正常工作, 但在生产环境较大的主机上可能会因为超出容器的任何内存限制而被内核杀死
对此有各种解决方法, 例如包括一个 JAVA_OPTIONS 环境变量, 可以从容器外部设置堆大小 (或 - XX:MaxRam) 但是, 这会让事情变得混乱, 因为你需要多次复制关于容器限制的信息 - 一次在容器中, 一次为 JVM 当然, 你也可以编写 JVM 启动脚本, 从 proc 文件系统中提取正确的内存限制但都不会让你优雅的解决问题
在 Linux 上隔离容器的主要机制是通过控制组 (cgroups), 这些机制允许(除其他外) 限制资源到一组进程 使用 Java 10,JVM 将读取容器过控制组 cgroup 中的内存限制和使用情况 , 并使用它来初始化最大内存, 从而消除对这些变通办法中的任何一种的需求
可用的 CPU
默认情况下, docker 容器可以无限制地访问系统上的所有 CPU 将利用率限制在一定比例的 CPU time(使用 CPU 份额)或系统的各个 CPU 范围 (使用 cpusets) 是 可能的也是常见的
不幸的是, 与堆大小一样, Java 8 中的 JVM 大多不知道用于限制容器内 CPU 利用率的各种机制这可能会在具有多个内核的大型物理主机上导致问题, 因为在容器内运行的所有 JVM 都会假定它们可以访问比实际更多的 CPU 这样做的结果是, JVM 的许多部分将根据可用的处理器进行自适应大小调整, 例如具有并行性和并发性的 JIT 编译器线程和 ForkJoin 池, 其大小将错误调整, 从而产生的线程数量大于他们所期望的数量并且这可能会导致过多的上下文切换以及生产中糟糕的性能许多第三方实用程序, 库和应用程序也使用 Runtime.availableProcessors()方法来调整自己的线程池或展现类似的行为
从 Java 8u131 和 Java 9 开始, JVM 可以理解和利用 cpusets 来确定可用处理器的大小, 而 Java 10 则支持 CPU 共享
从 host 连接
Attach API 允许从另一个 JVM 程式访问 JVM 它对于读取目标 JVM 的环境状态非常有用, 并且在 JVM 代理中动态加载可以执行额外的监控, 分析或诊断任务由于连接机制与进程名称空间的交互方式, 目前无法将主机上的 JVM 附加到在 Docker 容器内运行的 JVM
主流操作系统上的所有进程都有唯一的标识符 PIDLinux 还具有 PID 名称空间的概念, 其中不同名称空间中的两个进程可以共享相同的 PID 命名空间也可以嵌套, 这个功能用来隔离容器内的进程
连接机制的复杂性在于容器内部的 JVM 当前没有在容器外的 PID 概念 Java 10 通过容器内的 JVM 在根名称空间中找到它的 PID 并使用它来监视 JVM, 据此来 修复此问题
总之, 如果你正在 Docker 下运行 JVM, 那么你应该期待在本月底即将发布的 Java 10, 同时尽快升级到 Java 10
来源: http://www.tuicool.com/articles/FZRNf2b