前言

小公司请求量小,但喜欢滥用内存,开一堆线程,大把大把往jvm塞对象,最终问题是内存溢出。

大公司并发大,但喜欢强调HA,所以通常保留swap,最终问题是服务卡顿。

而喜欢用全局集合变量的某些同仁,把java代码当c写,对象塞进去但忘了销毁,最终问题是内存泄漏。

如何避免?
合理参数、优雅代码、禁用swap,三管齐下,trouble shooter(解决问题的人)。

从一个故事开始

老王的疑问

一个阳光明媚的下午,一条报警短信弹了出来。老王微微一笑,是cpu问题,idle瞬时值,大概是某批请求比较大引起的峰值问题。老王每天都会收到这样的短信,这样的一个小峰值,在数千台服务器中,不过是沧海一栗,继续喝茶就是了。

但,这次不一样。几分钟之后,几百个服务的超时报警铺天盖地到来。事后老王算了一下,大概千分之零点几的服务超时了,不过这已经很恐怖了。
事态升级,恐怕没时间喝茶了。

大面积报警,应该是全局问题,是网络卡顿?还是数据库抽风?老王挑了一台最近报警的服务器,轮流监控了各种状态,总结如下:

  • 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~,我们先上一点命令。

(#1) 物理内存

#根据使用量排序查看RES
top -> shift + m
#查看进程使用的物理内存
ps -p 75 -o rss,vsz
#显示内存的使用情况
free -h
#使用sar查看内存信息
sar -r
#显示内存每个区的详情
cat /proc/meminfo
#查看slab区使用情况
slabtop

通常,通过查看物理内存的占用,你发现不了多少问题,顶多发现那个进程占用内存高(比如vim等旁路应用)。meminfo和slabtop对系统的全局判断帮助很大,但掌握这两点坡度陡峭。

(#2) swap

#查看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罪与罚》这篇文章看下,千万不要更晕哦。

(#3) jvm

# 查看系统级别的故障和问题
dmesg
# 统计实例最多的类前十位
jmap -histo pid | sort -n -r -k 2 | head -10
# 统计容量前十的类
jmap -histo pid | sort -n -r -k 3 | head -10

以上命令是看堆内的,能够找到一些滥用集合的问题。堆外内存,依然推荐
《Java堆外内存排查小结》

(#4) 其他

# 释放内存
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。

接下来的知识有些烧脑,但有些名词,可能是你已经听过多次的了。

操作系统视角

我们来解释一下上图,第一部分是逻辑内存和物理内存的关系;第二部分是top命令展示的一个结果,详细的列出了每一个进程的内存使用情况;第三部分是free命令展示的结果,它的关系比较乱,所以给加上了箭头来作说明。

  • 学过计算机组成结构的都知道,程序编译后的地址是逻辑内存,需要经过翻译才能映射到物理内存。这个管翻译的硬件,就叫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 ...

oom-killer

以下问题已经不止一个小伙伴问了:我的java进程没了,什么都没留下,就像个屁一样蒸发不见了

why?是因为对象太多了么?

执行dmesg命令,大概率会看到你的进程崩溃信息躺尸在那里。

为了能看到发生的时间,我们习惯性加上参数T

dmesg -T

由于linux系统采用的是虚拟内存,进程的代码,库,堆和栈的使用都会消耗内存,但是申请出来的内存,只要没真正access过,是不算的,因为没有真正为之分配物理页面。

第一层防护墙就是swap;当swap也用的差不多了,会尝试释放cache;当这两者资源都耗尽,杀手就出现了。oom killer会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一点内存。2.4内核杀新进程;2.6杀用的最多的那个。所以,买内存吧。

这个oom和jvm的oom可不是一个概念。顺便,瞧一下我们的JVM堆在什么位置。

例子

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性能低

业务方的ES集群宿主机是32GB的内存,随着数据量和访问量增加,决定对其进行扩容=>内存改成了64GB。

内存升级后,发现ES的性能没什么变化,某些时候,反而更低了。

通过查看配置,发现有两个问题引起。
一、64GB的机器分配给jvm的有60G,预留给文件缓存的只有4GB,造成了文件缓存和硬盘的频繁交换,比较低效。
二、JVM大小超过了32GB,内存对象的指针无法启用压缩,造成了大量的内存浪费。由于ES的对象特别多,所以留给真正缓存对象内容的内存反而减少了。

解决方式:给jvm的内存30GB即可。

其他

基本上了解了内存模型,上手几次内存溢出排查,内存问题就算掌握了。但还有更多,这条知识系统可以深挖下去。

JMM

还是拿java来说。java中有一个经典的内存模型,一般面试到volitile关键字的时候,都会问到。其根本原因,就是由于线程引起的。

当两个线程同时访问一个变量的时候,就需要加所谓的锁了。由于锁有读写,所以java的同步方式非常多样。wait,notify、lock、cas、volitile、synchronized等,我们仅放上volitile的读可见性图作下示例。

线程对共享变量会拷贝一份到工作区。线程1修改了变量以后,其他线程读这个变量的时候,都从主存里刷新一份,此所谓读可见。

JMM问题是纯粹的内存问题,也是高级java必备的知识点。

CacheLine & False Sharing

是的,内存的工艺制造还是跟不上CPU的速度,于是聪明的硬件工程师们,就又给加了一个缓存(哦不,是多个)。而Cache Line为CPU Cache中的最小缓存单位。

这个缓存是每个核的,而且大小固定。如果存在这样的场景,有多个线程操作不同的成员变量,但是相同的缓存行,这个时候会发生什么?。没错,伪共享(False Sharing)问题就发生了!

伪共享也是高级java的必备技能(虽然几乎用不到),赶紧去探索吧。

HugePage

回头看我们最长的那副图,上面有一个TLB,这个东西速度虽然高,但容量也是有限的。当访问频繁的时候,它会成为瓶颈。
TLB是存放Virtual Address和Physical Address的映射的。如图,把映射阔上一些,甚至阔上几百上千倍,TLB就能容纳更多地址了。像这种将Page Size加大的技术就是Huge Page。

HugePage有一些副作用,比如竞争加剧(比如redis: https://redis.io/topics/latency )。但在大内存的现代,开启后会一定程度上增加性能(比如oracle: https://docs.oracle.com/cd/E11882_01/server.112/e10839/appi_vlm.htm )。

Numa

本来想将Numa放在cpu篇,结果发现numa改的其实是内存控制器。这个东西,将内存分段,分别”绑定”在不同的CPU上。也就是说,你的某核CPU,访问一部分内存速度贼快,但访问另外一些内存,就慢一些。

所以,Linux识别到NUMA架构后,默认的内存分配方案就是:优先尝试在请求线程当前所处的CPU的内存上分配空间。如果绑定的内存不足,先去释放绑定的内存。

以下命令可以看到当前是否是NUMA架构的硬件。

numactl --hardware

NUMA也是由于内存速度跟不上给加的折衷方案。Swap一些难搞的问题,大多是由于NUMA引起的。

总结

本文的其他,是给想更深入理解内存结构的同学准备的提纲。Linux内存牵扯的东西实在太多,各种缓冲区就是魔术。如果你遇到了难以理解的现象,费了九牛二虎之力才找到原因,不要感到奇怪。对发生的这一切,我深表同情,并深切的渴望通用量子计算机的到来。

那么问题来了,内存尚且如此,磁盘呢?

Linux之《荒岛余生》(三)内存篇相关推荐

  1. Linux之《荒岛余生》(二)CPU篇

    为什么80%的码农都做不了架构师?>>>    温馨提示,动图已压缩,流量党放心查看.CPU方面内容不多,我们顺便学点命令.本篇是<荒岛余生>系列第二篇,垂直观测CPU. ...

  2. DPDK内存篇(三): 标准大页、NUMA、DMA、IOMMU、IOVA、内存池

    作者简介:Anatoly Burakov,英特尔软件工程师,目前在维护DPDK中的VFIO和内存子系统. 目录 引言 标准大页 将内存固定到NUMA节点 硬件.物理地址和直接内存存取(DMA) IOM ...

  3. Linux内核分析(三)----初识linux内存管理子系统

    原文:Linux内核分析(三)----初识linux内存管理子系统 Linux内核分析(三) 昨天我们对内核模块进行了简单的分析,今天为了让我们今后的分析没有太多障碍,我们今天先简单的分析一下linu ...

  4. 电子书 鸟哥的Linux私房菜 (基础学习篇 第三版).pdf

    <鸟哥的Linux私房菜 (基础学习篇 第三版)>是颇具知名度的Linux入门书<鸟哥的Linux私房菜:基础学习篇>的全新版,全面而详细地介绍了Linux操作系统. 全书分为 ...

  5. Linux任督二脉之内存管理(三) PPT

    五节课的第三节课-进程的内存消耗和泄漏 *进程的VMA. *进程内存消耗的4个概念:vss.rss.pss和uss *page fault的几种可能性,major和minor *应用内存泄漏的界定方法 ...

  6. Android10.0 Binder通信原理(三)-ServiceManager篇

    摘要:本节主要来讲解Android10.0 Binder中守护进程ServiceManager是如何启动.注册.获取服务 阅读本文大约需要花费35分钟. 文章首发微信公众号:IngresGe 专注于A ...

  7. 详解Linux运维工程师打怪升级篇

    详解 Linux 运维工程师打怪升级篇 积累经验篇 做运维也快4年多了,就像游戏打怪升级,升级后知识体系和运维体系也相对变化挺大,学习了很多新的知识点. 运维工程师 是从一个呆逼进化为苦逼再成长为牛逼 ...

  8. linux c 修改用户组,Linux C Function()参照之用户组篇

    Linux C Function()参考之用户组篇 endgrent(关闭组文件) 相关函数 getgrent,setgrent 表头文件 #include #include 定义函数 void en ...

  9. DPDK内存篇(二): 深入学习 IOVA

    Anatoly Burakov:英特尔软件工程师,目前在维护DPDK中的VFIO和内存子系统. "文章转载自DPDK与SPDK开源社区公众号" 目录 引言 环境抽象层(EAL)参数 ...

  10. linux入门常识(三)

    五.linux命令篇 linux的命令是linux里面相当重要的部分,如果命令不能很好的掌握,那linux用起来真的比较麻烦阿,但是命令好像很不少阿,在命令行上按2次Tab键,你就可以看到有多少命令了 ...

最新文章

  1. GC之二--GC是如何回收时的判断依据、shallow(浅) size、retained(保留) size、Deep(深)size...
  2. 幽默感七个技巧_如何通过三招,让自己成为一个有幽默感的说话高手?
  3. Java_输出60的十六进制
  4. c语言规定在一个源程序中main函数的位置是什么?
  5. c++远征之模板篇——函数模板、类模板
  6. php请求api获取返回值,我用curl请求接口获取返回值,但是不成功,大神给看看怎么调取?...
  7. CTP 客户端 技术相关 简介 一
  8. 2020年4月数据库流行度排行:MySQL 成事实王者,国产openGauss引期待
  9. 如何在C#中生成与PHP一样的MD5 Hash Code
  10. 12306为什么要安装根证书
  11. 1.3 新概念 可数名词变复数
  12. 数学建模学习之聚类算法
  13. jos lab 2-3函数说明
  14. PCA(主成分分析)获取BoundingBox代码分析
  15. vue-video-play使用方法
  16. 如何给模型加入先验知识?
  17. 从「富爸爸现金流」游戏中总结的理财四条
  18. DTOJ#4170. 「PKUWC2018」猎人杀
  19. 阅读软件怎么添加书源_?软件智能化,傻瓜式运营,阅读10万+的影视解说到底是怎么做成的?...
  20. C语言学习—给学习C语言初学者的建议

热门文章

  1. 应届生多次面试失败后的内心独白
  2. python 离线翻译软件_简单翻译软件
  3. 工人级无人机,现在和未来可能都不会有垄断者出现
  4. 2009北海市东盟杯导游大赛结束,各奖项名花有主-渤锐软件提供了相关的软件支持
  5. vmware创建共享文件夹
  6. [LeetCode] 871. Minimum Number of Refueling Stops
  7. 50 道 Python 基础练习题(附答案详解)
  8. 深圳弘辽科技电商:拼多多“砍单免费拿”:一场关于人性的较量
  9. shardingsphere: SpringBoot整合shardingjdbc实现读写分离
  10. Python开发-- Lesson 1--Python介绍和入门(2016/07/23)