精华内容
下载资源
问答
  • ORACLE查询内存溢出

    2019-01-03 16:18:00
    首先我们来看一个带排序的查询,点击工具栏的显示包含实际的执行计划。...从执行计划里可以看出,SELECT运算符包含了内存授予(Memory Grant)信息(一般情况下不会出现,这里是因为我们的语句包含排序...

    首先我们来看一个带排序的查询,点击工具栏的显示包含实际的执行计划。

    1 SELECT * FROM AdventureWorks2008R2.Person.Person WHERE FirstName LIKE 'w%' ORDER BY 1

    从执行计划里可以看出,SELECT运算符包含了内存授予(Memory Grant)信息(一般情况下不会出现,这里是因为我们的语句包含排序操作)。内存授予是KB为单位,是当执行计划中的一些运算符(像Sort/Hash等运算符)的执行,需要使用内存来完成——因此也被称为查询内存(Query Memory) 。

    在查询正式执行前,查询内存必须被SQL Server授予才可以。对于提供的查询,查询优化器根据查询对象的对应统计信息来决定需要多少查询内存。现在的问题就是,当统计信息过期了,SQL Server就会低估要处理的行数。在这个情况下,SQL Server对于提供的查询还是会请求更少的查询内存。但当查询真正开始后,SQL Server就不能改变授予的内存大小,也不能请求更多的内存。查询必须在授予的查询内存里完成操作。在这个情况下,SQL Server需要把Sort/Hash运算符涌进TempDb,这就意味我们原先在内存里快速操作变成物理磁盘上慢速操作。SQL Server Profiler可以通过Sort WarningsHash Warning这2个事件来跟踪查询内存溢出(Query Memory Spills)。

    很遗憾在SQL SERVER 2008(R2)没有提供这样的扩展事件来跟踪内存溢出事件。在SQL Server 2012里才有来解决这个问题。在这个文章里我会向你展示一个非常简单的例子,由于统计信息过期,你是如何产生内存溢出(Query Memory Spills)。我们来创建一个新的数据库,在里面创建一个表:

    复制代码
     1 SET STATISTICS IO ON
     2 SET STATISTICS TIME ON
     3 GO
     4 
     5 -- Create a new database
     6 CREATE DATABASE InsufficientMemoryGrants
     7 GO
     8 
     9 USE InsufficientMemoryGrants
    10 GO
    11 
    12 -- Create a test table
    13 CREATE TABLE TestTable
    14 (
    15    Col1 INT IDENTITY PRIMARY KEY,
    16    Col2 INT,
    17    Col3 CHAR(4000)
    18 )
    19 GO
    20 
    21 -- Create a Non-Clustered Index on column Col2
    22 CREATE NONCLUSTERED INDEX idxTable1_Column2 ON TestTable(Col2)
    23 GO
    复制代码

    TestTable表包含第1列的主键,第2列的非聚集索引,第3列的CHAR(4000)列。接下来我们要用第3列来做ORDER BY,因此在执行计划里,查询优化器必须生成明确的排序运算符。下一步我会往表里插入1500条记录,表里数据的所有值在第2列会平均分布——在表里每个值只出现一次。

    复制代码
     1 -- Insert 1500 records
     2 DECLARE @i INT = 1
     3 WHILE (@i <= 1500)
     4 BEGIN
     5     INSERT INTO TestTable VALUES
     6     (
     7          @i ,
     8         REPLICATE('x',4000)
     9     )
    10     
    11     SET @i += 1
    12 END
    13 GO
    复制代码

    有了这样的数据准备,我们可以执行一个简单的查询,会在执行计划里好似用独立的排序运算符:

    1 DECLARE @x INT
    2  
    3 SELECT @x = Col2 FROM TestTable
    4 WHERE Col2 = 2
    5 ORDER BY Col3
    6 GO

    当我们在SQL Server Profiler里尝试跟踪Sort WarningsHash Warning这2个事件时,会发现跟踪不到。

    你也可以使用DMV sys.dm_io_virtual_file_stats,看下num_of_writes列和num_of_bytes_written列,来看下刚才查询在TempDb是否有活动。当然,这个只有你一个人在使用当前数据库时有效。

    复制代码
     1 -- Check the activity in TempDb before we execute the sort operation.
     2 SELECT num_of_writes, num_of_bytes_written FROM 
     3 sys.dm_io_virtual_file_stats(DB_ID('tempdb'), 1)
     4 GO
     5 
     6 -- Select a record through the previous created Non-Clustered Index from the table.
     7 -- SQL Server retrieves the record through a Non-Clustered Index Seek operator.
     8 -- SQL Server estimates for the sort operator 1 record, which also reflects
     9 -- the actual number of rows.
    10 -- SQL Server requests a memory grant of 1024kb - the sorting is done inside
    11 -- the memory.
    12 DECLARE @x INT
    13 
    14 SELECT @x = Col2 FROM TestTable
    15 WHERE Col2 = 2
    16 ORDER BY Col3
    17 GO
    18 
    19 -- Check the activity in TempDb after the execution of the sort operation.
    20 -- There was no activity in TempDb during the previous SELECT statement.
    21 SELECT num_of_writes, num_of_bytes_written FROM 
    22 sys.dm_io_virtual_file_stats(DB_ID('tempdb'), 1)
    23 GO
    复制代码

    可以发现,查询执行前后没有任何改变。这个查询在我的系统里花费了1毫秒。

    现在我们有了1500条记录的表,这就是说我们需要修改20% + 500的数据行才可以触发SQL Server来更新统计信息。我们来计算下,就可以知道我们需要需要修改800条行数据(500 + 300)。因此让我们来插入第2列值为2的799条数据。这样我们就改变了数据的分布情况,当SQL Server还是不会更新统计信息,因为还有一条数据没有更新,直到这条数据更新了才会触发SQL Server内部的统计信息自动更新!

    我们再次执行刚才的查询:

    复制代码
     1 -- Check the activity in TempDb before we execute the sort operation.
     2 SELECT num_of_writes, num_of_bytes_written FROM 
     3 sys.dm_io_virtual_file_stats(DB_ID('tempdb'), 1)
     4 GO
     5 
     6 -- Select a record through the previous created Non-Clustered Index from the table.
     7 -- SQL Server retrieves the record through a Non-Clustered Index Seek operator.
     8 -- SQL Server estimates for the sort operator 1 record, which also reflects
     9 -- the actual number of rows.
    10 -- SQL Server requests a memory grant of 1024kb - the sorting is done inside
    11 -- the memory.
    12 DECLARE @x INT
    13 
    14 SELECT @x = Col2 FROM TestTable
    15 WHERE Col2 = 2
    16 ORDER BY Col3
    17 GO
    18 
    19 -- Check the activity in TempDb after the execution of the sort operation.
    20 -- There was no activity in TempDb during the previous SELECT statement.
    21 SELECT num_of_writes, num_of_bytes_written FROM 
    22 sys.dm_io_virtual_file_stats(DB_ID('tempdb'), 1)
    23 GO
    复制代码

    SQL Server就会把排序运算符涌进TempDb,因为SQL Server只申请了1K的查询内存授予(Query Memory Grant),它的估计行数是1——内存授予和刚才的一样。

    DMV sys.dm_io_virtual_file_stats 显示在TempDb里有活动,这是SQL Server把排序运算符涌进TempDb的证据。

    SQL Server Profiler也显示了Sort Warning的事件。

    我们检查下执行计划里的估计行数(Estimated Number of Rows),和实际行数(Actual Number of Rows)完全不一样。

    这里的执行时间花费了184毫秒,和刚才的1毫秒完全不一样。

    现在我们往表里再插入1条记录,再次执行查询,一切正常,因为SQL Server会触发统计信息更新并正确估计查询内存授予(Query Memory Grant):

    复制代码
     1 -- Insert 1 records into table TestTable
     2 SELECT TOP 1 IDENTITY(INT, 1, 1) AS n INTO #Nums
     3 FROM master.dbo.syscolumns sc1
     4  
     5 INSERT INTO TestTable (Col2, Col3)
     6 SELECT 2, REPLICATE('x', 2000) FROM #nums
     7 DROP TABLE #nums
     8 GO
     9  
    10 -- Check the activity in TempDb before we execute the sort operation.
    11 SELECT num_of_writes, num_of_bytes_written FROM
    12 sys.dm_io_virtual_file_stats(DB_ID('tempdb'), 1)
    13 GO
    14  
    15 -- SQL Server has now accurate statistics and estimates 801 rows for the sort operator.
    16 -- SQL Server requests a memory grant of 6.656kb, which is now enough.
    17 -- SQL Server now spills the sort operation not to TempDb.
    18 -- Logical reads: 577
    19 DECLARE @x INT
    20  
    21 SELECT @x = Col2 FROM TestTable
    22 WHERE Col2 = 2
    23 ORDER BY Col3
    24 GO
    25  
    26 -- Check the activity in TempDb after the execution of the sort operation.
    27 -- There is now no activity in TempDb during the previous SELECT statement.
    28 SELECT num_of_writes, num_of_bytes_written FROM
    29 sys.dm_io_virtual_file_stats(DB_ID('tempdb'), 1)
    30 GO
    复制代码

    嗯,这是个非常简单的例子,向你展示在SQL Server内部如何产生Sort Warning,其实一点也不神秘!

    参考文章:

    https://www.sqlpassion.at/archive/2011/10/19/query-memory-spills/

    注:此文章为WoodyTu学习MS SQL技术,收集整理相关文档撰写,欢迎转载,请在文章页面明显位置给出此文链接!
    若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢!

    转载于:https://www.cnblogs.com/UUUz/p/10214866.html

    展开全文
  • linux 进程占用内存查询

    万次阅读 2014-04-15 17:58:51
    作者: 黄永兵/译 出处:51CTO.com 阅读提示:本文是为那些经常疑惑的人准备的,“为什么一个简单的KDE文本编辑器要占用25M内存?”导致大多数人认为许多Linux应用程序,特别是KDE或GNOME程序都象ps报告一样臃肿......

    作者: 黄永兵/译 出处:51CTO.com 阅读提示:本文是为那些经常疑惑的人准备的,“为什么一个简单的KDE文本编辑器要占用25M内存?”导致大多数人认为许多Linux应用程序,特别是KDE或GNOME程序都象ps报告一样臃肿...【51CTO.com独家译文】本文是为那些经常疑惑的人准备的,“为什么一个简单的KDE文本编辑器要占用25M内存?”导致大多数人认为许多Linux应用程序,特别是KDE或GNOME程序都象ps报告一样臃肿,虽然这可能是也可能不是真的,依赖于具体的程序,它通常不是真的,一些程序比它们看起来消耗更多的内存。ps工具能为一个进程输出许多块有关的信息,象进程ID,当前运行状态,资源利用情况等。其中可能输出VSZ(代表虚拟设置大小)和RSS(驻留设置大小),它们经常被世界各地的计算机爱好者用来查看进程占用了多少内存。例如:下面是在我电脑上用ps aux命令为KEit的输出:USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMANDdbunker 3468 0.0 2.7 25400 14452 ? S 20:19 0:00 kdeinit:kedit 按照ps的输出,KEdit占用了大约25M的虚拟大小内存空间,大约14M驻留大小空间(上面报告中的两个数字都用k为单位),看起来大部分人都喜欢随意选择其中一个数字来表示某个进程的真实内存占用情况。我现在暂时先不解释VSZ和RSS之间的不同之处。不用说,前面那种认识是错误的!想要知道为什么,必须先学习Linux是如何在程序中控制共享库的。在Linux上的大部分主要程序使用共享库有助于确定功能,例如:一个KDE文件编辑程序将使用几个KDE共享库(为了允许与其他的KDE组件进行交互),几个X库(为了允许它显示、拷贝和粘贴图像)和几个常用系统库(为了允许它执行基本的操作)。大部分这些库,特别是象libc这样常用的库,是被许多Linux程序使用的,正是由于有这些共享,Linux可以使用一个巨大的诀窍:它将只载入单个共享库的拷贝到内存中,使用这一个拷贝就可以供每个引用它的程序使用。许多工具不再关心这个非常通用的技巧,这可能是个好现象也可能是个坏现象;它们只是简单地报告某个进程使用了多少内存,而不管是否是与其他进程共享了部分内存,两个程序使用一个很大的共享库,并且它的大小倾向于它们内存使用的总和,共享库被计入了双倍的大小,如果你不清楚这一点将使你产生误解。不幸的是,关于进程内存使用的准确表示法不是那么容易获得,不仅需要你理解系统是如何真实地工作的,而且还需要解决你想处理的一些困难问题,一个共享库应该为那个使用它的进程内存使用进行计数吗?如果一个共享库被许多进程使用,在这些进程间它的内存使用是平均分布的吗?或者刚好可以忽略?没有一个确定的及快速的规则,依赖于你面对的位置你可能会有不同的答案。这下容易看出来为什么ps不会尽力尝试报告一个正确的内存使用总量,而是给出一个模糊的数字。看一个进程的内存映像足以说明,让我们来看一看那个庞大的Kedit进程的位置,要查看Kedit的内存象什么样子,我们将使用pmap程序(使用-d标志)Address Kbytes Mode Offset Device Mapping08048000 40 r-x– 0000000000000000 0fe:00000 kdeinit08052000 4 rw— 0000000000009000 0fe:00000 kdeinit08053000 1164 rw— 0000000008053000 000:00000 [ anon ]40000000 84 r-x– 0000000000000000 0fe:00000 ld-2.3.5.so40015000 8 rw— 0000000000014000 0fe:00000 ld-2.3.5.so40017000 4 rw— 0000000040017000 000:00000 [ anon ]40018000 4 r-x– 0000000000000000 0fe:00000 kedit.so40019000 4 rw— 0000000000000000 0fe:00000 kedit.so40027000 252 r-x– 0000000000000000 0fe:00000 libkparts.so.2.1.040066000 20 rw— 000000000003e000 0fe:00000 libkparts.so.2.1.04006b000 3108 r-x– 0000000000000000 0fe:00000 libkio.so.4.2.040374000 116 rw— 0000000000309000 0fe:00000 libkio.so.4.2.040391000 8 rw— 0000000040391000 000:00000 [ anon ]40393000 2644 r-x– 0000000000000000 0fe:00000 libkdeui.so.4.2.040628000 164 rw— 0000000000295000 0fe:00000 libkdeui.so.4.2.040651000 4 rw— 0000000040651000 000:00000 [ anon ]40652000 100 r-x– 0000000000000000 0fe:00000 libkdesu.so.4.2.04066b000 4 rw— 0000000000019000 0fe:00000 libkdesu.so.4.2.04066c000 68 r-x– 0000000000000000 0fe:00000 libkwalletclient.so.1.0.04067d000 4 rw— 0000000000011000 0fe:00000 libkwalletclient.so.1.0.04067e000 4 rw— 000000004067e000 000:00000 [ anon ]4067f000 2148 r-x– 0000000000000000 0fe:00000 libkdecore.so.4.2.040898000 64 rw— 0000000000219000 0fe:00000 libkdecore.so.4.2.0408a8000 8 rw— 00000000408a8000 000:00000 [ anon ]…. (trimmed) …mapped: 25404K writeable/private: 2432K shared: 0K我剪掉了许多输出内容,剩下的与展示出来的类似,即使没有完整的输出,我们也可以看到一些非常有趣的内容,一个重要的内容就是注意到每一个共享库都列出了两次,一次为它的代码段一次为它的数据段,代码段具有“r-x-”样式,而数据段具有“rw--”样式,我们关心的只有字节数、样式和映像栏,剩下的对于讨论都是不重要的。如果你仔细检查输出内容,你会发现最大字节数的行通常是包含共享库(以lib开头的行就是共享库)的代码段行,如果你找出了在进程之间所有的共享部分,它们以“writeable/private” 结束,显示在输出的底端,这可以理解为进程的消耗增量,因此,运行Kedit(假设所有共享库都已经被载入了)实例大约要占用2M,这和ps报告的14或25M完全不是一回事。这意味着什么?这个故事的寓意是Linux进程内存使用是一个复杂的事情,你不能仅通过运行ps来了解,当你处理一个创建了大量子进程的程序时特别真实,如Apache,ps可能报告每个Apache进程使用10M内存,实际上每个Apache进程只消耗了1M内存,在调整Apache的MaxClients参数(它决定了你的服务器能同时处理的请求数量,)设置时这个信息变得非常重要。同时,它也适应于桌面软件,如果你运行了KDE,但是几乎全部使用Gnome应用程序,那么你将为多余的(但是不同的)共享库付出巨大的代价,因此请尽量保持要么全部运行KDE应用程序要么全部运行Gnome应用程序,这样Linux就可以使用更多的内存来做其他事情(如文件缓存,它可以极大地提高文件的访问速度)。原文出处:http://www.linuxquestions.org/linux/articles/Technical/Understanding_memory_usage_on_Linux



    想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以通过top命令查看进程占用了多少内存。这里我们可以看到VIRT、RES和SHR三个重要的指标,他们分别代表什么意思呢?这是本文需要跟大家一起探讨的问题。当然如果更加深入一点,你可能会问进程所占用的那些物理内存都用在了哪些地方?这时候top命令可能不能给到你你所想要的答案了,不过我们可以分析proc文件系统提供的smaps文件,这个文件详尽地列出了当前进程所占用物理内存的使用情况。

    这篇blog总共分为三个部分。第一部分简要阐述虚拟内存和驻留内存这两个重要的概念;第二部分解释top命令中VIRT、RES以及SHR三个参数的实际参考意义;最后一部分向大家介绍一下smaps文件的格式,通过分析smaps文件我们可以详细了解进程物理内存的使用情况,比如mmap文件占用了多少空间、动态内存开辟消耗了多少空间、函数调用栈消耗了多少空间等等。

    关于内存的两个概念

    要理解top命令关于内存使用情况的输出,我们必须首先搞清楚虚拟内存(Virtual Memory)和驻留内存(Resident Memory)两个概念。

    【虚拟内存】

    首先需要强调的是虚拟内存不同于物理内存,虽然两者都包含内存字眼但是它们属于两个不同层面的概念。进程占用虚拟内存空间大并非意味着程序的物理内存也一定占用很大。虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。我们程序中的指针其实都是这个虚拟内存空间中的地址。比如我们在写完一段C++程序之后都需要采用g++进行编译,这时候编译器采用的地址其实就是虚拟内存空间的地址。因为这时候程序还没有运行,何谈物理内存空间地址?凡是程序运行过程中可能需要用到的指令或者数据都必须在虚拟内存空间中。既然说虚拟内存是一个逻辑意义上(假象的)的内存空间,为了能够让程序在物理机器上运行,那么必须有一套机制可以让这些假象的虚拟内存空间映射到物理内存空间(实实在在的RAM内存条上的空间)。这其实就是操作系统中页映射表(page table)所做的事情了。内核会为系统中每一个进程维护一份相互独立的页映射表。。页映射表的基本原理是将程序运行过程中需要访问的一段虚拟内存空间通过页映射表映射到一段物理内存空间上,这样CPU访问对应虚拟内存地址的时候就可以通过这种查找页映射表的机制访问物理内存上的某个对应的地址。“页(page)”是虚拟内存空间向物理内存空间映射的基本单元。

          下图1演示了虚拟内存空间和物理内存空间的相互关系,它们通过Page Table关联起来。其中虚拟内存空间中着色的部分分别被映射到物理内存空间对应相同着色的部分。而虚拟内存空间中灰色的部分表示在物理内存空间中没有与之对应的部分,也就是说灰色部分没有被映射到物理内存空间中。这么做也是本着“按需映射”的指导思想,因为虚拟内存空间很大,可能其中很多部分在一次程序运行过程中根本不需要访问,所以也就没有必要将虚拟内存空间中的这些部分映射到物理内存空间上。
    到这里为止已经基本阐述了什么是虚拟内存了。总结一下就是,虚拟内存是一个假象的内存空间,在程序运行过程中虚拟内存空间中需要被访问的部分会被映射到物理内存空间中。虚拟内存空间大只能表示程序运行过程中可访问的空间比较大,不代表物理内存空间占用也大。
    t1

    驻留内存】

    驻留内存,顾名思义是指那些被映射到进程虚拟内存空间的物理内存。上图1中,在系统物理内存空间中被着色的部分都是驻留内存。比如,A1、A2、A3和A4是进程A的驻留内存;B1、B2和B3是进程B的驻留内存。进程的驻留内存就是进程实实在在占用的物理内存。一般我们所讲的进程占用了多少内存,其实就是说的占用了多少驻留内存而不是多少虚拟内存。因为虚拟内存大并不意味着占用的物理内存大。

    关于虚拟内存和驻留内存这两个概念我们说到这里。下面一部分我们来看看top命令中VIRT、RES和SHR分别代表什么意思。

    top命令中VIRT、RES和SHR的含义
          搞清楚了虚拟内存的概念之后解释VIRT的含义就很简单了。VIRT表示的是进程虚拟内存空间大小。对应到图1中的进程A来说就是A1、A2、A3、A4以及灰色部分所有空间的总和。也就是说VIRT包含了在已经映射到物理内存空间的部分和尚未映射到物理内存空间的部分总和。
    RES的含义是指进程虚拟内存空间中已经映射到物理内存空间的那部分的大小。对应到图1中的进程A来说就是A1、A2、A3以及A4几个部分空间的总和。所以说,看进程在运行过程中占用了多少内存应该看RES的值而不是VIRT的值。
    最后来看看SHR所表示的含义。SHR是share(共享)的缩写,它表示的是进程占用的共享内存大小。在上图1中我们看到进程A虚拟内存空间中的A4和进程B虚拟内存空间中的B3都映射到了物理内存空间的A4/B3部分。咋一看很奇怪。为什么会出现这样的情况呢?其实我们写的程序会依赖于很多外部的动态库(.so),比如libc.so、libld.so等等。这些动态库在内存中仅仅会保存/映射一份,如果某个进程运行时需要这个动态库,那么动态加载器会将这块内存映射到对应进程的虚拟内存空间中。多个进展之间通过共享内存的方式相互通信也会出现这样的情况。这么一来,就会出现不同进程的虚拟内存空间会映射到相同的物理内存空间。这部分物理内存空间其实是被多个进程所共享的,所以我们将他们称为共享内存,用SHR来表示。某个进程占用的内存除了和别的进程共享的内存之外就是自己的独占内存了。所以要计算进程独占内存的大小只要用RES的值减去SHR值即可。

    进程的smaps文件

    查看命令是:cat /proc/进程的pid/smaps

    通过top命令我们已经能看出进程的虚拟空间大小(VIRT)、占用的物理内存(RES)以及和其他进程共享的内存(SHR)。但是仅此而已,如果我想知道如下问题:

    1. 进程的虚拟内存空间的分布情况,比如heap占用了多少空间、文件映射(mmap)占用了多少空间、stack占用了多少空间?
    2.  进程是否有被交换到swap空间的内存,如果有,被交换出去的大小?
    3. mmap方式打开的数据文件有多少页在内存中是脏页(dirty page)没有被写回到磁盘的?
    4. mmap方式打开的数据文件当前有多少页面已经在内存中,有多少页面还在磁盘中没有加载到page cahe中?
    5. 等等

    以上这些问题都无法通过top命令给出答案,但是有时候这些问题正是我们在对程序进行性能瓶颈分析和优化时所需要回答的问题。所幸的是,世界上解决问题的方法总比问题本身要多得多。linux通过proc文件系统为每个进程都提供了一个smaps文件,通过分析该文件我们就可以一一回答以上提出的问题。

    在smaps文件中,每一条记录(如下图2所示)表示进程虚拟内存空间中一块连续的区域。其中第一行从左到右依次表示地址范围、权限标识、映射文件偏移、设备号、inode、文件路径。详细解释可以参见understanding-linux-proc-id-maps

    接下来8个字段的含义分别如下:

    1. Size:表示该映射区域在虚拟内存空间中的大小。
    2. Rss:表示该映射区域当前在物理内存中占用了多少空间。
    3. Shared_Clean:和其他进程共享的未被改写的page的大小。
    4. Shared_Dirty: 和其他进程共享的被改写的page的大小。
    5. Private_Clean:未被改写的私有页面的大小。
    6. Swap:表示非mmap内存(也叫anonymous memory,比如malloc动态分配出来的内存)由于物理内存不足被swap到交换空间的大小。
    7. Pss:该虚拟内存区域平摊计算后使用的物理内存大小(有些内存会和其他进程共享,例如mmap进来的)。比如该区域所映射的物理内存部分同时也被另一个进程映射了,且该部分物理内存的大小为1000KB,那么该进程分摊其中一半的内存,即Pss=500KB。t_2

    图2. smaps文件示例

    有了smap如此详细关于虚拟内存空间到物理内存空间的映射信息,相信大家已经能够通过分析该文件回答上面提出的4个问题。

    最后希望所有读者能够通过阅读本文对进程的虚拟内存和物理内存有一个更加清晰认识,并能更加准确理解top命令关于内存的输出,最后可以通过smaps文件更进一步分析进程使用内存的情况





     http://yalung929.blog.163.com/blog/static/203898225201212981731971/


    引 : top命令作为Linux下最常用的性能分析工具之一,可以监控、收集进程的CPUIO、内存使用情况。比如我们可以通过top命令获得一个进程使用了多少虚拟内存(VIRT)、物理内存(RES)、共享内存(SHR)。

    最近遇到一个咨询问题,某产品做性能分析需要获取进程占用物理内存的实际大小(不包括和其他进程共享的部分),看似很简单的问题,但经过研究分析后,发现背后有很多故事……

    VIRT RES SHR的准确含义

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     

    三个内存指标,VRITRESSHR准确含义是什么?谁能告诉我们?MAN页?Linux专家?SUSE工程师?Linus?谁能说出最正确答案?没人!因为惟有源代码才是最正确的答案。

    那我们就去看下源码吧,这就是开源软件的最大的好处。

    首先这三个数据的源头,肯定是内核,进程的相关数据结构肯定是由内核维护。那么top作为一个用户空间的程序,要想获取内核空间的数据,就需要通过系统接口(API)获取。而proc文件系统是Linux内核空间和用户空间交换数据的一个途径,而且是非常重要的一种途径,这点和windows更倾向于基于函数调用的形式不同。

    当你调用系统函数read读取一个普通文件时,内核执行对应文件系统的代码从磁盘传送文件内容给你。

    当你调用系统函数read读取一个 proc文件时,内核执行对应的proc文件系统的代码从内核的数据结构中传送相关内容给你。proc文件和磁盘没有关系。只是系统接口而已。

    而一个进程的相关信息,Linux全部通过/proc/<pid>/内的文件告诉了我们。

    如下,你可以使用普通的文件读写工具,比如cat获取进程的各种信息。这比函数调用的方式灵活多了、丰富多了。

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     

     

    回到我们的问题,top命令显示的进程信息,肯定也是通过proc获取的,因为除此之外没有其他途径,没有系统函数可以做这个事情,top也不可能越过用户层直取内核获取数据。

    带着以上信息,很快就可以从top的源码中找到关键代码:

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     
     

    啊哈,statm文件:

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     

    根据sscanf的顺序,第一个值是VIRT,第二个值是RES,第三个值是SHR

    等等,好像数值对不上,top显示的SHR344k,而statm给出的是86

    再来看一行关键代码:

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     
     

    statm显示的是页数,top显示的是KBX86下,一页是4KB86 344。这就对了!

     

    于是乎,我们找到了最关键的入口,接下来按图索骥,看看内核是怎么产生statm文件内容就可以了。~~

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     

     

    proc_pid_statm函数负责产生statm文件内容,当你使用cat命令打印statm文件时,内核中的这个函数会执行。

    proc_pid_statm获取进程的mm_struct数据结构,而这个数据结构就是进程的内存描述符,通过它可以获取进程内存使用、映射的全部信息。

         进一步考察task_statm函数,可以看到:

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     

    第一个值(VIRT)就是mm->total_vm,即进程虚存的总大小,这个比较清晰,只要进程申请了内存,无论是malloc还是堆栈还是全局,都会计入这个值;

    第二个值(RES)是mm->file_rss+mm->anon_rss

    第三个值(SHR)是mm->file_rss

     RES要和SHR结合者看,内核把物理内存分为了两部分,一部分是映射至文件的,一部分是没有映射至文件的即匿名内存,完全和共不共享没有关系!

    file_rss为什么叫做shared呢?应该是一种指示性表述,表示这部分内存可能是共享的。但并不代表真正共享了。那么到底哪些计入file_rss?通过查阅相关代码,发现(可能有遗漏):

    程序的代码段。

    动态库的代码段。

    通过mmap做的文件映射。

    通过mmap做的匿名映射,但指明了MAP_SHARED属性。

    通过shmget申请的共享内存。

     即进程通过以上方式占用的物理内存,计入file_rss,也就是topSHR字段。我们看到一般这些内存都是以共享方式存在。但如果某个动态库只一个进程在使用,它的代码段就没有被共享着。

    反过来再来看anon_rss统计的内容,是否就一定是独占的?也不是,比如新fork之后的子进程,由于copy on write机制,在页面被修改之前,和父进程共享。这部分值并不体现在top命令的SHR字段内。

     综上所述top命令显示的SHR字段,并不是准确描述了进程与其他进程共享使用的内存数量,是存在误差的。 

    那么如何获取进程准确的共享内存数量?

    获取进程准确的共享内存数量

    我们注意到在描述进程信息的proc/<pid>内,有一个smaps文件,里面展示了所有内存段的信息,其中有Shared_Clean Shared_Dirty Private_Clean Private_Dirty:几个字段

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     
     

     

    找到相关代码,可以看到,一个页面如果映射数>=2计入Shared_* ; 如果=1计入Private_*。(脏页计入*_Dirty,否则计入*_Clean

     

     

    剖析top命令显示的VIRT RES SHR值 - yalung - Y A L U N G
     

         统计smaps文件内所有段的Shared_*值的总和就是进程准确的共享内存数量!

         统计smaps文件内所有段的Private_*值的总和就是进程准确的独占内存数量!

    总结

    通过以上分析,我们可以得到如下结论:

    top命令通过解析/proc/<pid>/statm统计VIRTRESSHR字段值。

    VIRT是申请的虚拟内存总量。

    RES是进程使用的物理内存总和。

    SHRRES映射至文件的物理内存总和。包括:

    程序的代码段。

    动态库的代码段。

    通过mmap做的文件映射。

    通过mmap做的匿名映射,但指明了MAP_SHARED属性。

    通过shmget申请的共享内存。

    /proc/<pid>/smapsShared_*统计的是RES映射数量>=2的物理内存。

    /proc/<pid>/smapsPrivate_*统计的是RES映射数量=1的物理内存。





    在Linux下查看内存我们一般用free命令:
    [root@scs-2 tmp]# free
                 total       used       free     shared    buffers     cached
    Mem:       3266180    3250004      16176          0     110652    2668236
    -/+ buffers/cache:     471116    2795064
    Swap:      2048276      80160    1968116

    下面是对这些数值的解释:
    total:总计物理内存的大小。
    used:已使用多大。
    free:可用有多少。
    Shared:多个进程共享的内存总额。
    Buffers/cached:磁盘缓存的大小。
    第三行(-/+ buffers/cached):
    used:已使用多大。
    free:可用有多少。
    第四行就不多解释了。
    区别:第二行(mem)的used/free与第三行(-/+ buffers/cache) used/free的区别。 这两个的区别在于使用的角度来看,第一行是从OS的角度来看,因为对于OS,buffers/cached 都是属于被使用,所以他的可用内存是16176KB,已用内存是3250004KB,其中包括,内核(OS)使用+Application(X, oracle,etc)使用的+buffers+cached.
    第三行所指的是从应用程序角度来看,对于应用程序来说,buffers/cached 是等于可用的,因为buffer/cached是为了提高文件读取的性能,当应用程序需在用到内存的时候,buffer/cached会很快地被回收。
    所以从应用程序的角度来说,可用内存=系统free memory+buffers+cached。
    如上例:
    2795064=16176+110652+2668236

    接下来解释什么时候内存会被交换,以及按什么方交换。 当可用内存少于额定值的时候,就会开会进行交换。
    如何看额定值:
    cat /proc/meminfo

    [root@scs-2 tmp]# cat /proc/meminfo
    MemTotal:      3266180 kB
    MemFree:         17456 kB
    Buffers:        111328 kB
    Cached:        2664024 kB
    SwapCached:          0 kB
    Active:         467236 kB
    Inactive:      2644928 kB
    HighTotal:           0 kB
    HighFree:            0 kB
    LowTotal:      3266180 kB
    LowFree:         17456 kB
    SwapTotal:     2048276 kB
    SwapFree:      1968116 kB
    Dirty:               8 kB
    Writeback:           0 kB
    Mapped:         345360 kB
    Slab:           112344 kB
    Committed_AS:   535292 kB
    PageTables:       2340 kB
    VmallocTotal: 536870911 kB
    VmallocUsed:    272696 kB
    VmallocChunk: 536598175 kB
    HugePages_Total:     0
    HugePages_Free:      0
    Hugepagesize:     2048 kB

    用free -m查看的结果:
    [root@scs-2 tmp]# free -m 
                 total       used       free     shared    buffers     cached
    Mem:          3189       3173         16          0        107       2605
    -/+ buffers/cache:        460       2729
    Swap:         2000         78       1921


    查看/proc/kcore文件的大小(内存镜像):
    [root@scs-2 tmp]# ll -h /proc/kcore 
    -r-------- 1 root root 4.1G Jun 12 12:04 /proc/kcore

    备注:

    占用内存的测量

    测量一个进程占用了多少内存,linux为我们提供了一个很方便的方法,/proc目录为我们提供了所有的信息,实际上top等工具也通过这里来获取相应的信息。

    /proc/meminfo 机器的内存使用信息

    /proc/pid/maps pid为进程号,显示当前进程所占用的虚拟地址。

    /proc/pid/statm 进程所占用的内存

    [root@localhost ~]# cat /proc/self/statm

    654 57 44 0 0 334 0

    输出解释

    CPU 以及CPU0。。。的每行的每个参数意思(以第一行为例)为:

    参数 解释 /proc//status

    Size (pages) 任务虚拟地址空间的大小 VmSize/4

    Resident(pages) 应用程序正在使用的物理内存的大小 VmRSS/4

    Shared(pages) 共享页数 0

    Trs(pages) 程序所拥有的可执行虚拟内存的大小 VmExe/4

    Lrs(pages) 被映像到任务的虚拟内存空间的库的大小 VmLib/4

    Drs(pages) 程序数据段和用户态的栈的大小 (VmData+ VmStk )4

    dt(pages) 04

    查看机器可用内存

    /proc/28248/>free

    total used free shared buffers cached

    Mem: 1023788 926400 97388 0 134668 503688

    -/+ buffers/cache: 288044 735744

    Swap: 1959920 89608 1870312

    我们通过free命令查看机器空闲内存时,会发现free的值很小。这主要是因为,在linux中有这么一种思想,内存不用白不用,因此它尽可能的cache和buffer一些数据,以方便下次使用。但实际上这些内存也是可以立刻拿来使用的。

    所以 空闲内存=free+buffers+cached=total-used

     

     

    用/proc文件系统查看进程的内存使用情况

    /proc目录Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统

    /proc/vmstat 虚拟内存统计信息

    /proc/vmcore 内核panic时的内存映像

    /proc/diskstats 取得磁盘信息

    /proc/schedstat kernel调度器的统计信息

    /proc/zoneinfo 显示内存空间的统计信息,对分析虚拟内存行为很有用

    以下是/proc目录中进程N的信息

    /proc/N pid为N的进程信息

    /proc/N/cmdline 进程启动命令

    /proc/N/cwd 链接到进程当前工作目录

    /proc/N/environ 进程环境变量列表

    /proc/N/exe 链接到进程的执行命令文件

    /proc/N/fd 包含进程相关的所有的文件描述符

    /proc/N/maps 与进程相关的内存映射信息

    /proc/N/mem 指代进程持有的内存,不可读

    /proc/N/root 链接到进程的根目录

    /proc/N/stat 进程的状态

    /proc/N/statm 进程使用的内存的状态

    /proc/N/status 进程状态信息,比stat/statm更具可读性

    /proc/self 链接到当前正在运行的进程


    ps命令的输出关于内存的情况不是很详细,尤其是进程所使用的内存中有很大一部分是共享库函数使用的,因此通过ps命令的输出看不到进程自己使用了多少内存。为了查看更详细的信息,可以借助于/proc文件系统。这个文件系统并存在于磁盘上,但是可以象操作其它普通文件一样操作它。它是Linux提供给用户查看进程相关信息的接口。在/proc下有2个文件和进程内存有关:/proc/<pid>/status和/proc/<pid>/smaps。

    通过/proc/<pid>/status可以查看进程的内存使用情况,包括虚拟内存大小(VmSize),物理内存大小(VmRSS),数据段大小(VmData),栈的大小(VmStk),代码段的大小(VmExe),共享库的代码段大小(VmLib)等等。

    $ cat /proc/10069/status
    Name:   a.out
    State:  S (sleeping)
    Tgid:   10069
    Pid:    10069
    PPid:   6793
    TracerPid:      0
    Uid:    1001    1001    1001    1001
    Gid:    1001    1001    1001    1001
    FDSize: 256
    Groups: 1000 1001 
    VmPeak:     1692 kB
    VmSize:     1616 kB
    VmLck:         0 kB
    VmHWM:       304 kB
    VmRSS:       304 kB
    VmData:       28 kB
    VmStk:        88 kB
    VmExe:         4 kB
    VmLib:      1464 kB
    VmPTE:        20 kB
    Threads:        1
    SigQ:   0/16382
    SigPnd: 0000000000000000
    ShdPnd: 0000000000000000
    SigBlk: 0000000000000000
    SigIgn: 0000000000000000
    SigCgt: 0000000000000000
    CapInh: 0000000000000000
    CapPrm: 0000000000000000
    CapEff: 0000000000000000
    CapBnd: ffffffffffffffff
    Cpus_allowed:   f
    Cpus_allowed_list:      0-3
    Mems_allowed:   1
    Mems_allowed_list:      0
    voluntary_ctxt_switches:        1
    nonvoluntary_ctxt_switches:     1注意,VmData,VmStk,VmExe和VmLib之和并不等于VmSize。这是因为共享库函数的数据段没有计算进去(VmData仅包含a.out程序的数据段,不包括共享库函数的数据段,也不包括通过mmap映射的区域。VmLib仅包括共享库的代码段,不包括共享库的数据段)。

    通过/proc/<pid>/smaps可以查看进程整个虚拟地址空间的映射情况,它的输出从低地址到高地址按顺序输出每一个映射区域的相关信息,如下所示:

    $ cat /proc/10069/smaps
    00110000-00263000 r-xp 00000000 08:07 128311     /lib/tls/i686/cmov/libc-2.11.1.so
    Size:               1356 kB
    Rss:                 148 kB
    Pss:                   8 kB
    Shared_Clean:        148 kB
    Shared_Dirty:          0 kB
    Private_Clean:         0 kB
    Private_Dirty:         0 kB
    Referenced:          148 kB
    Swap:                  0 kB
    KernelPageSize:        4 kB
    MMUPageSize:           4 kB
    ......
    ......
    bfd7f000-bfd94000 rw-p 00000000 00:00 0          [stack]
    Size:                 88 kB
    Rss:                   8 kB
    Pss:                   8 kB
    Shared_Clean:          0 kB
    Shared_Dirty:          0 kB
    Private_Clean:         0 kB
    Private_Dirty:         8 kB
    Referenced:            8 kB
    Swap:                  0 kB
    KernelPageSize:        4 kB
    MMUPageSize:           4 kB注意:rwxp中,p表示私有映射(采用Copy-On-Write技术)。 Size字段就是该区域的大小。



    一提到内存管理,我们头脑中闪出的两个概念,就是虚拟内存,与物理内存。这两个概念主要来自于linux内核的支持。

    Linux在内存管理上份为两级,一级是线性区,类似于00c73000-00c88000,对应于虚拟内存,它实际上不占用实际物理内存;一级是具体的物理页面,它对应我们机器上的物理内存。

    这里要提到一个很重要的概念,内存的延迟分配。Linux内核在用户申请内存的时候,只是给它分配了一个线性区(也就是虚存),并没有分配实际物理内存;只有当用户使用这块内存的时候,内核才会分配具体的物理页面给用户,这时候才占用宝贵的物理内存。内核释放物理页面是通过释放线性区,找到其所对应的物理页面,将其全部释放的过程。

    1
    2
    3
    char *p=malloc(2048)//这里只是分配了虚拟内存2048,并不占用实际内存。
    strcpy(p,”123”)//分配了物理页面,虽然只是使用了3个字节,但内存还是为它分配了2048字节的物理内存。
    free(p)//通过虚拟地址,找到其所对应的物理页面,释放物理页面,释放线性区。

    我们知道用户的进程和内核是运行在不同的级别,进程与内核之间的通讯是通过系统调用来完成的。进程在申请和释放内存,主要通过brk,sbrk,mmap,unmmap这几个系统调用,传递的参数主要是对应的虚拟内存。

    注意一点,在进程只能访问虚拟内存,它实际上是看不到内核物理内存的使用,这对于进程是完全透明的。

     

    glibc内存管理器

    那么我们每次调用malloc来分配一块内存,都进行相应的系统调用呢?

    答案是否定的,这里我要引入一个新的概念,glibc的内存管理器。

    我们知道malloc和free等函数都是包含在glibc库里面的库函数,我们试想一下,每做一次内存操作,都要调用系统调用的话,那么程序将多么的低效。

    实际上glibc采用了一种批发和零售的方式来管理内存。glibc每次通过系统调用的方式申请一大块内存(虚拟内存),当进程申请内存时,glibc就从自己获得的内存中取出一块给进程。

     

    内存管理器面临的困难

    我们在写程序的时候,每次申请的内存块大小不规律,而且存在频繁的申请和释放,这样不可避免的就会产生内存碎块。而内存碎块,直接会导致大块内存申请无法满足,从而更多的占用系统资源;如果进行碎块整理的话,又会增加cpu的负荷,很多都是互相矛盾的指标,这里我就不细说了。

    我们在写程序时,涉及内存时,有两个概念heap和stack。传统的说法stack的内存地址是向下增长的,heap的内存地址是向上增长的。

    函数malloc和free,主要是针对heap进行操作,由程序员自主控制内存的访问。

    在这里heap的内存地址向上增长,这句话不完全正确。

    glibc对于heap内存申请大于128k的内存申请,glibc采用mmap的方式向内核申请内存,这不能保证内存地址向上增长;小于128k的则采用brk,对于它来讲是正确的。128k的阀值,可以通过glibc的库函数进行设置。

    这里我先讲大块内存的申请,也即对应于mmap系统调用。

    对于大块内存申请,glibc直接使用mmap系统调用为其划分出另一块虚拟地址,供进程单独使用;在该块内存释放时,使用unmmap系统调用将这块内存释放,这个过程中间不会产生内存碎块等问题。

    针对小块内存的申请,在程序启动之后,进程会获得一个heap底端的地址,进程每次进行内存申请时,glibc会将堆顶向上增长来扩展内存空间,也就是我们所说的堆地址向上增长。在对这些小块内存进行操作时,便会产生内存碎块的问题。实际上brk和sbrk系统调用,就是调整heap顶地址指针。

     

    那么heap堆的内存是什么时候释放呢?

    当glibc发现堆顶有连续的128k的空间是空闲的时候,它就会通过brk或sbrk系统调用,来调整heap顶的位置,将占用的内存返回给系统。这时,内核会通过删除相应的线性区,来释放占用的物理内存。

    下面我要讲一个内存空洞的问题:

    一个场景,堆顶有一块正在使用的内存,而下面有很大的连续内存已经被释放掉了,那么这块内存是否能够被释放?其对应的物理内存是否能够被释放?

    很遗憾,不能。

    这也就是说,只要堆顶的部分申请内存还在占用,我在下面释放的内存再多,都不会被返回到系统中,仍然占用着物理内存。为什么会这样呢?

    这主要是与内核在处理堆的时候,过于简单,它只能通过调整堆顶指针的方式来调整调整程序占用的线性区;而又只能通过调整线性区的方式,来释放内存。所以只要堆顶不减小,占用的内存就不会释放。

    提一个问题:

    1
    2
    char *p=malloc(2);
    free(p)

    为什么申请内存的时候,需要两个参数,一个是内存大小,一个是返回的指针;而释放内存的时候,却只要内存的指针呢?

    这主要是和glibc的内存管理机制有关。glibc中,为每一块内存维护了一个chunk的结构。glibc在分配内存时,glibc先填写chunk结构中内存块的大小,然后是分配给进程的内存。

    1
    2
    chunk ------size
    p------------ content

    在进程释放内存时,只要 指针-4 便可以找到该块内存的大小,从而释放掉。

    注:glibc在做内存申请时,最少分配16个字节,以便能够维护chunk结构。

    glibc提供的调试工具:

    为了方便调试,glibc 为用户提供了 malloc 等等函数的钩子(hook),如 __malloc_hook

    对应的是一个函数指针,

    1
    void*function(size_t size, constvoid*caller)

    其中 caller 是调用 malloc 返回值的接受者(一个指针的地址)。另外有 __malloc_initialize_hook函数指针,仅仅会调用一次(第一次分配动态内存时)。(malloc.h)

    一些使用 malloc 的统计量(SVID 扩展)可以用 struct mallinfo 储存,可调用获得。

    1
    struct mallinfo mallinfo (void)

    如何检测 memory leakage?glibc 提供了一个函数

    void mtrace (void)及其反作用void muntrace (void)

    这时会依赖于一个环境变量 MALLOC_TRACE 所指的文件,把一些信息记录在该文件中

    用于侦测 memory leakage,其本质是安装了前面提到的 hook。一般将这些函数用

    #ifdef DEBUGGING 包裹以便在非调试态下减少开销。产生的文件据说不建议自己去读,

    而使用 mtrace 程序(perl 脚本来进行分析)。下面用一个简单的例子说明这个过程,这是

    源程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include
    #include
    #include
    intmain(intargc, char *argv[] )
    {
      int*p, *q ;
      #ifdef DEBUGGING
      mtrace( ) ;
      #endif
      p = malloc( sizeof( int) ) ;
      q = malloc( sizeof( int) ) ;
      printf("p = %p\nq = %p\n", p, q ) ;
      *p = 1 ;
      *q = 2 ;
      free( p ) ;
      return0 ;
    }

    很简单的程序,其中 q 没有被释放。我们设置了环境变量后并且 touch 出该文件

    执行结果如下:

    p = 0x98c0378q = 0x98c0388

    该文件内容如下

    1
    2
    3
    4
    = Start
    @./test30:[0x8048446] + 0x98c03780x4
    @./test30:[0x8048455] + 0x98c03880x4
    @./test30:[0x804848f] - 0x98c0378

    到这里我基本上讲完了,我们写程序时,数据部分内存使用的问题。

     

    代码占用的内存

    数据部分占用内存,那么我们写的程序是不是也占用内存呢?

    在linux中,程序的加载,涉及到两个工具,linker 和loader。Linker主要涉及动态链接库的使用,loader主要涉及软件的加载。

    1. exec执行一个程序
    2. elf为现在非常流行的可执行文件的格式,它为程序运行划分了两个段,一个段是可以执行的代码段,它是只读,可执行;另一个段是数据段,它是可读写,不能执行。
    3. loader会启动,通过mmap系统调用,将代码端和数据段映射到内存中,其实也就是为其分配了虚拟内存,注意这时候,还不占用物理内存;只有程序执行到了相应的地方,内核才会为其分配物理内存。
    4.  loader会去查找该程序依赖的链接库,首先看该链接库是否被映射进内存中,如果没有使用mmap,将代码段与数据段映射到内存中,否则只是将其加入进程的地址空间。这样比如glibc等库的内存地址空间是完全一样。

    因此一个2M的程序,执行时,并不意味着为其分配了2M的物理内存,这与其运行了的代码量,与其所依赖的动态链接库有关。

     

    运行过程中链接动态链接库与编译过程中链接动态库的区别

    我们调用动态链接库有两种方法:一种是编译的时候,指明所依赖的动态链接库,这样loader可以在程序启动的时候,来所有的动态链接映射到内存中;一种是在运行过程中,通过dlopen和dlfree的方式加载动态链接库,动态将动态链接库加载到内存中。

    这两种方式,从编程角度来讲,第一种是最方便的,效率上影响也不大,在内存使用上有些差别。

    第一种方式,一个库的代码,只要运行过一次,便会占用物理内存,之后即使再也不使用,也会占用物理内存,直到进程的终止。

    第二中方式,库代码占用的内存,可以通过dlfree的方式,释放掉,返回给物理内存。

    这个差别主要对于那些寿命很长,但又会偶尔调用各种库的进程有关。如果是这类进程,建议采用第二种方式调用动态链接库




    包含了所有CPU活跃的信息,该文件中的所有值都是从系统启动开始累计到当前时刻。

    [root@localhost ~]# cat /proc/self/statm

    654 57 44 0 0 334 0

    输出解释

    CPU 以及CPU0的每行的每个参数意思(以第一行为例)为:

    参数 解释 /proc/pid/statm

    Size (pages) 任务虚拟地址空间的物理内存页数 

    Resident(pages) 应用程序正在使用的物理内存页数 

    Shared(pages) 共享页数 0

    Trs(pages) 程序所拥有的可执行虚拟内存的物理内存页数 

    Lrs(pages) 被映像到任务的虚拟内存空间的库的物理内存页数 

    Drs(pages) 程序数据段和用户态的栈的物理内存页数 

    dt(pages) 0

    linux下page的大小一般为4096,即4KB

    查看linux下page大小的命令是 getconf PAGE_SIZE

    打开 /proc/pid/statm 文件 即可获取进程pid下包含了所有CPU活跃的信息,该文件中的所有值都是从系统启动开始累计到当前时刻。
    [root@localhost ~]# cat /proc/self/statm
    654 57 44 0 0 334 0
    输出解释
    CPU 以及CPU0的每行的每个参数意思(以第一行为例)为:
    参数 解释 /proc/pid/statm
    Size (pages) 任务虚拟地址空间的物理内存页数 
    Resident(pages) 应用程序正在使用的物理内存页数 
    Shared(pages) 共享页数 0
    Trs(pages) 程序所拥有的可执行虚拟内存的物理内存页数 
    Lrs(pages) 被映像到任务的虚拟内存空间的库的物理内存页数 
    Drs(pages) 程序数据段和用户态的栈的物理内存页数 
    dt(pages) 0
    linux下page的大小一般为4096,即4KB
    查看linux下page大小的命令是 getconf PAGE_SIZE
    打开 /proc/pid/statm 文件 即可获取进程pid下的内存使用情况的内存使用情况



    linux 下面查看内存有多种渠道,比如通过命令 ps ,top,free 等,比如通过/proc系统,一般需要比较详细和精确地知道整机内存/某个进程内存的使用情况,最好通过/proc 系统,下面介绍/proc系统下内存相关的几个文件

     

    单个进程的内存查看  cat /proc/[pid] 下面有几个文件: maps , smaps, status

     

    maps 文件可以查看某个进程的代码段、栈区、堆区、动态库、内核区对应的虚拟地址,如果你还不了解linux进程的内存空间,可以参考这里

    下图是maps文件内存示例

    复制代码
     Develop>cat /proc/self/maps 
    00400000-0040b000 r-xp 00000000 fd:00 48              /mnt/cf/orig/root/bin/cat
    0060a000-0060b000 r--p 0000a000 fd:00 48              /mnt/cf/orig/root/bin/cat
    0060b000-0060c000 rw-p 0000b000 fd:00 48              /mnt/cf/orig/root/bin/cat 代码段
    0060c000-0062d000 rw-p 00000000 00:00 0               [heap] 堆区
    7f1fff43b000-7f1fff5d4000 r-xp 00000000 fd:00 861   /mnt/cf/orig/root/lib64/libc-2.15.so
    7f1fff5d4000-7f1fff7d3000 ---p 00199000 fd:00 861  /mnt/cf/orig/root/lib64/libc-2.15.so
    7f1fff7d3000-7f1fff7d7000 r--p 00198000 fd:00 861   /mnt/cf/orig/root/lib64/libc-2.15.so
    7f1fff7d7000-7f1fff7d9000 rw-p 0019c000 fd:00 861   /mnt/cf/orig/root/lib64/libc-2.15.so
    7f1fff7d9000-7f1fff7dd000 rw-p 00000000 00:00 0 
    7f1fff7dd000-7f1fff7fe000 r-xp 00000000 fd:00 2554  /mnt/cf/orig/root/lib64/ld-2.15.so
    7f1fff9f9000-7f1fff9fd000 rw-p 00000000 00:00 0 
    7f1fff9fd000-7f1fff9fe000 r--p 00020000 fd:00 2554  /mnt/cf/orig/root/lib64/ld-2.15.so
    7f1fff9fe000-7f1fff9ff000 rw-p 00021000 fd:00 2554  /mnt/cf/orig/root/lib64/ld-2.15.so
    7f1fff9ff000-7f1fffa00000 rw-p 00000000 00:00 0 
    7fff443de000-7fff443ff000 rw-p 00000000 00:00 0     [stack] 用户态栈区
    7fff443ff000
    -7fff44400000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 内核区
    复制代码

    有时候可以通过不断查看某个进程的maps文件,通过查看其虚拟内存(堆区)是否不停增长来简单判断进程是否发生了内存溢出。

    maps文件只能显示简单的分区,smap文件可以显示每个分区的更详细的内存占用数据

    下图是smaps文件内存示例, 实际显示内容会将每一个区都显示出来,下面我只拷贝了代码段和堆区,

    每一个区显示的内容项目是一样的,smaps文件各项含义可以参考这里

    复制代码
     Develop>cat /proc/self/smaps 
    00400000-0040b000 r-xp 00000000 fd:00 48  /mnt/cf/orig/root/bin/cat
    Size:                 44 kB 虚拟内存大小
    Rss:                  28 kB 实际使用物理内存大小
    Pss:                  28 kB
    Shared_Clean:         0 kB 页面被改,则是dirty,否则是clean,页面引用计数>1,是shared,否则是private
    Shared_Dirty:          0 kB
    Private_Clean:        28 kB
    Private_Dirty:         0 kB
    Referenced:           28 kB
    Anonymous:             0 kB
    AnonHugePages:         0 kB
    Swap:                  0 kB  处于交换区的页面大小
    KernelPageSize:        4 kB  操作系统一个页面大小
    MMUPageSize:           4 kB  体系结构MMU一个页面大小 
    Locked:                0 kB

    0060c000-0062d000 rw-p 00000000 00:00 0 [heap]
    Size: 132 kB
    Rss: 8 kB
    Pss: 8 kB
    Shared_Clean: 0 kB
    Shared_Dirty: 0 kB
    Private_Clean: 0 kB
    Private_Dirty: 8 kB
    Referenced: 8 kB
    Anonymous: 8 kB
    AnonHugePages: 0 kB
    Swap: 0 kB
    KernelPageSize: 4 kB
    MMUPageSize: 4 kB
    Locked: 0 kB

    复制代码

     

    下图是status文件内存示例, 加粗部分是内存相关的统计,

     

    复制代码
     Develop>cat /proc/24475/status
    Name:    netio   可执行程序的名字
    State:    R (running) 任务状态,运行/睡眠/僵死
    Tgid:    24475  线程组号
    Pid:    24475   进程id
    PPid:    19635  父进程id
    TracerPid:    0  
    Uid:    0    0    0    0
    Gid:    0    0    0    0
    FDSize:    256 该进程最大文件描述符个数
    Groups:    0 
    VmPeak:     6330708 kB  内存使用峰值
    VmSize:
    268876 kB 进程虚拟地址空间大小
    VmLck:
    0 kB 进程锁住的物理内存大小,锁住的物理内存无法交换到硬盘

    VmHWM:
    16656 kB
    VmRSS:
    11420 kB 进程正在使用的物理内存大小
    VmData:
    230844 kB 进程数据段大小
    VmStk:
    136 kB 进程用户态栈大小
    VmExe:
    760 kB 进程代码段大小
    VmLib:
    7772 kB 进程使用的库映射到虚拟内存空间的大小
    VmPTE:
    120 kB 进程页表大小
    VmSwap:
    0
    kB Threads: 5 SigQ: 0/63346 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000001000000 SigCgt: 0000000180000000 CapInh: 0000000000000000 CapPrm: ffffffffffffffff CapEff: ffffffffffffffff CapBnd: ffffffffffffffff Cpus_allowed: 01 Cpus_allowed_list: 0 Mems_allowed: 01 Mems_allowed_list: 0 voluntary_ctxt_switches: 201 nonvoluntary_ctxt_switches: 909
    复制代码

    可以看到,linux下内存占用是一个比较复杂的概念,不能

    简单通过一个单一指标就判断某个程序“内存消耗”大小,原因有下面2点:

    • 进程所申请的内存不一定真正会被用到(malloc或mmap的实现)
    • 真正用到了的内存也不一定是只有该进程自己在用 (比如动态共享库)

    关于内存的使用分析及本文几个命令的说明也可以参考这里

    下面是查看整机内存使用情况的文件 /proc/meminfo

    复制代码
     Develop>cat /proc/meminfo 
    MemTotal:        8112280 kB 所有可用RAM大小 (即物理内存减去一些预留位和内核的二进制代码大小)
    MemFree:         4188636 kB LowFree与HighFree的总和,被系统留着未使用的内存
    Buffers:           34728 kB 用来给文件做缓冲大小
    Cached:           289740 kB 被高速缓冲存储器(cache memory)用的内存的大小
    (等于 diskcache minus SwapCache )
    SwapCached:
    0 kB 被高速缓冲存储器(cache memory)用的交换空间的大小 
    已经被交换出来的内存,但仍然被存放在swapfile中。
    用来在需要的时候很快的被替换而不需要再次打开I/O端口
    Active:
    435240 kB 在活跃使用中的缓冲或高速缓冲存储器页面文件的大小,
    除非非常必要否则不会被移作他用
    Inactive:
    231512 kB 在不经常使用中的缓冲或高速缓冲存储器页面文件的大小,可能被用于其他途径. Active(anon): 361252 kB Inactive(anon): 120688 kB Active(file): 73988 kB Inactive(file): 110824 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 0 kB 交换空间的总大小 SwapFree: 0 kB 未被使用交换空间的大小 Dirty: 0 kB 等待被写回到磁盘的内存大小 Writeback: 0 kB 正在被写回到磁盘的内存大小 AnonPages: 348408 kB 未映射页的内存大小 Mapped: 33600 kB 已经被设备和文件等映射的大小 Shmem: 133536 kB Slab: 55984 kB 内核数据结构缓存的大小,可以减少申请和释放内存带来的消耗 SReclaimable: 25028 kB 可收回Slab的大小 SUnreclaim: 30956 kB 不可收回Slab的大小(SUnreclaim+SReclaimable=Slab) KernelStack: 1896 kB 内核栈区大小 PageTables: 8156 kB 管理内存分页页面的索引表的大小 NFS_Unstable: 0 kB 不稳定页表的大小 Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 2483276 kB Committed_AS: 1804104 kB VmallocTotal: 34359738367 kB 可以vmalloc虚拟内存大小 VmallocUsed: 565680 kB 已经被使用的虚拟内存大小 VmallocChunk: 34359162876 kB HardwareCorrupted: 0 kB HugePages_Total: 1536 大页面数目 HugePages_Free: 0 空闲大页面数目 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB 大页面一页大小 DirectMap4k: 10240 kB DirectMap2M: 8302592 kB
    复制代码

     

     

    想必在linux上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题——你的程序在运行时占用了多少内存(物理内存)?通常我们可以通过top命令查看进程占用了多少内存。这里我们可以看到VIRT、RES和SHR三个重要的指标,他们分别代表什么意思呢?这是本文需要跟大家一起探讨的问题。当然如果更加深入一点,你可能会问进程所占用的那些物理内存都用在了哪些地方?这时候top命令可能不能给到你你所想要的答案了,不过我们可以分析proc文件系统提供的smaps文件,这个文件详尽地列出了当前进程所占用物理内存的使用情况。

        这篇blog总共分为三个部分。第一部分简要阐述虚拟内存和驻留内存这两个重要的概念;第二部分解释top命令中VIRT、RES以及SHR三个参数的实际参考意义;最后一部分向大家介绍一下smaps文件的格式,通过分析smaps文件我们可以详细了解进程物理内存的使用情况,比如mmap文件占用了多少空间、动态内存开辟消耗了多少空间、函数调用栈消耗了多少空间等等。

    关于内存的两个概念

          要理解top命令关于内存使用情况的输出,我们必须首先搞清楚虚拟内存(Virtual Memory)驻留内存(Resident Memory)两个概念。

    • 虚拟内存

       首先需要强调的是虚拟内存不同于物理内存,虽然两者都包含内存字眼但是它们属于两个不同层面的概念。进程占用虚拟内存空间大并非意味着程序的物理内存也一定占用很大。虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。我们程序中的指针其实都是这个虚拟内存空间中的地址。比如我们在写完一段C++程序之后都需要采用g++进行编译,这时候编译器采用的地址其实就是虚拟内存空间的地址。因为这时候程序还没有运行,何谈物理内存空间地址?凡是程序运行过程中可能需要用到的指令或者数据都必须在虚拟内存空间中。既然说虚拟内存是一个逻辑意义上(假象的)的内存空间,为了能够让程序在物理机器上运行,那么必须有一套机制可以让这些假象的虚拟内存空间映射到物理内存空间(实实在在的RAM内存条上的空间)。这其实就是操作系统中页映射表(page table)所做的事情了。内核会为系统中每一个进程维护一份相互独立的页映射表。。页映射表的基本原理是将程序运行过程中需要访问的一段虚拟内存空间通过页映射表映射到一段物理内存空间上,这样CPU访问对应虚拟内存地址的时候就可以通过这种查找页映射表的机制访问物理内存上的某个对应的地址。“页(page)”是虚拟内存空间向物理内存空间映射的基本单元。

            下图1演示了虚拟内存空间和物理内存空间的相互关系,它们通过Page Table关联起来。其中虚拟内存空间中着色的部分分别被映射到物理内存空间对应相同着色的部分。而虚拟内存空间中灰色的部分表示在物理内存空间中没有与之对应的部分,也就是说灰色部分没有被映射到物理内存空间中。这么做也是本着“按需映射”的指导思想,因为虚拟内存空间很大,可能其中很多部分在一次程序运行过程中根本不需要访问,所以也就没有必要将虚拟内存空间中的这些部分映射到物理内存空间上。

            到这里为止已经基本阐述了什么是虚拟内存了。总结一下就是,虚拟内存是一个假象的内存空间,在程序运行过程中虚拟内存空间中需要被访问的部分会被映射到物理内存空间中。虚拟内存空间大只能表示程序运行过程中可访问的空间比较大,不代表物理内存空间占用也大。

                      图1. 虚拟内存空间到物理内存空间映射

    • 驻留内存

      驻留内存,顾名思义是指那些被映射到进程虚拟内存空间的物理内存。上图1中,在系统物理内存空间中被着色的部分都是驻留内存。比如,A1、A2、A3和A4是进程A的驻留内存;B1、B2和B3是进程B的驻留内存。进程的驻留内存就是进程实实在在占用的物理内存。一般我们所讲的进程占用了多少内存,其实就是说的占用了多少驻留内存而不是多少虚拟内存。因为虚拟内存大并不意味着占用的物理内存大。

      关于虚拟内存和驻留内存这两个概念我们说到这里。下面一部分我们来看看top命令中VIRT、RES和SHR分别代表什么意思。

    top命令中VIRT、RES和SHR的含义

         搞清楚了虚拟内存的概念之后解释VIRT的含义就很简单了。VIRT表示的是进程虚拟内存空间大小。对应到图1中的进程A来说就是A1、A2、A3、A4以及灰色部分所有空间的总和。也就是说VIRT包含了在已经映射到物理内存空间的部分和尚未映射到物理内存空间的部分总和。

      RES的含义是指进程虚拟内存空间中已经映射到物理内存空间的那部分的大小。对应到图1中的进程A来说就是A1、A2、A3以及A4几个部分空间的总和。所以说,看进程在运行过程中占用了多少内存应该看RES的值而不是VIRT的值。

      最后来看看SHR所表示的含义。SHR是share(共享)的缩写,它表示的是进程占用的共享内存大小。在上图1中我们看到进程A虚拟内存空间中的A4和进程B虚拟内存空间中的B3都映射到了物理内存空间的A4/B3部分。咋一看很奇怪。为什么会出现这样的情况呢?其实我们写的程序会依赖于很多外部的动态库(.so),比如libc.so、libld.so等等。这些动态库在内存中仅仅会保存/映射一份,如果某个进程运行时需要这个动态库,那么动态加载器会将这块内存映射到对应进程的虚拟内存空间中。多个进展之间通过共享内存的方式相互通信也会出现这样的情况。这么一来,就会出现不同进程的虚拟内存空间会映射到相同的物理内存空间。这部分物理内存空间其实是被多个进程所共享的,所以我们将他们称为共享内存,用SHR来表示。某个进程占用的内存除了和别的进程共享的内存之外就是自己的独占内存了。所以要计算进程独占内存的大小只要用RES的值减去SHR值即可。

    进程的smaps文件

      通过top命令我们已经能看出进程的虚拟空间大小(VIRT)、占用的物理内存(RES)以及和其他进程共享的内存(SHR)。但是仅此而已,如果我想知道如下问题:

    1. 进程的虚拟内存空间的分布情况,比如heap占用了多少空间、文件映射(mmap)占用了多少空间、stack占用了多少空间?
    2. 进程是否有被交换到swap空间的内存,如果有,被交换出去的大小?
    3. mmap方式打开的数据文件有多少页在内存中是脏页(dirty page)没有被写回到磁盘的?
    4. mmap方式打开的数据文件当前有多少页面已经在内存中,有多少页面还在磁盘中没有加载到page cahe中?
    5. 等等

      以上这些问题都无法通过top命令给出答案,但是有时候这些问题正是我们在对程序进行性能瓶颈分析和优化时所需要回答的问题。所幸的是,世界上解决问题的方法总比问题本身要多得多。linux通过proc文件系统为每个进程都提供了一个smaps文件,通过分析该文件我们就可以一一回答以上提出的问题。

      在smaps文件中,每一条记录(如下图2所示)表示进程虚拟内存空间中一块连续的区域。其中第一行从左到右依次表示地址范围、权限标识、映射文件偏移、设备号、inode、文件路径。详细解释可以参见understanding-linux-proc-id-maps

      接下来8个字段的含义分别如下:

    • Size:表示该映射区域在虚拟内存空间中的大小。
    • Rss:表示该映射区域当前在物理内存中占用了多少空间      
    • Shared_Clean:和其他进程共享的未被改写的page的大小
    • Shared_Dirty: 和其他进程共享的被改写的page的大小
    • Private_Clean:未被改写的私有页面的大小。
    • Private_Dirty: 已被改写的私有页面的大小。
    • Swap:表示非mmap内存(也叫anonymous memory,比如malloc动态分配出来的内存)由于物理内存不足被swap到交换空间的大小。
    • Pss:该虚拟内存区域平摊计算后使用的物理内存大小(有些内存会和其他进程共享,例如mmap进来的)。比如该区域所映射的物理内存部分同时也被另一个进程映射了,且该部分物理内存的大小为1000KB,那么该进程分摊其中一半的内存,即Pss=500KB。

                                图2. smaps文件中的一条记录

      有了smap如此详细关于虚拟内存空间到物理内存空间的映射信息,相信大家已经能够通过分析该文件回答上面提出的4个问题。

      最后希望所有读者能够通过阅读本文对进程的虚拟内存和物理内存有一个更加清晰认识,并能更加准确理解top命令关于内存的输出,最后可以通过smaps文件更进一步分析进程使用内存的情况。


    展开全文
  • 【精】Linux 使用free查询可用内存

    千次阅读 2016-07-18 15:42:15
    free 查询可用内存free工具用来查看系统可用内存:/opt/app/tdev1$free total used free shared buffers cached Mem: 8175320 6159248 2016072 0 310208 5243680 -/+

    free 查询可用内存


    free工具用来查看系统可用内存:

    /opt/app/tdev1$free
                 total       used       free     shared    buffers     cached
    Mem:       8175320    6159248    2016072          0     310208    5243680
    -/+ buffers/cache:     605360    7569960
    Swap:      6881272      16196    6865076
    

    解释一下Linux上free命令的输出。

    下面是free的运行结果,一共有4行。为了方便说明,我加上了列号。这样可以把free的输出看成一个二维数组FO(Free Output)。例如:

    FO[2][1] = 24677460
    FO[3][2] = 10321516
    
                       1          2          3          4          5          6
    1              total       used       free     shared    buffers     cached
    2 Mem:      24677460   23276064    1401396          0     870540   12084008
    3 -/+ buffers/cache:   10321516   14355944
    4 Swap:     25151484     224188   24927296
    

    free的输出一共有四行,第四行为交换区的信息,分别是交换的总量(total),使用量(used)和有多少空闲的交换区(free),这个比较清楚,不说太多。

    free输出地第二行和第三行是比较让人迷惑的。这两行都是说明内存使用情况的。第一列是总量(total),第二列是使用量(used),第三列是可用量(free)。

      第一行的输出时从操作系统(OS)来看的。也就是说,从OS的角度来看,计算机上一共有:

    24677460KB(缺省时free的单位为KB)物理内存,即FO[2][1]; 在这些物理内存中有23276064KB(即FO[2][2])被使用了; 还用1401396KB(即FO[2][3])是可用的;

    这里得到第一个等式:

    FO[2][1] = FO[2][2] + FO[2][3]

    FO[2][4]表示被几个进程共享的内存的,现在已经deprecated,其值总是0(当然在一些系统上也可能不是0,主要取决于free命令是怎么实现的)。

    FO[2][5]表示被OS buffer住的内存。FO[2][6]表示被OS cache的内存。在有些时候buffer和cache这两个词经常混用。不过在一些比较低层的软件里是要区分这两个词的,看老外的洋文:

    A buffer is something that has yet to be "written" to disk.
    A cache is something that has been "read" from the disk and stored for later use.
    

    也就是说buffer是用于存放要输出到disk(块设备)的数据的,而cache是存放从disk上读出的数据。这二者是为了提高IO性能的,并由OS管理。

    Linux和其他成熟的操作系统(例如windows),为了提高IO read的性能,总是要多cache一些数据,这也就是为什么FO[2][6](cached memory)比较大,而FO[2][3]比较小的原因。我们可以做一个简单的测试:

    释放掉被系统cache占用的数据:

    echo 3>/proc/sys/vm/drop_caches
    
    1. 读一个大文件,并记录时间;
    2. 关闭该文件;
    3. 重读这个大文件,并记录时间;

    第二次读应该比第一次快很多。原来我做过一个BerkeleyDB的读操作,大概要读5G的文件,几千万条记录。在我的环境上,第二次读比第一次大概可以快9倍左右。

    free输出的第二行是从一个应用程序的角度看系统内存的使用情况。

    • 对于FO[3][2],即-buffers/cache,表示一个应用程序认为系统被用掉多少内存;
    • 对于FO[3][3],即+buffers/cache,表示一个应用程序认为系统还有多少内存;

    因为被系统cache和buffer占用的内存可以被快速回收,所以通常FO[3][3]比FO[2][3]会大很多。

    这里还用两个等式:

    FO[3][2] = FO[2][2] - FO[2][5] - FO[2][6]
    FO[3][3] = FO[2][3] + FO[2][5] + FO[2][6]
    

    这二者都不难理解。

    free命令由procps.*.rpm提供(在Redhat系列的OS上)。free命令的所有输出值都是从/proc/meminfo中读出的。

    在系统上可能有meminfo(2)这个函数,它就是为了解析/proc/meminfo的。procps这个包自己实现了meminfo()这个函数。可以下载一个procps的tar包看看具体实现,现在最新版式3.2.8。

    展开全文
  • 本文从索引、查询内存三个方面介绍一些基础的Elasticsearch性能优化方法。 1.索引优化 1.1 批量提交 当有大量数据提交的时候,建议采用批量提交。 比如在做 ELK 过程中 ,Logstash indexer 提交数据到 Elastic...

    本文从索引、查询和内存三个方面介绍一些基础的Elasticsearch性能优化方法。

    1.索引优化

    1.1 批量提交

    当有大量数据提交的时候,建议采用批量提交。

    比如在做 ELK 过程中 ,Logstash indexer 提交数据到 Elasticsearch 中 ,batch size 就可以作为一个优化功能点。但是优化 size 大小需要根据文档大小和服务器性能而定。

    像 Logstash 中提交文档大小超过 20MB ,Logstash 会请一个批量请求切分为多个批量请求。

    如果在提交过程中,遇到 EsRejectedExecutionException 异常的话,则说明集群的索引性能已经达到极限了。这种情况,要么提高服务器集群的资源,要么根据业务规则,减少数据收集速度,比如只收集 Warn、Error 级别以上的日志。

    1.2 优化硬件

    优化硬件设备一直是最快速有效的手段。

    在经济压力能承受的范围下, 尽量使用固态硬盘 SSD。SSD 相对于机器硬盘,无论随机写还是顺序写,都较大的提升。
    磁盘备份采用 RAID0。因为 Elasticsearch 在自身层面通过副本,已经提供了备份的功能,所以不需要利用磁盘的备份功能,同时如果使用磁盘备份功能的话,对写入速度有较大的影响。

    1.3 增加 Refresh 时间间隔

    为了提高索引性能,Elasticsearch 在写入数据时候,采用延迟写入的策略,即数据先写到内存中,当超过默认 1 秒 (index.refresh_interval)会进行一次写入操作,就是将内存中 segment 数据刷新到操作系统中,此时我们才能将数据搜索出来,所以这就是为什么 Elasticsearch 提供的是近实时搜索功能,而不是实时搜索功能。

    当然像我们的内部系统对数据延迟要求不高的话,我们可以通过延长 refresh 时间间隔,可以有效的减少 segment 合并压力,提供索引速度。在做全链路跟踪的过程中,我们就将 index.refresh_interval 设置为 30s,减少 refresh 次数。

    同时,在进行全量索引时,可以将 refresh 次数临时关闭,即 index.refresh_interval 设置为 -1,数据导入成功后再打开到正常模式,比如 30s。

    1.4 减少副本数量

    Elasticsearch 默认副本数量为 3 个,虽然这样会提高集群的可用性,增加搜索的并发数,但是同时也会影响写入索引的效率。

    在索引过程中,需要把更新的文档发到副本节点上,等副本节点生效后在进行返回结束。使用 Elasticsearch 做业务搜索的时候,建议副本数目还是设置为 3 个,但是像内部 ELK 日志系统、分布式跟踪系统中,完全可以将副本数目设置为 1 个。

    2.查询优化

    2.1 路由

    当我们查询文档的时候,Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?它其实是通过下面这个公式来计算出来:

    shard = hash(routing) % number_of_primary_shards
    

    routing 默认值是文档的 id,也可以采用自定义值,比如用户 id。

    不带 routing 查询

    在查询的时候因为不知道要查询的数据具体在哪个分片上,所以整个过程分为 2 个步骤

    • 分发:请求到达协调节点后,协调节点将查询请求分发到每个分片上。
    • 聚合: 协调节点搜集到每个分片上查询结果,在将查询的结果进行排序,之后给用户返回结果。
    带 routing 查询

    查询的时候,可以直接根据 routing 信息定位到某个分配查询,不需要查询所有的分配,经过协调节点排序。

    向上面自定义的用户查询,如果 routing 设置为 userid 的话,就可以直接查询出数据来,效率提升很多。

    2.2 Filter VS Query

    Ebay 曾经分享过他们使用 Elasticsearch 的经验中说到:

    Use filter context instead of query context if possible.
    尽可能使用过滤器上下文(Filter)替代查询上下文(Query)

    • Query:此文档与此查询子句的匹配程度如何?
    • Filter:此文档和查询子句匹配吗?

    Elasticsearch 针对 Filter 查询只需要回答「是」或者「否」,不需要像 Query 查询一下计算相关性分数,同时 Filter 结果可以缓存。

    2.3 大翻页

    在使用 Elasticsearch 过程中,应尽量避免大翻页的出现。

    正常翻页查询都是从 From 开始 Size 条数据,这样就需要在每个分片中查询打分排名在前面的 From + Size 条数据。协同节点收集每个分配的前 From + Size 条数据。协同节点一共会受到 N * ( From + Size )条数据,然后进行排序,再将其中 From 到 From + Size 条数据返回出去。

    如果 From 或者 Size 很大的话,导致参加排序的数量会同步扩大很多,最终会导致 CPU 资源消耗增大。

    可以通过使用 Elasticsearch scroll 和 scroll-scan 高效滚动的方式来解决这样的问题。具体写法,可以参考scroll 查询

    3.JVM 设置

    Elasticsearch 默认安装后设置的堆内存是 1 GB。 对于任何一个业务部署来说, 这个设置都太小了。

    比如机器有 64G 内存,那么我们是不是设置的越大越好呢?

    其实不是的。

    主要 Elasticsearch 底层使用 Lucene。Lucene 被设计为可以利用操作系统底层机制来缓存内存数据结构。 Lucene 的段是分别存储到单个文件中的。因为段是不可变的,这些文件也都不会变化,这是对缓存友好的,同时操作系统也会把这些段文件缓存起来,以便更快的访问。

    如果你把所有的内存都分配给 Elasticsearch 的堆内存,那将不会有剩余的内存交给 Lucene。 这将严重地影响全文检索的性能。

    标准的建议是把 50% 的可用内存作为 Elasticsearch 的堆内存,保留剩下的 50%。当然它也不会被浪费,Lucene 会很乐意利用起余下的内存。

    其主要原因是 :JVM 在内存小于 32 GB 的时候会采用一个内存对象指针压缩技术。
    在 Java 中,所有的对象都分配在堆上,并通过一个指针进行引用。 普通对象指针(OOP)指向这些对象,通常为 CPU 字长 的大小:32 位或 64 位,取决于你的处理器。指针引用的就是这个 OOP 值的字节位置。

    对于 32 位的系统,意味着堆内存大小最大为 4 GB。对于 64 位的系统, 可以使用更大的内存,但是 64 位的指针意味着更大的浪费,因为你的指针本身大了。更糟糕的是, 更大的指针在主内存和各级缓存(例如 LLC,L1 等)之间移动数据的时候,会占用更多的带宽.

    所以最终我们都会采用 31 G 设置

    -Xms 31g
    -Xmx 31g
    

    假设你有个机器有 128 GB 的内存,你可以创建两个节点,每个节点内存分配不超过 32 GB。 也就是说不超过 64 GB 内存给 ES 的堆内存,剩下的超过 64 GB 的内存给 Lucene

    参考资料

    合理配置堆内存:大小和交换

    展开全文
  • 分布式内存数据技术为查询提速

    千次阅读 2015-03-06 10:43:47
    背景和需求 ...大量同时涌入的网络访问造成12306几近...同时,感谢Pivotal公司及其实施方项目团队的努力,在技术开改造过程中确保旧系统顺畅运行、旧系统到新系统平滑迁移,快速实现新系统的上线。”
  • Linux环境 Java内存快速查看

    千次阅读 2017-08-24 23:01:00
    最近生产环境遇到内存老是占用很大的情况,16G的内存Free的内存只剩100多M,仿佛一颗定时炸弹一般,说不定就服务Down了。于是开始网上不断的找查看内存使用的方法。现学现卖,以下通过一个例子来演示,共3步。 一...
  • 初出茅庐在学习,君若有不喜之处,吾愿听君之见,然存己身之断...除非内存不值钱! static int min = 1; static int max = 10000000; public static void main(String[] args) { ArrayList<String> arr = new
  • Tomcat内存溢出快速解决办法

    千次阅读 2018-05-04 10:26:03
    cd /opt/apache-tomcat-...cd /bin使用Tomcat关闭命令(一般在运行的项目这样停不下来的,我反正没停下来过)./shutdown.sh查看Tomcat是否以关闭,如果tomcat显示内存过大,没有关掉可以使用ps命令:ps -ef|gre...
  •  因为分页查询每次只会查询少量数据,所以不会占用太多内存,而且数据量很大的时候,分页查询会节约一些时间的。    String sql = " SELECT uid,uname FROM t_user LIMIT ?,? " ;  PreparedSta...
  • 对linux系统中cpu、内存、磁盘IO异常定位的方式进行梳理总结如下: 1、先记录下基本的cpu信息查询方式: # 总核数 = 物理CPU个数 X 每颗物理CPU的核数 # 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超...
  • java内存泄露和内存溢出

    千次阅读 2017-07-06 16:36:04
    内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。 内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于...
  • 内存测试入门

    千次阅读 2018-06-27 19:48:27
    1.1新手入门当软件实现了新功能后,准备发布版本前,往往需要进行一轮性能测试以确定没有性能问题,这类测试通常包括功能的流畅度,电量消耗和内存使用情况等。由于内存组成的复杂性,实际上并没有简单通用的方法就...
  • HBase的快速查询

    千次阅读 2018-05-16 17:18:30
    快速查询和实时查询 快速查询: 一、分区存储 HBase将每个表划分为多个region,每个region用rowkey来华为,数据的查询也是通过rowkey来查询 查询过程:client向HBase依赖的zookeeper获取metaregion的位置,...
  • JVM快速找出耗内存大对象

    千次阅读 2019-07-01 17:42:16
    当我们的java应用运行时,突然出现内存占用暴增,说明系统中存在长期占用的对象无法回收,或者出现巨大的对象如何快速定位产生的大对象是什么? 找到耗内存的进程 top命令查看你的应用对应的进程ID 1、输入top命令 ...
  • 游标查询和流式查询都 能够很好的防止 OOM 并发调用内存使用 并发调用:Jmete 1 秒 10 个线程并发调用 流式查询内存性能报告如下 并发调用对于内存占用情况也很 OK,不存在叠加式增加 流式查询并发调用时间平均消耗...
  • 上篇博客我们写到了 Java/Android 内存的分配以及相关 GC 的详细分析,这篇博客我们会继续分析 Android 中内存泄漏的检测以及相关案例,和 Android 的内存优化相关内容。 Android 内存泄漏案例和检测   常见...
  • 堆外内存与堆内内存详解

    万次阅读 多人点赞 2018-05-07 17:14:42
    一、什么是堆外内存1、堆内内存(on-heap memory)回顾堆外内存和堆内内存是相对的二个概念,其中堆内内存是我们平常工作中接触比较多的,我们在jvm参数中只要使用-Xms,-Xmx等参数就可以设置堆的大小和最大值,理解...
  • MySQL内存调优

    万次阅读 2014-05-28 20:50:05
    原文链接: MySQL Memory Allocation -- by Rick James 原文日期: Created 2010; Refreshed Oct, 2012, Jan, ...MySQL 内存分配—— 快速设置方案 如果仅使用MyISAM存储引擎,设置 key_buffer_size 为可用内存的20%,(再
  • 内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,内存抖动出现原因主要是频繁(很重要)在循环里创建对象(导致大量对象在短时间内被创建,由于新对象是要占用内存空间的而且是频繁,如果一次或者两次在...
  • Android内存(内存溢出 内存不足 内存低 .)优化详解  Android内存泄露 前言  不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露。 其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用...
  • 内存泄露的危害: (1)过多的内存泄露最终会导致内存溢出(OOM)(2)内存泄露导致可用内存不足,会触发频繁GC,不管是Android2.2以前的单线程GC还是现在的CMS和G1,都有一部分的操作会导致用户线程停止(就是所谓...
  • Android内存泄露 ... 不少人认为JAVA程序,因为有垃圾回收...其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造
  • 内存数据库

    千次阅读 2014-04-02 17:29:37
    同时,内存数据库抛弃了磁盘数据管理的传统方式,基于全部数据都在内存中重新设计了体系结构,并且在数据缓存、快速算法、并行操作方面也进行了相应的改进,所以数据处理速度比传统数据库的数据处理速度要快很多,...
  • 搜索引擎之内存映射快速索引文件

    千次阅读 2009-07-01 12:01:00
    搜索引擎之利用内存映射快速索引文件 用户在通过关键字检索信息时,如果反馈的内容在3S之内是适度范围,如果10s了一般用户就会感觉难以忍受了,所以如何提高对用户的反馈速度就成了搜索引擎技术中一个瓶颈. 每天都会...
  • APP性能-内存优化-内存管理认知

    千次阅读 2017-06-04 10:58:44
    前言作为一名Java程序员,我们不需要像C/C++那样为每一个new出来的对象手动delete/free释放内存。因为有GC(垃圾回收器)的自动回收机制会帮我们自动处理。正因为我们把这些操作交给了JVM,所以如果出现内存溢出和...
  • Android内存优化汇总

    千次阅读 2017-09-05 22:07:47
    所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读。(本文最后我会尽量列出所参考的文章)。 内存...
  • 内存

    千次阅读 2019-02-24 14:55:42
    2.ask x y:查询区间[x,y]内所有数的和。 3.goto t:回到第t次操作之后的状态。 n,m&lt;=1e5。特别注意:空间限制为8MB 【分析】由于题目背景中出现了可持久化线段树,又有回退到历史版本的操作,所以很多人试图...
  • App内存占用优化

    千次阅读 2017-01-12 18:47:04
    RAM(Random-access memory)在任何软件开发中都是非常宝贵的资源,移动操作系统由于其物理内存的局限性更是如此。尽管ART(Android Runtime)与Dalvik虚拟机会执行常规的垃圾回收,但这并不意味着可以忽略App中的...
  • 1.内存泄漏:2G的JVM,2天就崩。 2.方法区内存持续飙升,最终导致频繁的触发FullGC 3.class load频繁导致CPU有30%的资源浪费 在写之前先吐槽下:这个自研的JPA组件真TM坑人,放着开源的不用,非得自己

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 257,249
精华内容 102,899
关键字:

内存快速查询