内存问题,脑瓜疼脑瓜疼。脑瓜疼的意思,就是脑壳运算空间过小,撑的疼。本篇是《荒岛余生》系列第三篇,让人脑瓜疼的内存篇。其他参见:java
Linux之《荒岛余生》(一)准备篇linux
小公司请求量小,但喜欢滥用内存,开一堆线程,大把大把往jvm塞对象,最终问题是内存溢出。redis
大公司并发大,但喜欢强调HA,因此一般保留swap,最终问题是服务卡顿。数据库
而喜欢用全局集合变量的某些同仁,把java代码当c写,对象塞进去但忘了销毁,最终问题是内存泄漏。vim
如何避免? 合理参数、优雅代码、禁用swap,三管齐下, trouble shooter。缓存
一个阳光明媚的下午,一条报警短信弹了出来。老王微微一笑,是cpu问题,idle瞬时值,大概是某批请求比较大引发的峰值问题。老王天天都会收到这样的短信,这样的一个小峰值,在数千台服务器中,不过是沧海一栗,继续喝茶就是了。性能优化
但,此次不同。几分钟以后,几百个服务的超时报警铺天盖地到来。过后老王算了一下,大概千分之零点几的服务超时了,不过这已经很恐怖了。 事态升级,恐怕没时间喝茶了。bash
大面积报警,应该是全局问题,是网络卡顿?仍是数据库抽风?老王挑了一台最近报警的服务器,轮流监控了各类状态,总结以下:服务器
cpu偶尔有瞬时峰值,但load很是正常
内存虽然free很少了,但cached还有很多
网络各类ping,基本正常
磁盘I/O通常,毕竟是服务计算节点
数据库链接池稳定,应该不是db抽风
swap用了很多,但好像每台机器都用了,没啥大不了
全局性的东西不太多,网关、LVS、注册中心、DB、MQ,好像都没问题。老王开始脑瓜疼了。
让老王休息一下,咱们把镜头转向小王。
小王不是老王的儿子,他是老王的徒弟。徒弟一思考,导师就发笑。此次小王用的是vim,想查找一个Exception,他打开了一个8GB的日志文件,而后乐呵呵的在那等着加载。而后,服务器就死了。
这里直接给出答案,缘由等读完本文天然会了解。
老王的问题最终定位到是因为某个运维工程师使用ansible批量执行了一句命令
find / | grep "x"
复制代码
他是想找一个叫作x的文件,看看在哪台服务器上。结果,这些老服务器因为文件太多,扫描后这些文件信息都缓存到了slab区。而服务器开了swap,操做系统发现物理内存占满后,并无当即释放cache,致使每次GC,都和硬盘打一次交道。而后,全部服务不间歇卡顿了...
最终,只能先关闭swap分区,而后强制内核释放cache,而后再开启swap。固然这个过程也不会顺利,由于开、关swap,一样会引发大量I/O交换,因此不能批量去执行。这几千台机器,是要忙活一阵喽。
小王的问题就简单多了。他使用vim打开大文件,全部文件的内容都会先加载到内存。结果,内存占满、接着swap也满了,而后oom-killer杀死了服务进程,给一头雾水的小王留下了个莫名其妙。
内存分两部分,物理内存和swap。物理内存问题主要是内存泄漏,而swap的问题主要是用了swap~,咱们先上一点命令。
#根据使用量排序查看RES
top -> shift + m
#查看进程使用的物理内存
ps -p 75 -o rss,vsz
#显示内存的使用状况
free -h
#使用sar查看内存信息
sar -r
#显示内存每一个区的详情
cat /proc/meminfo
#查看slab区使用状况
slabtop
复制代码
一般,经过查看物理内存的占用,你发现不了多少问题,顶多发现那个进程占用内存高(好比vim等旁路应用)。meminfo和slabtop对系统的全局判断帮助很大,但掌握这两点坡度陡峭。
#查看si,so是否异常
vmstat 1
#使用sar查看swap
sar -W
#禁用swap
swapoff
#查询swap优先级
sysctl -q vm.swappiness
#设置swap优先级
sysctl vm.swappiness=10
复制代码
建议关注非0 swap的全部问题,即便你用了ssd。swap用的多,一般伴随着I/O升高,服务卡顿。swap一点都很差玩,不信搜一下《swap罪与罚》这篇文章看下,千万不要更晕哦。
# 查看系统级别的故障和问题
dmesg
# 统计实例最多的类前十位
jmap -histo pid | sort -n -r -k 2 | head -10
# 统计容量前十的类
jmap -histo pid | sort -n -r -k 3 | head -10
复制代码
以上命令是看堆内的,可以找到一些滥用集合的问题。堆外内存,依然推荐 《Java堆外内存排查小结》
# 释放内存
echo 3 > /proc/sys/vm/drop_caches
#查看进程物理内存分布
pmap -x 75 | sort -n -k3
#dump内存内容
gdb --batch --pid 75 -ex "dump memory a.dump 0x7f2bceda1000 0x7f2bcef2b000"
复制代码
二王的问题表象都是CPU问题,CPU都间歇性的增高,那是由于Linux的内存管理机制引发的。你去监控Linux的内存使用率,大几率是没什么用的。由于通过一段时间,剩余的内存都会被各类缓存迅速占满。一个比较典型的例子是ElasticSearch,分一半内存给JVM,剩下的一半会迅速被Lucene索引占满。
若是你的App进程启动后,通过两层缓冲后还不能落地,迎接它的,将会是oom killer。
接下来的知识有些烧脑,但有些名词,多是你已经听过屡次的了。
学过计算机组成结构的都知道,程序编译后的地址是逻辑内存,须要通过翻译才能映射到物理内存。这个管翻译的硬件,就叫MMU
;TLB
就是存放这些映射的小缓存。内存特别大的时候,会涉及到**hugepage
,在某些时候,是进行性能优化的杀手锏,好比优化redis (THP,注意理解透彻前不要妄动)**
物理内存的可用空间是有限的,因此逻辑内存映射一部分地址到硬盘上,以便获取更大的物理内存地址,这就是swap
分区。swap是不少性能场景的万恶之源,建议禁用
像top
展现的字段,RES
才是真正的物理内存占用(不包括swap,ps命令里叫RSS
)。在java中,表明了堆内+堆外内存的总和。而VIRT、SHR等,几乎没有判断价值(某些场景除外)
系统的可用内存,包括:free
+ buffers
+ cached
,由于后二者能够自动释放。但不要迷信,有很大一部分,你是释放不了的
slab区,是内核的缓存文件句柄等信息等的特殊区域,slabtop命令能够看到具体使用
更详细的,从/proc/meminfo
文件中能够看到具体的逻辑内存块的大小。有多达40项的内存信息,这些信息均可以经过/proc一些文件的遍历获取,本文只挑重点说明。
[xjj@localhost ~]$ cat /proc/meminfo
MemTotal: 3881692 kB
MemFree: 249248 kB
MemAvailable: 1510048 kB
Buffers: 92384 kB
Cached: 1340716 kB
40+ more ...
复制代码
如下问题已经不止一个小伙伴问了:个人java进程没了,什么都没留下,就像个屁同样蒸发不见了
why?是由于对象太多了么?
执行dmesg
命令,大几率会看到你的进程崩溃信息躺尸在那里。
为了能看到发生的时间,咱们习惯性加上参数T
dmesg -T
复制代码
因为linux系统采用的是虚拟内存,进程的代码
,库
,堆
和栈
的使用都会消耗内存,可是申请出来的内存,只要没真正access过,是不算的,由于没有真正为之分配物理页面。
第一层防御墙就是swap;当swap也用的差很少了,会尝试释放cache;当这二者资源都耗尽,杀手就出现了。oom killer会在系统内存耗尽的状况下跳出来,选择性的干掉一些进程以求释放一点内存。2.4内核杀新进程;2.6杀用的最多的那个。因此,买内存吧。
这个oom和jvm的oom可不是一个概念。顺便,瞧一下咱们的JVM堆在什么位置。
应用程序发布后,jvm持续增加。使用jstat命令,能够看到old区一直在增加。
jstat -gcutil 28266 1000
复制代码
在jvm参数中,加入-XX:+HeapDumpOnOutOfMemoryError
,在jvm oom的时候,生成hprof快照。而后,使用Jprofile、VisualVM、Mat等打开dump文件进行分析。
你要是个急性子,可使用jmap立马dump一份
jmap -heap:format=b pid
复制代码
最终发现,有一个全局的Cache对象,不是guava的,也不是commons包的,是一个简单的ConcurrentHashMap,结果越积累越多,最终致使溢出。
溢出的状况也有多种区别,这里总结以下:
关键字 | 缘由 |
---|---|
Java.lang.OutOfMemoryError: Java heap space | 堆内存不够了,或者存在内存溢出 |
java.lang.OutOfMemoryError: PermGen space | Perm区不够了,可能使用了大量动态加载的类,好比cglib |
java.lang.OutOfMemoryError: Direct buffer memory | 堆外内存、操做系统没内存了,比较严重的状况 |
java.lang.StackOverflowError | 调用或者递归层次太深,修正便可 |
java.lang.OutOfMemoryError: unable to create new native thread | 没法建立线程,操做系统内存没有了,必定要预留一部分给操做系统,不要都给jvm |
java.lang.OutOfMemoryError: Out of swap space | 一样没有内存资源了,swap都用光了 |
jvm程序内存问题,除了真正的内存泄漏,大多数都是因为太贪心引发的。一个4GB的内存,有同窗就把jvm设置成了3840M,只给操做系统256M,不死才怪。
另一个问题就是swap了,当你的应用真正的高并发了,swap绝对能让你体验到它魔鬼性的一面:进程却是死不了了,但GC时间长的没法忍受。
业务方的ES集群宿主机是32GB的内存,随着数据量和访问量增长,决定对其进行扩容=>内存改为了64GB。
内存升级后,发现ES的性能没什么变化,某些时候,反而更低了。
经过查看配置,发现有两个问题引发。 1、64GB的机器分配给jvm的有60G,预留给文件缓存的只有4GB,形成了文件缓存和硬盘的频繁交换,比较低效。 2、JVM大小超过了32GB,内存对象的指针没法启用压缩,形成了大量的内存浪费。因为ES的对象特别多,因此留给真正缓存对象内容的内存反而减小了。
解决方式:给jvm的内存30GB便可。
基本上了解了内存模型,上手几回内存溢出排查,内存问题就算掌握了。但还有更多,这条知识系统能够深挖下去。
仍是拿java来讲。java中有一个经典的内存模型,通常面试到volitile关键字的时候,都会问到。其根本缘由,就是因为线程引发的。
当两个线程同时访问一个变量的时候,就须要加所谓的锁了。因为锁有读写,因此java的同步方式很是多样。wait,notify、lock、cas、volitile、synchronized等,咱们仅放上volitile的读可见性图做下示例。
JMM问题是纯粹的内存问题,也是高级java必备的知识点。
是的,内存的工艺制造仍是跟不上CPU的速度,因而聪明的硬件工程师们,就又给加了一个缓存(哦不,是多个)。而Cache Line为CPU Cache中的最小缓存单位。
伪共享也是高级java的必备技能(虽然几乎用不到),赶忙去探索吧。
回头看咱们最长的那副图,上面有一个TLB,这个东西速度虽然高,但容量也是有限的。当访问频繁的时候,它会成为瓶颈。 TLB是存放Virtual Address和Physical Address的映射的。如图,把映射阔上一些,甚至阔上几百上千倍,TLB就能容纳更多地址了。像这种将Page Size加大的技术就是Huge Page。
原本想将Numa放在cpu篇,结果发现numa改的实际上是内存控制器。这个东西,将内存分段,分别"绑定"在不一样的CPU上。也就是说,你的某核CPU,访问一部份内存速度贼快,但访问另一些内存,就慢一些。
因此,Linux识别到NUMA架构后,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的内存上分配空间。若是绑定的内存不足,先去释放绑定的内存。
如下命令能够看到当前是不是NUMA架构的硬件。
numactl --hardware
复制代码
NUMA也是因为内存速度跟不上给加的折衷方案。Swap一些难搞的问题,大可能是因为NUMA引发的。
本文的其余,是给想更深刻理解内存结构的同窗准备的提纲。Linux内存牵扯的东西实在太多,各类缓冲区就是魔术。若是你遇到了难以理解的现象,费了九牛二虎之力才找到缘由,不要感到奇怪。对发生的这一切,我深表同情,并深切的渴望通用量子计算机的到来。
那么问题来了,内存尚且如此,磁盘呢?