一.背景
我们十几个服务测试环境验证通过之后,准备上预发环境演练。其中有一个服务总是在启动过程中卡住。
二.初步分析
这个服务有点特殊。它作为我们整个服务架构中的辅助角色,包含了定时任务、工作流、OSS存储等服务。
一开始我怀疑是堆内存不够,导致一直在GC。毕竟该服务包含了如此多的能力,像定时任务、工作流都是比较耗费资源的。因此在启动过程中一直在进行GC!
基于此,我进入容器中观察GC日志,果然发现一直在GC!而且Young GC竟长达14s。
于是我又开始思虑:预发环境中该服务的配置是2C2G,就算内存吃紧,也不至于GC这么久!于是我先检查了一下容器配置,确实还是2C2G,接着我又检查了一下启动脚本,结果我差点惊呼:启动脚本中的配置什么时候变成了-Xmx:5120m -xms:5120m
,我们默认的配置是-Xmx:1024m -xms:1024m
.看来有人马大哈把正式环境的配置拷贝到预发环境来忘记修改了!
三.深入分析
我以为启动卡住是因为堆内存不够,而实际情况是配置的堆内存远远大于了物理内存!
将配置修改正确启动之后,我开始推演这种情况下,JVM的工作过程。
3.1 分析内存
配置中-XX:NewRatio=1
,说明年轻代和老年代各占一半,也即是说年轻代可以分配到2.5g
但物理内存只有2g,既2.5g>2g
。看到这里我大概知道原因所在,为了验证自己的猜想,看了一下机器的内存使用情况,其中swap内存
居然打满。
3.2 虚拟内存
Linux系统通过虚拟内存来解决内存不足的问题。当物理内存不足时,可以通过swap内存(存在于磁盘上)和物理内存进行数据交换,来释放一部分物理内存;当需要用到这部分数据时,再从swap内存交换回来。以此来解决内存不足的情况。但这用的坏处是:数据在内存和硬盘之间swap的过程极度非常消耗CPU!因此像ES等服务一般都是将该功能禁止的。
3.3 JVM分配的内存是物理内存么?
虚拟内存技术给每一个进程一定的虚拟内存空间,当虚拟内存实际被使用时,才会分配真正的物理内存。通过虚拟内存技术+swap内存使得每个进程得到的虚拟地址大小一样,并且可以超过实际的物理内存大小。当进程所需的内存不够时,就可以通过交换数据的方式,释放一部分物理内存给进程使用。
对于Linux系统而言,JVM和其他进程一样,都是一个普通的工作进程。因此JVM在启动时申请的内存和其他进程一样时虚拟内存大小。原空间、年轻代、老年代的分配打内存也都是虚拟内存。当实际用到时才会映射到物理内存,并在内存不够时,使用swap内存技术。
3.4 为什么Young GC时间这么长?
在这个场景下,年轻代分配的虚拟内存为2.5g,而实际物理内存为2g
,因此用到了swap内存,从而导致Young GC过程非常慢!
3.5 解决方案
1.将JVM启动配置修改正确。
2.机器配置升级。
结合实际情况,我们对机器升级为2C4G
,同时将JVM配置修改为:-Xmx:3g -Xms:3g
。因为原有配置下,该服务启动较慢,性能不太好,比较影响预发环境演练。是故将机器做了适当的升级。
三.总结
1.教育了小伙伴一顿,不要做马大哈哈哈哈~
2.之前只是在《计算机操作系统》书上看过关于虚拟内存的理论知识,没想到正是因为这理论的支撑让我分析出了事件原因。看来多读多思考总有好处,说不定哪天就用上了!