Java程序运行在Docker等容器环境有哪些新问题
基本回答
一. 对于Java来说,Docker毕竟是一个较新的环境,其内存、CPU等资源限制是通过ControlGroup实现的。早期的JDK版本并不能识别这些限制,进而会导致一些基础问题。
1.如果未配置合适的JVM堆和元数据区、直接内存等参数,Java就有可能试图使用超过容器限制的内存,最终被容器OOM kill,或者自身发生OOM。
2.错误判断了可获取的CPU资源,例如,Docker限制了CPU的核数,JVM就可能设置不合适的GC并行线程数等。
二. 从应用打包、发布等角度出发JDK自身就比较大,生成的镜像就更为臃肿,当我们的镜像非常多的时候,镜像的存储等开销就比较明显了。
三. 如果考虑到微服务、Serverless等新的架构和场景,Java自身的大小、内存占用、启动速度,都存在一定局限性,因为Java早期的优化大多是针对长时间运行的大型服务器端应用。
如果真的没做过,可以从操作系统、容器原理、JVM内部机制、软件开发实践等角度展示系统性分心新问题、新场景的能力,毕竟变化才是世界永恒的主题,能够在新变化中找出共性与关键,是优秀工程师的必备能力。
扩展
Java在容器环境的局限性来源,Docker到底有什么特别?
虽然看起来Docker之类容器和虚拟机非常相似,例如,有自己的shell,能独立安装软件包,运行时与其他容器互不干扰。但是深入分析会发现,Docker并不是一种完全的虚拟化技术,而更是一种轻量级的隔离技术。
1.从技术角度讲,基于namespace,Docker为每个容器提供了单独的命名空间,对网络,PID,用户,IPC通信,文件系统挂载点等实现了隔离。对于CPU\内存,磁盘IO等资源,则是通过ControlGroup等进行管理。
2.Docker仅在类似Linux内核之上实现了有限的 隔离和虚拟化,并不是像传统虚拟化软件那样,独立运行与一个新的操作系统。如果是虚拟化的操作系统,不管是Java还是其他程序,只要调用的是同一个系统API,都可以透明的获取所需的信息,基本不需要额外的兼容性改变。
容器虽然省略了虚拟操作系统的开销,实现了轻量级的目标,但也带来了额外复杂性,它限制对于应用是不透明的,需要用户理解Docker的新行为。所以有人曾说过,“幸运的是Docker没有完全隐藏底层信息,但是不幸的也是Docker没有隐藏底层信息”。
对于Java平台来说,这些未隐藏的底层信息带来很多意外的困难,只要体现在以下几个方面:
1). 容器环境对计算资源的管理方式是全新的,ControlGroup作为相对比较新的技术,历史版本的Java显然并不能自然的理解相应的资源机制。
2). namespace对于容器内的应用细节增加了一些微妙的差异,比如jcmd、jstack等工具会依赖于"/proc"下面提供的部分信息,但是Docker的设计改变了这部分信息的原有结构,我需要对原有工具进行修改以适应这种变化。
3.从JVM运行机制的角度,为什么这些沟通障碍会导致OOM问题?
这个问题实际反映了JVM如何根据系统资源(内存,CPU等)情况,在启动时设置默认参数。
这就是所谓的Ergonomics机制,例如:
1)JVM会根据检测到的内存大小,设置最初启动时的堆大小为系统内存的1/64;并将堆最大值,设置为系统内存的1/4
2)而JVM检测到系统的CPU核数,则直接影响到了Parallel GC的并行线程数目和JIT complier线程数目,甚至是我们应用中ForkJoinPool等机制的并行等级。
这些默认参数,是根据通用场景选择的初始值。但是由于容器环境的差异,Java的判断很可能是基于错误信息而做出的,这就类似我以为我住的是整栋别墅,实际上却只有一个房间是给我住的。
更加严重的是,JVM的一些原有诊断或备用机制也受到影响,为保证服务的可用性,一种常见的选择是依赖 -XX:OnOutOfMemoryError 功能,通过调用处理脚本的形式来做一些补救措施,比如自动重启服务等,但是这种机制是基于fork实现的,当Java进程已经过度提交内存时,fork新的进程往往已经不可能正常运行了。
该如何解决这些问题呢?
1))首先,如果能升级到最新的JDK版本,就一切ok了
针对这种情况,JDK9中引入了一些实验性的参数,以方便Docker和Java沟通,通过针对内存限制,可以使用下面的参数设置:
-XX:+UnlockExperimentaVMOptions
-XX:+UserCGroupMemoryLimitForHeap
这两个参数是顺序敏感的,并且只支持Linux环境。而对于CPU核数限定,Java已经可以正确理解-cpuset-cpus等设置,,无需单独设置参数。
2))如果可以切换到JDK10或者更新的版本,问题就更加简单了。Java对容器Docker的支持已经比较完善,默认就会自适应各种资源限制和实现差异,前面提到的实验性参数已经被标记为废弃。于此同时,新增了参数用以明确指定CPU核心的数目。
-xx:ActiveProcessorCount=N
如果实践中发现有问题,也可以使用-XX:UseContainerSupport,关闭Java容器的支持特性,这可作为一种防御型机制,避免新特性破坏原有功能。
幸运的是,JKD9中的实验性改进已经被移植到Oracle JDK 8u131中了,可以直接下载镜像,并配置UseCGroupMemoryLimitForHeap
3))如果暂时只能用JDK老版本怎么办?
第一:明确设置堆、元数据区等内存区域大小,保证Java进程的总大小可控,
限制容器内存: $ docker run -it --rm --name your container -p 8080:8080 -m 800M repo/your -java-container:openjdk
这样就可以额外配置下面的环境变量,直接指定JVM堆大小 -e JAVA_OPTIONS='xMX300m'
第二:明确GC和JIT并行线程数目,以避免二者占用过多资源。
-XX:ParallelGCTreads
-XX:CICompilerCount
第三:Java在Docker环境中会意外Swap。
当内存消耗达到一定门限,操作系统会试图将不活跃的进程唤出
Java程序运行在Docker等容器环境有哪些新问题的更多相关文章
- 把AspDotNetCoreMvc程序运行在Docker上-part3:使用独立的存储容器
接上一篇博文<把AspDotNetCoreMvc程序运行在Docker上-part2:修改容器以及发布镜像>,这次我们看看如何使用docker存储数据. 背景 之前的示例都只有一个网站应用 ...
- linux(ubuntu) 搭建java程序运行环境
一:简介 ubuntu 系统的和linux差不多,我们需要在系统上搭建java程序运行环境,需要安装jdk,mysql这两个软件,tomcat是绿色版,直接通过taz -zxvf tomcat 就可以 ...
- 把AspDotNetCoreMvc程序运行在Docker上-part2:修改容器以及发布镜像
在上一个part<把AspDotNetCoreMvc程序运行在Docker上-part1>,已经将成功将aspdotnetcore程序运行在两个不同的容器中,目前两个容器的内容完全相同,只 ...
- 1、Java语言概述与开发环境——Java程序运行机制
Java语言是一种特殊的高级语言,它既有解释型语言的特性,也具有编译型语言的特征,因为Java要经过先编译后解释两个步骤. 一.高级语言的运行机制 计算机高级语言按程序的执行方式可以分为编译型和解释型 ...
- 把AspDotNetCoreMvc程序运行在Docker上-part4:实现负载均衡
在上一part<把AspDotNetCoreMvc程序运行在Docker上-part3:使用独立的存储容器>,我们利用MySql容器和Volume实现了真正意义上的数据存储.整个结构非常简 ...
- 把AspDotNetCoreMvc程序运行在Docker上-part1
接<基于ASP.Net Core学习Docker技术第一步:在CentOS7安装Docker平台>这个博文,在搭建完成Docker平台之后,可以开始让aspdotnetcore程序运行在d ...
- 把AspDotNetCoreMvc程序运行在Docker上-part5:使用docker-compose
在上一part<把AspDotNetCoreMvc程序运行在Docker上-part4:实现负载均衡>中,我们通过几个比较复杂的步骤在docker平台上实现了对网站程序的负载均衡,配置步骤 ...
- java程序运行时内存分配详解
java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下 一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...
- 02 基础 卸载JDK 安装JDK Java程序运行机制
基础 JDK:Java Development Kit(Java开发者工具 包含JRE和JVM) JRE:Java Runtime Environment(java运行时环境,包含JVM) JVM:J ...
随机推荐
- GSAP 官方文档(结贴)
好久没写GSAP的教程的(其实我也不懂哈哈),国内也没什么人用,不对动画要求特别高的话,其实也没必要用GSAP,现在工作上没用到这个东西,也懒得写了,所以有问题的话去找一下greensock的官方文档 ...
- std::ostringstream
ostringstream是C++的一个字符集操作模板类,定义在sstream.h头文件中.ostringstream类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓冲区,替代sp ...
- python基础之1-安装
author:headsen chen date :2018-03-22 17:16:14 notice :This article created by headsen chen and no ...
- 【BZOJ4231】回忆树 离线+fail树+KMP
[BZOJ4231]回忆树 Description 回忆树是树. 具体来说,是n个点n-1条边的无向连通图,点标号为1~n,每条边上有一个字符(出于简化目的,我们认为只有小写字母). 对一棵回忆树来说 ...
- python线程池ThreadPoolExecutor用法
线程池concurrent.futures.ThreadPoolExecutor模板 import time from concurrent.futures import ThreadPoolExec ...
- 02.Elasticsearch入门
Elasticsearch支持Http类型的Restful风格API请求,需要打开9200端口.Elasticsearch服务会监听两个端口9200和9300,9200提供Http Restf ...
- Maven聚合、Maven仓库jar包以及Spring+MyBatis+JUnit+Maven整合测试的搭建过程
一.Maven将父项目创建到父项目的内部 在父项目的pom.xml上 点右键,选择maven-->new-->maven module project 二.Maven聚合 在某个项目的p ...
- JSP中的内置对象和Struts中的Web资源的详解
JSP中的内置对象有如下几种: request :继承于HttpServletRequest, HttpServletRequest继承ServletRequest, 获得的Request对象的方法: ...
- CentOS设置密码复杂度及过期时间等
我们在使用linux系统设置密码的时候,经常遇到这样的问题,系统提示:您的密码太简单,或者您的密码是字典的一部分.那么系统是如何实现对用户的密码的复杂度的检查的呢? 系统对密码的控制是有两部分(我知道 ...
- linux下安装mysql-5.7.25
1.下载对应安装包 https://dev.mysql.com/downloads/mysql/ 2.卸载旧版本mysql 列出旧版本MySql的组件列表 rpm -qa | grep mysql ...