基本回答

一.  对于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等容器环境有哪些新问题的更多相关文章

  1. 把AspDotNetCoreMvc程序运行在Docker上-part3:使用独立的存储容器

    接上一篇博文<把AspDotNetCoreMvc程序运行在Docker上-part2:修改容器以及发布镜像>,这次我们看看如何使用docker存储数据. 背景 之前的示例都只有一个网站应用 ...

  2. linux(ubuntu) 搭建java程序运行环境

    一:简介 ubuntu 系统的和linux差不多,我们需要在系统上搭建java程序运行环境,需要安装jdk,mysql这两个软件,tomcat是绿色版,直接通过taz -zxvf tomcat 就可以 ...

  3. 把AspDotNetCoreMvc程序运行在Docker上-part2:修改容器以及发布镜像

    在上一个part<把AspDotNetCoreMvc程序运行在Docker上-part1>,已经将成功将aspdotnetcore程序运行在两个不同的容器中,目前两个容器的内容完全相同,只 ...

  4. 1、Java语言概述与开发环境——Java程序运行机制

    Java语言是一种特殊的高级语言,它既有解释型语言的特性,也具有编译型语言的特征,因为Java要经过先编译后解释两个步骤. 一.高级语言的运行机制 计算机高级语言按程序的执行方式可以分为编译型和解释型 ...

  5. 把AspDotNetCoreMvc程序运行在Docker上-part4:实现负载均衡

    在上一part<把AspDotNetCoreMvc程序运行在Docker上-part3:使用独立的存储容器>,我们利用MySql容器和Volume实现了真正意义上的数据存储.整个结构非常简 ...

  6. 把AspDotNetCoreMvc程序运行在Docker上-part1

    接<基于ASP.Net Core学习Docker技术第一步:在CentOS7安装Docker平台>这个博文,在搭建完成Docker平台之后,可以开始让aspdotnetcore程序运行在d ...

  7. 把AspDotNetCoreMvc程序运行在Docker上-part5:使用docker-compose

    在上一part<把AspDotNetCoreMvc程序运行在Docker上-part4:实现负载均衡>中,我们通过几个比较复杂的步骤在docker平台上实现了对网站程序的负载均衡,配置步骤 ...

  8. java程序运行时内存分配详解

    java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下   一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...

  9. 02 基础 卸载JDK 安装JDK Java程序运行机制

    基础 JDK:Java Development Kit(Java开发者工具 包含JRE和JVM) JRE:Java Runtime Environment(java运行时环境,包含JVM) JVM:J ...

随机推荐

  1. 第三篇:C++ 中的几种初始化

    前言 阅读C++教材时,想必你听过复制初始化,直接初始化,值初始化这三个概念吧.笔者本人常将其混淆,遂在此记录下它们的具体含义以便日后查阅. 复制初始化( copy-initialization ) ...

  2. 如何优化JAVA代码及提高执行效率

    可供程序利用的资源(内存.CPU时间.网络带宽等)是有限的,优化的目的就是让程序用尽可能少的资源完成预定的任务.优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率.本文讨论的主要是如何提高代 ...

  3. cxGrid 锁定列

    cxGrid锁定列 第1步: 双击cxGrid -> 点击页签“Bands”->点击“Add”加入2个tcxGridBrand, 将1个锁定在左边,最后一个锁定在右边. 如下图 第2步: ...

  4. Android Fragment Base

    public class FragmentTabsActivity extends FragmentActivity implements OnClickListener { //定义Fragment ...

  5. mysql数据的导入与导出

    参考:http://blog.sina.com.cn/s/blog_81b2b2a1010188q0.html  http://blog.csdn.net/xin_yu_xin/article/det ...

  6. 单台centos7.3 虚拟机实现主从复制和哨兵集群

    环境: centos7.3一台 部署图: 从服务器配置: slaveof 哨兵配置: port sentinel monitor m1 127.0.0.1 6379 2 sentinel monito ...

  7. Maven的安装配置及初次创建项目与java单元测试工具JUnit

    Maven  安装     1.把maven安装包解压到某个位置     2.配置M2_HOME环境变量指向这个位置 3.在path环境变量中添加;%M2_HOME%\bin 配置镜像 国内的阿里云镜 ...

  8. scrapy - grab english name

    wxpath定位-采集验证-入库-使用. from scrapy.spider import Spider from scrapy.crawler import CrawlerProcess clas ...

  9. Python进阶知识

    装饰器 迭代器 生成器 mixins 元编程 描述符 量化领域常用 列表推导式 字典推导式 高阶函数 lambda函数 三目表达式

  10. SpringCloud 进阶之Eureka(服务注册和发现)

    1. Eureka 服务注册与发现 Eureka 是一个基于REST的服务,用于服务的的注册与发现; Eureka采用C-S的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册 ...