对于java来说,Docker毕竟是一个较新的环境,例如,其内存、CPU等资源限制是通过CGroup实现的,早期的JDK版本并不能识别这些限制,进而会导致一些基础问题:
1、如果未配置合适的JVM堆和元数据区、直接内存等参数,java就有可能试图使用超过容器限制的内存,最终被容器OOM kill,或者自身发生OOM。
2、错误判断了可获取的CPU资源,例如,Docker限制了CPU的核数,JVM就可能设置不合适的GC并行线程数等。
从应用打包、发布等角度出发,JDK自身就比较大,生成的镜像就更为臃肿,当我们的镜像非常多的时候,镜像的存储等开销就比较明显了;
如果考虑到微服务、Serverless等新的架构和场景,java自身的大小、内存占用、启动速度,都存在一定局限性,因为java早期的优化大多是针对长时间运行的大型服务器端应用。
Docker和虚拟机的区别?
虽然Docker之类的容器和虚拟机非常相似,例如,它也有自己的shell,能独立安装软件包,运行时与其他容器互不干扰。但是,如果深入分析会发现,Docker并不是一种完全的虚拟化技术,而更是一种轻量级的隔离技术。
Docker未每个容器提供了单独的命名空间,堆网络、PID、用户、IPC通信、文件系统挂载点等实现了隔离。对于CPU、内存、磁盘IO等计算资源,则是通过CGroup进行管理。Docker仅在类似linux内核之上实现了有限的隔离和虚拟化,并不是像传统虚拟化软件那样,独立运行一个新的操作系统。如果是虚拟化的操作系统,不管是java还是其他程序,只要调用的是同一个系统API,都可以透明地获取所需的信息,基本不需要额外的兼容性改变。容器虽然省略了虚拟操作系统的开销,实现了轻量级的目标,但也带来了额外复杂性,它限制对于应用不是透明的,需要用户理解Docker的新行为。
对于java平台来说,Docker没有隐藏的底层信息带来了很多意外的困难,主要体现在几个方面:
1、容器环境对于计算资源的管理方式是全新的,CGroup作为相对比较新的技术,历史版本的java显然斌不能自然地理解相应的资源限制;
2、namespace对于容器内的应用细节增加了一些微妙的差异,比如jcmd、jstack等工具会依赖于“/proc//”下面提供的部分信息,但是Docker的设计改变了这部分信息的原有结构,我们需要堆原有工具进行修改以适应这种变化。