-
2019-04-09 10:33:08
查找内存泄漏问题,可以使用valgrind、malloc_stats和malloc_info 监控查看内存情况。
1、 Linux内存介绍
1.1 Linux 的虚拟内存管理有几个关键概念:
1、每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址;
2、虚拟地址可通过每个进程上的页表(在每个进程的内核虚拟地址空间)与物理地址进行映射,获得真正物理地址;
3、如果虚拟地址对应物理地址不在物理内存中,则产生缺页中断,真正分配物理地址,同时更新进程的页表;如果此时物理内存已耗尽,则根据内存替换算法淘汰部分页面至物理磁盘中。1.2、Linux 虚拟地址空间如何分布?
Linux 使用虚拟地址空间,大大增加了进程的寻址空间,由低地址到高地址分别为:
1、只读段:该部分空间只能读,不可写;(包括:代码段、rodata 段(C常量字符串和#define定义的常量) )
2、数据段:保存全局变量、静态变量的空间;
3、堆 :就是平时所说的动态内存, malloc/new 大部分都来源于此。其中堆顶的位置可通过函数 brk 和 sbrk 进行动态调整。
4、文件映射区域 :如动态库、共享内存等映射物理空间的内存,一般是 mmap 函数所分配的虚拟地址空间。
5、栈:用于维护函数调用的上下文空间,一般为 8M ,可通过 ulimit –s 查看。
6、内核虚拟空间:用户代码不可见的内存区域,由内核管理(页表就存放在内核虚拟空间)。1.3 32 位系统有4G 的地址空间::
其中 0x08048000~0xbfffffff 是用户空间,0xc0000000~0xffffffff 是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈)等。另外,%esp 执行栈顶,往低地址方向变化;brk/sbrk 函数控制堆顶_edata往高地址方向变化。
1.4 64位系统结果怎样呢? 64 位系统是否拥有 2^64 的地址空间吗?
事实上, 64 位系统的虚拟地址空间划分发生了改变:
1、地址空间大小不是2^32,也不是2^64,而一般是2^48。因为并不需要 2^64 这么大的寻址空间,过大空间只会导致资源的浪费。64位Linux一般使用48位来表示虚拟地址空间,40位表示物理地址,这可通过 /proc/cpuinfo 来查看
address sizes : 40 bits physical, 48 bits virtual2、其中,0x0000000000000000~0x00007fffffffffff 表示用户空间, 0xFFFF800000000000~ 0xFFFFFFFFFFFFFFFF 表示内核空间,共提供 256TB(2^48) 的寻址空间。
这两个区间的特点是,第 47 位与 48~63 位相同,若这些位为 0 表示用户空间,否则表示内核空间。3、用户空间由低地址到高地址仍然是只读段、数据段、堆、文件映射区域和栈;
2、 valgrind
valgrind可以用来检测内存泄露,但在使用中,往往会遇到一些问题,给调试工作带来很多不必要的麻烦,我自己遇到的有以下两种:
(1)内存泄露误检(系统初始化时,可能有一些需要长期保存在内存中的数据结构,这些空间是永远不释放的,而这些内存会被认为绝对泄露)
(2) valgrind检查内存泄露过于全面,运行后的结果太多往往很难从中找到有用的信息。有时候,我们只需要关注某些函数,可能在执行某个操作,调用某些函数时会出现内存泄露,此时,valgrind的工作显得冗余而复杂
3、 mallinfo
mallinfo
函数已不推荐使用,并且都不再更新。可以添加代码到程序:
#include <malloc.h> #include <stdio.h> void dumpMallinfo(void) { struct mallinfo m = mallinfo(); printf("uordblks = %dnfordblks = %dn", m.uordblks, m.fordblks); }
在GDB,可以
call dumpMallinfo()
.4、 malloc_stats
系统库函数中提供了malloc_stats()函数,可以统计本进程具体的内存使用情况,精确到字节。
glibc 提供了以下结构和接口来查看堆内内存和 mmap 的使用情况。
struct mallinfo { int arena; /* non-mmapped space allocated from system */ int ordblks; /* number of free chunks */ int smblks; /* number of fastbin blocks */ int hblks; /* number of mmapped regions */ int hblkhd; /* space in mmapped regions */ int usmblks; /* maximum total allocated space */ int fsmblks; /* space available in freed fastbin blocks */ int uordblks; /* total allocated space */ int fordblks; /* total free space */ int keepcost; /* top-most, releasable (via malloc_trim) space */ };
/*返回heap(main_arena)的内存使用情况,以 mallinfo 结构返回 */
struct mallinfo mallinfo();/* 将heap和mmap的使用情况输出到stderr*/
void malloc_stats();4.1 gdb内部调试
call malloc_stats()
(gdb) call malloc_stats() Arena 0: system bytes = 135168 in use bytes = 96 Total (incl. mmap): system bytes = 135168 in use bytes = 96 max mmap regions = 0 max mmap bytes = 0
call malloc_info(0, stdout)
(gdb) call malloc_info(0, stdout) <malloc version="1"> <heap nr="0"> <sizes> <unsorted from="1228788" to="1229476" total="3917678" count="3221220448"/> </sizes> <total type="fast" count="0" size="0"/> <total type="rest" count="3221220448" size="3917678"/> <system type="current" size="135168"/> <system type="max" size="135168"/> <aspace type="total" size="135168"/> <aspace type="mprotect" size="135168"/> </heap> <total type="fast" count="0" size="0"/> <total type="rest" count="3221220448" size="3917678"/> <system type="current" size="135168 /> <system type="max" size="135168 /> <aspace type="total" size="135168"/> <aspace type="mprotect" size="135168"/> </malloc>
4.2 gdb外部调试
根据 malloc_stats()的手册手册,内存信息被发送到标准错误。 一般输出到
stderr
。命名:
gdb --batch --pid <pid> --ex 'call malloc_stats()'
查看stderr.log:
------------------------------------------------ MALLOC: 11159496 ( 10.6 MiB) Bytes in use by application MALLOC: + 1769472 ( 1.7 MiB) Bytes in page heap freelist MALLOC: + 3580792 ( 3.4 MiB) Bytes in central cache freelist MALLOC: + 1998848 ( 1.9 MiB) Bytes in transfer cache freelist MALLOC: + 19240128 ( 18.3 MiB) Bytes in thread cache freelists MALLOC: + 1379480 ( 1.3 MiB) Bytes in malloc metadata MALLOC: ------------ MALLOC: = 39128216 ( 37.3 MiB) Actual memory used (physical + swap) MALLOC: + 0 ( 0.0 MiB) Bytes released to OS (aka unmapped) MALLOC: ------------ MALLOC: = 39128216 ( 37.3 MiB) Virtual address space used MALLOC: MALLOC: 810 Spans in use MALLOC: 157 Thread heaps in use MALLOC: 32768 Tcmalloc page size ------------------------------------------------ Call ReleaseFreeMemory() to release freelist memory to the OS (via madvise()). Bytes released to the OS take up virtual address space but no physical memory.
4.3 其他
- 如果默认编译,使用的是libc的ptmalloc内存分配器库,这个库可能有一定的内存碎片问题,我们线上也有遇到过,见comment最后
- 任然无法查出,得上通用的内存分配扫描工具 。比如systap脚本抓取malloc/free的栈记录,然后分析记录信息查看。比如valgrind --mem-leak(往上可以搜索方法, 注意)
4.3.1 glibc库的ptmalloc2信息采集:
强制nginx worker调用malloc_stats()函数,让其将数据输出到stderr(nginx重定向到error.log) -------- $ gdb -p $(pgrep -P $(cat logs/nginx.pid)) -ex 'call malloc_stats()' error.log内dump出来的信息 ----- Arena 0: <<< 线程 0(一般tengine worker内只有1个thread) system bytes = 1372160 <<< 从os抽取的内存(一般为mmap) in use bytes = 1198448 <<< 应用程序malloc的 Total (incl. mmap): system bytes = 1372160 in use bytes = 1198448 max mmap regions = 6 max mmap bytes = 2789376
4.3.2 for jemalloc:
$ gdb -batch -p <pid> -ex 'call malloc_stats_print(0,0,0)'
如果想知道堆内片究竟有多碎 ,可通过 mallinfo 结构中的 fsmblks 、 smblks 、 ordblks值得到,这些值表示不同大小区间的碎片总个数,这些区间分别是 0~80 字节, 80~512 字节,512~128k 。如果 fsmblks 、 smblks 的值过大,那碎片问题可能比较严重了。
不过, mallinfo 结构有一个很致命的问题,就是其成员定义全部都是 int ,在 64 位环境中,其结构中的 uordblks/fordblks/arena/usmblks 很容易就会导致溢出,应该是历史遗留问题,使用时要注意!
5、 除了 glibc 的 malloc/free ,还有其他第三方实现吗?
其实,很多人开始诟病 glibc 内存管理的实现,就是在高并发性能低下和内存碎片化问题都比较严重,因此,陆续出现一些第三方工具来替换 glibc 的实现,最著名的当属 google 的tcmalloc 和 facebook 的 jemalloc 。网上有很多资源,可搜索之,这里就不详述了。
6、 既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢?
既然堆内碎片不能直接释放,导致疑似“内存泄露”问题,为什么 malloc 不全部使用 mmap 来实现呢(mmap分配的内存可以会通过 munmap 进行 free ,实现真正释放)?而是仅仅对于大于 128k 的大块内存才使用 mmap ?
其实,进程向 OS 申请和释放地址空间的接口 sbrk/mmap/munmap 都是系统调用,频繁调用系统调用都比较消耗系统资源的。并且, mmap 申请的内存被 munmap 后,重新申请会产生更多的缺页中断。例如使用 mmap 分配 1M 空间,第一次调用产生了大量缺页中断 (1M/4K 次 ) ,当munmap 后再次分配 1M 空间,会再次产生大量缺页中断。缺页中断是内核行为,会导致内核态CPU消耗较大。另外,如果使用 mmap 分配小内存,会导致地址空间的分片更多,内核的管理负担更大。
同时堆是一个连续空间,并且堆内碎片由于没有归还 OS ,如果可重用碎片,再次访问该内存很可能不需产生任何系统调用和缺页中断,这将大大降低 CPU 的消耗。 因此, glibc 的 malloc 实现中,充分考虑了 sbrk 和 mmap 行为上的差异及优缺点,默认分配大块内存 (128k) 才使用 mmap 获得地址空间,也可通过 mallopt(M_MMAP_THRESHOLD, <SIZE>) 来修改这个临界值。malloc系统有自己的内存池管理策略:
1、malloc的时候,检测池中是否有足够内存,有则直接分配,无则从内存中调用brk/mmap函数分配,一般小于等于128k(可设置)的内存,使用brk函数,此时堆向上(有人有的硬件或系统向下)增长,大于128k的内存使用mmap函数申请,此时堆的位置任意,无固定增长方向。
2、free的时候,检测标记是否是mmap申请,是则调用unmmap归还给操作系统,非则检测堆顶是否有大于128k的空间,有则通过brk归还给操作系统,无则标记未使用,仍在glibc的管理下。
glibc为申请的内存存储多余的结构用于管理,因此即使是malloc(0),也会申请出内存(一般16字节,依赖于malloc的实现方式),在应用程序层面,malloc(0)申请出的内存大小是0,因为malloc返回的时候在实际的内存地址上加了16个字节偏移,而c99标准则规定malloc(0)的返回行为未定义。除了内存块头域,malloc系统还有红黑树结构保存内存块信息,不同的实现又有不同的分配策略。频繁直接调用malloc,会增加内存碎片,增加和内核态交互的可能性,降低系统性能。
7、如何查看进程的缺页中断信息?
可通过以下命令查看缺页中断信息
ps -o majflt,minflt -C <program_name> ps -o majflt,minflt -p <pid>
MAJFLT MINFLT 0 27698
其中:: majflt 代表 major fault ,指大错误; minflt 代表 minor fault ,指小错误。
这两个数值表示一个进程自启动以来所发生的缺页中断的次数。
其中 majflt 与 minflt 的不同是:majflt 表示需要读写磁盘,可能是内存对应页面在磁盘中需要load 到物理内存中,也可能是此时物理内存不足,需要淘汰部分物理页面至磁盘中。
参考:
https://github.com/alibaba/tengine/issues/1043
https://blog.csdn.net/origin_lee/article/details/42740535
http://landcareweb.com/questions/20566/jian-cha-gdbzhong-de-c-c-dui-nei-cun-tong-ji-xin-xi
更多相关内容 -
DRAM 内存介绍
2018-08-14 19:24:10SDRAM(Synchronous dynamic random access memory),同步动态随机访问内存,通常包括 SDR (Single Data Rate) SDRAMs以及DDR (Double Data Rate) SDRAMs.在显卡中常...https://www.cnblogs.com/mikewolf2002/archive/2012/11/13/2768804.html
SDRAM(Synchronous dynamic random access memory),同步动态随机访问内存,通常包括 SDR (Single Data Rate) SDRAMs以及DDR (Double Data Rate) SDRAMs.在显卡中常用的是GDDR SDRAMs以及HBM。
如图一所示,左边就是PC系统中常用的内存条,该内存条是双通道2G内存(dual inline Memory Module),通常简称为DIMM。我们可以看到内存条上黑色的128MB内存芯片,这些内存芯片简称为IC。该内存条是双面内存,就是说正反两面都有8个IC,总共16个IC,16*128M=2GB。DIMM的单面称作rank,比如下图的2GB内存条,它就是由rank1,rank2两个单面组成,每个面有8个IC。
图一,DRAM的组成
每个IC内部通常由8个bank组成(DDR3通常为8个bank,GDDR5通常有16个bank),这些bank共享一个memory I/O controller, 但是在每个bank内部的读写可以并行进行。
每个bank内部包括行地址解码器,列地址解码器,传感放大器,以及DRAM内存阵列。如图2所示,这些内存阵列由行列组成,每个行列交叉的单元,表示n bit,通常是8bit或者16位【每一位都是由一个晶体管和一个电容组成,在GDDR5和HBM内存中,通常为32Byte】,表示一个字节或者一个word。bank中的每一行组成一个page,每一行又包括很多列(这儿列是指单个交叉单元)。内存读写的最小单位就是这些交叉单元,通常只有这些单元被放入传感放大器的时候,才能够被读写,所以通常要不断在行和传感放大器之间移动数据。
把一行放入传感放大器称作"activate”,因为这个操作会激活bank。把传感放大器的内容放入行,称作“precharge”。有时候Read或者write的时候会隐含着 precharge的操作,称作AP-read,或者AP-write,AP(auto precharge)。
图二 bank内部结构
在图一中每个bank由16k的page组成,每个page包括1k的列,每列是8bit的byte,所以总共16,384 rows/bank x 1,024 columns addresses/row x 1 byte/column address x 8 stacked banks=128M
对于DDR3,我们通常说它是8n-prefetch(这儿n是指每个rank的bank数目),因为DDR3,每个IC有8个bank,每个bank读取数据的最小单位是8bit,一个byte。每次数据读取request,都会读取8*8bit=64bitdata,而不管这些数据是否都是我们所需要的,比如我们只需要其中的某个byte,但读request会读取8个byte。
如果图三所示, SDRAM读写通常能用一个简单的状态机来描述,它的状态包括idle, active, precharging一个或多个bank。和任何其它状态机一样,从一个状态转换到另一个状态,并在新的状态开始数据操作,都需要一些最小等待时间,这些时延会影响SDRAM读写数据的性能,从而影响整个计算机系统的性能。
SDARM bank中的内存单元行列交叉(通常称作cell )点,用来存储数据,它通常都是一些电容和放大器组成,由于电容的特性,它的电量会随着时间衰减,比如温度等因素都会影响它的衰减速度,所以需要周期性进行加电刷新操作,维持其中的数据。刷新频率通常依赖于内存die的工艺以及cell本身的设计。对内存cell的读写和内存刷新有相同的效果,但是在电容电量衰减到必须刷新之前,并不是所有的内存cell都有读写操作,所以定时刷新仍是需要的。通常刷新操作是按行或者说page进行的,刷新之后,该行cell的电容就会被充电。通常的刷新操作周期是几百clocks到几千clocks。
在刷新命令之前,每个bank必须要先precharged,然后处于idle状态,这需要消耗一个tRP时延(The minimum number of clock cycles required between the issuing of the precharge command and activating a different row within the same bank)。在一个刷新命令完成后,所有的bank处于precharge (idle)状态,在刷新命令和下一个activate命令(ACT)之间cycles数目必须大于等于tRFC(the Row Refresh Cycle Time )。
图三, SDRAM数据传输状态机
由于数据传输时候,都有一定的时延,所以有下面的一些符号描述bank内数据传输的各个阶段时延。
参数
符号
注释
Row Active Time
TRAS
The minimum number of clock cycles required between a bank active command and issuing the precharge command.
Row Address to Column Address Delay
TRCD
The minimum number of clock cycles required between the activation of a row and accessing columns within it.
CAS latency
CL
The time between sending a column address to the memory and the beginning of the data in response. This is the time it takes to read the first bit of memory from a DRAM with the correct row already open.
Row Precharge Time
TRP
The minimum number of clock cycles required between the issuing of the precharge command and activating a different row within the same bank.
Activate to Activate in same bank.
TRC
The minimum number of clock cycles required between the activation of a row activting another row in the same bank.
Burst
The number of data beats in a column access. This is usually 8 for recent DDR3/GDDR5 devices.
SDRAM在响应读写命令之前,bank必须处于激活状态,内存控制器通过发送activate命令,指定被访问的rank,bank以及page(row)。激活一个bank的时间称作tRCD,the Row-Column (or Command) Delay ,它表示激活发送active命令,program控制逻辑以及把内存行列单元读取到传感放大器中以便读写的cycles数目。
bank激活之后,传感放大器中有完整page内容,这个时候,可以发射读写命令,指定从某列开始读写数据。从某个激活的page(放在传感放大器中)中读取一个byte数据消耗的时间称作, the Column Address Strobe (CAS) Latency ,通常间歇位CL 或者tCAS, 它包括在读写接口发送读写命令,program控制逻辑,把传感放大器的内容传输入到输入输出缓冲,并把数据的第一个word放在内存总线上总共消耗的时间。
一个bank每次只能打开一个page(这儿打开是指把page内容放入到传感放大器),对于处于打开状态的page,我们可以进行读写操作,如果不需要再对该page进行读写操作,可以关闭该page, 把该page内容写入bank的行列单元对应的page中,以便对其它page进行读写操作。这个关闭操作通过发射一个Precharge命令实现,precharge命令可以关闭某一个bank,也可以关闭rank中所有打开的bank。
Precharge命令可以和bank中的上一个读写操作进行绑定,从而进行一个组合操作,这时发送一个Read with Auto-Precharge (RDA) 或 Write with Auto-Precharge (WRA)代替单独的读写操作命令。只要满足一定的条件,这将允许SDRAM控制逻辑自动的打开或者关闭bank。需要满足的条件包括:(1) A minimum of RAS Activation Time (tRAS) has elapsed since the ACT command was issued, and (2) a minimum of Read to Precharge Delay (tRTP) has elapse since the most recent READ command was issued。
precharge命令把传感放大器中的数据写入bank中对应的page中,然后DRAM core能够准备下一个数据访问。 precharge一个打开的bank所消耗的时间称作the Row Access Strobe (RAS) Precharge Delay ,通过写作tRP。同一个bank两个activate命令之间所消耗的时间称作tRC,它等于tRAS+tRP。不同bank的ACT命令间隔时间称作the Read-to-Read Delay (tRRD)。
下面的时序图标出了各个阶段时延:
-
JVM堆内存介绍、垃圾收集算法、垃圾回收器汇总
2022-01-11 17:17:07JVM堆内存介绍、垃圾收集算法、垃圾回收器汇总目录
- 1. JAVA堆内存是如何划分的
- 2. 怎么确定哪些对象可以GC?
- 3. JVM GC算法有哪些(GC,Garbage Collection)?
- 3. 垃圾收集器汇总
- 4. Java虚拟机常用的垃圾收集器相关参数
- 5. JDK版本默认垃圾收集器
1. JAVA堆内存是如何划分的
- JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。
- 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
- 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
- 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
1.1 为什么移除永久代?
移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
有了元空间就不再会出现永久代OOM问题了!1.2 分代概念
新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。
老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。- Minor GC : 清理年轻代
- Major GC : 清理老年代
- Full GC : 清理整个堆空间,包括年轻代和永久代
所有GC都会停止应用所有线程。
1.3 为什么分代?
将对象根据存活概率进行分类,对存活时间长的对象,放到固定区,从而减少扫描垃圾时间及GC频率。针对分类进行不同的垃圾回收算法,对算法扬长避短。
1.4 为什么survivor分为两块相等大小的幸存空间?
主要为了解决碎片化。如果内存碎片化严重,也就是两个对象占用不连续的内存,已有的连续内存不够新对象存放,就会触发GC。
1.5 JVM堆内存常用参数
2. 怎么确定哪些对象可以GC?
2.1 引用计数法
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都为 0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。
2.1.1 JAVA中四种引用类型
2.1.1.1 强引用
在 Java 中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即使该对象以后永远都不会被用到 JVM 也不会回收。因此强引用是造成 Java 内存泄漏的主要原因之一。
2.1.1.2 软引用
软引用需要用 SoftReference 类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
2.1.1.3 弱引用
弱引用需要用 WeakReference 类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,总会回收该对象占用的内存。
2.1.1.4 虚引用
虚引用需要 PhantomReference 类来实现,它不能单独使用,必须和引用队列联合使用。虚引用的主要作用是跟踪对象被垃圾回收的状态。
2.2 可达性分析
为了解决引用计数法的循环引用问题,Java 使用了可达性分析的方法。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
2.2.1 根搜索算法
根搜索算法是从离散数学中的图论引入的,程序把所有的引用关系看作一张图,从一个节点GC ROOT开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点。
目前java中可作为GC Root的对象有
1、 虚拟机栈中引用的对象(本地变量表)
2、 方法区中静态属性引用的对象
3、 方法区中常量引用的对象
4、 本地方法栈中引用的对象(Native对象)3. JVM GC算法有哪些(GC,Garbage Collection)?
JVM垃圾收集算法有四种:标记-清除算法、复制算法、标记-整理算法、分代收集算法
红色是标记的非活动对象,绿色是活动对象。
2.1 标记-清除算法
该算法如同它的名字一样,分为两个阶段:标记、清除。首先标记出所有需要回收的对象,然后,统一清除这些被标记的对象。
该算法的缺点是:- 1、效率不高;
- 2、产生大量不连续的内存碎片,导致有大量内存剩余的情况下,由于,没有连续的空间来存放较大的对象,从而触发了另一次垃圾收集动作。
2.2 复制算法
由于标记-清除算法的效率不高,从而提出了复制算法。复制算法将可用的内存分成两样大小的两块,每次只使用其中一块内存。当这块内存用完之后,就把还存活的对象复制到另外一块上面,然后,把这块清空。复制算法克服了标记-清除算法的两个缺点,但是太浪费内存,相当于内存空间减小了一半。
随着时间的积累,现在使用的复制算法的虚拟机,不再是把内存分为1:1的两块。因为98%的对象是寿命很短的,创建之后,很快就被回收了,存活下来的只有2%,所以,用来存储存活对象的内存区,可以小一些。现在的商业虚拟机是把可用内存分为一个较大的Eden空间和两个较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,把Eden和Survivor中的存活对象一次复制到另一块Survivor内存区上,然后把Eden和刚才用过的Survivor空间清空。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,这样,每次新产生的对象可以使用90%的内存空间。 缺点需要两倍的内存空间。2.3 标记-整理算法
从名字可以看出,该算法是对“标记-清除”算法的改进升级版。同样的该算法分为两个阶段:标记、整理。标记阶段同“标记-清除”算法。整理阶段,不是直接对标记对象进行清理,而是让所有存活的对象都移动到一端,然后,直接把边界以外的内存清空。这就解决了“标记-清除”算法会造成大量不连续内存碎片的问题。
2.4 分代收集算法
分代收集算法是根据对象的存活周期的不同,将内存划分为几块。当前的商业虚拟机的垃圾收集都采用了该算法。一般把Java堆分成新生代(年轻代)和老年代(年老代)。这样就可以根据各年代中对象的存活周期来选择最合适的收集算法了。新生代,由于只有少量的对象能存活下来,所以选用“复制算法”,只需要付出少量存活对象的复制成本。老年代,由于对象的存活率高,没有额外的空间分担,就必须使用“标记-清除”或“标记-整理”算法。
3. 垃圾收集器汇总
Java堆内存被划分为新生代和老年代两部分,新生代主要使用复制和标记-清除垃圾回收算法,老年代主要使用标记-整理垃圾回收算法,因此java虚拟中针对新生代和老年代分别提供了多种不同的垃圾收集器,JDK1.6中Sun HotSpot虚拟机的垃圾收集器如下:
图中如果两个垃圾收集器直接有连线,则表明这两个垃圾收集器可以搭配使用。3.1 Serial垃圾收集器(新生代-单线程-复制算法)
Serial是最基本、历史最悠久的垃圾收集器,使用复制算法,曾经是JDK1.3.1之前新生代唯一的垃圾收集器。
Serial是一个单线程的收集器,它不仅仅只会使用一个CPU或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。
Serial垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
3.2 ParNew垃圾收集器(新生代-多线程-复制算法)
ParNew垃圾收集器其实是Serial收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和Serial收集器完全一样,ParNew垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。
ParNew收集器默认开启和CPU数目相同的线程数,可以通过
-XX:ParallelGCThreads
参数来限制垃圾收集器的线程数。ParNew虽然是除了多线程外和Serial收集器几乎完全一样,但是ParNew垃圾收集器是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。
3.3 Parallel Scavenge(新生代-多线程<自适应策略注重吞吐量>-复制算法)
Parallel Scavenge收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU用于运行用户代码的时间/CPU总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。
Parallel Scavenge收集器提供了两个参数用于精准控制吞吐量:
- -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间,是一个大于0的毫秒数。
- -XX:GCTimeRation:直接设置吞吐量大小,是一个大于0小于100的整数,也就是程序运行时间占总时间的比率,默认值是99,即垃圾收集运行最大1%(1/(1+99))的垃圾收集时间。
Parallel Scavenge是吞吐量优先的垃圾收集器,它还提供一个参数:
-XX:+UseAdaptiveSizePolicy
,这是个开关参数,打开之后就不需要手动指定新生代大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、新生代晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数,虚拟机会根据当前系统运行情况收集性能监控信息,动态调整这些参数以达到最大吞吐量,这种方式称为GC自适应调节策略,自适应调节策略也是ParallelScavenge收集器与ParNew收集器的一个重要区别。3.4 Serial Old收集器(老年代-单线程-标记整理算法)
Serial Old是Serial垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client默认的java虚拟机默认的老年代垃圾收集器。
在Server模式下,主要有两个用途:
-
在JDK1.5之前版本中与新生代的Parallel Scavenge收集器搭配使用。
-
作为老年代中使用CMS收集器的后备垃圾收集方案。
3.4.1 新生代Serial与老年代Serial Old搭配垃圾收集过程
新生代Parallel Scavenge收集器与ParNew收集器工作原理类似,都是多线程的收集器,都使用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。3.4.2 新生代Parallel Scavenge/ParNew与老年代Serial Old搭配垃圾收集过程
3.5 Parallel Old收集器(老年代-多线程-标记整理算法)
Parallel Old收集器是Parallel Scavenge的老年代版本,使用多线程的标记-整理算法,在JDK1.6才开始提供。
在JDK1.6之前,新生代使用ParallelScavenge收集器只能搭配老年代的Serial Old收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和老年代Parallel Old收集器的搭配策略。
3.5.1 新生代Parallel Scavenge和老年代Parallel Old收集器搭配运行过程
3.6 Concurrent mark sweep(CMS)收集器(老年代-多线程-标记清除算法)
Concurrent mark sweep(CMS)收集器是一种老年代垃圾收集器,其最主要目标是获取
最短垃圾回收停顿时间
,和其他老年代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验,CMS收集器是Sun HotSpot虚拟机中第一款真正意义上并发垃圾收集器,它第一次实现了让
垃圾收集线程和用户线程同时工作
。CMS工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下4个阶段:
- 初始标记:只是标记一下GC Roots能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
- 并发标记:进行GC Roots跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
- 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
- 并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。
由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。
3.6.1 CMS收集器工作过程
3.6.2 CMS收集器有以下三个不足
-
CMS收集器对CPU资源非常敏感,其默认启动的收集线程数=(CPU数量+3)/4,在用户程序本来CPU负荷已经比较高的情况下,如果还要分出CPU资源用来运行垃圾收集器线程,会使得CPU负载加重。
-
CMS无法处理浮动垃圾(Floating Garbage),可能会导致Concurrent ModeFailure失败而导致另一次Full GC。由于CMS收集器和用户线程并发运行,因此在收集过程中不断有新的垃圾产生,这些垃圾出现在标记过程之后,CMS无法在本次收集中处理掉它们,只好等待下一次GC时再将其清理掉,这些垃圾就称为浮动垃圾。
CMS垃圾收集器不能像其他垃圾收集器那样等待老年代机会完全被填满之后再进行收集,需要预留一部分空间供并发收集时的使用,可以通过参数-XX:CMSInitiatingOccupancyFraction
来设置老年代空间达到多少的百分比时触发CMS进行垃圾收集,默认是68%。
如果在CMS运行期间,预留的内存无法满足程序需要,就会出现一次ConcurrentMode Failure失败,此时虚拟机将启动预备方案,使用Serial Old收集器重新进行老年代垃圾回收。 -
CMS收集器是基于标记-清除算法,因此不可避免会产生大量不连续的内存碎片,如果无法找到一块足够大的连续内存存放对象时,将会触发因此Full GC。CMS提供一个开关参数-XX:+UseCMSCompactAtFullCollection,用于指定在Full GC之后进行内存整理,内存整理会使得垃圾收集停顿时间变长,CMS提供了另外一个参数-XX:CMSFullGCsBeforeCompaction,用于设置在执行多少次不压缩的Full GC之后,跟着再来一次内存整理。
3.7 G1收集器(新老年代-多线程-标记整理算法)
Garbage first垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS收集器,G1收集器两个最突出的改进是:
-
基于标记-整理算法,不产生内存碎片。
-
可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
G1收集器将堆内存划分多个大小相等的独立区域(Region),并且能预测暂停时间,能预测原因它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。
区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。
G1收集器工作工程分为4个步骤,包括:
- 初始标记(Initial Mark)
- 并发标记(Concurrent Mark)
- 最终标记(Final Mark)
- 筛选回收(Live Data Counting and Evacuation)
初始标记与CMS一样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也可以与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运作而导致标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。
4. Java虚拟机常用的垃圾收集器相关参数
5. JDK版本默认垃圾收集器
jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Serial Old(老年代)
jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Serial Old(老年代)
jdk1.9 默认垃圾收集器G1
jdk10 默认垃圾收集器G1
-XX:+PrintCommandLineFlagsjvm
参数可查看默认设置收集器类型-XX:+PrintGCDetails
亦可通过打印的GC日志的新生代、老年代名称判断java虚拟机的
-XX:+PrintGCDetails
参数可以打印垃圾收集器的日志信息。-verbose:gc
可以查看Java虚拟机垃圾收集结果。Over,Enjoy!
-
【内存】ECC内存简介
2020-07-01 00:23:43ECC内存简介。什么是ECC内存
ECC的全称是Error Correction Code,当我们讨论ECC内存时,指的是具有纠错功能的内存。
这里的纠错并不是无限的,比如ECC内存通常能够纠正单比特的错误,但是对于多比特错误通常无法纠正。
ECC的作用
Intel一图流:
ECC的使用
内存的ECC是自动完成的,但是它依赖于CPU和内存本身的支持。
通常使用在服务器或工作站上的CPU,对稳定性的要求更高,会支持ECC内存,比如前面图中的Intel XEON处理器。
ECC内存相比普通内存,会有更多的颗粒:
关于ECC的实现原理,简单来说就是使用更多的冗余来进行纠错,具体的实现可以参考百度或维基百科。
最后,可能还需要在BIOS下设置ECC的开关(不同BIOS设置的方式不同,下面是一个例子):
ECC和非ECC内存对比
一图流:
需要注意的是,可能跟一般的想法有差异,虽然ECC内存更贵,但是它的性能实际上是相比同等级的非ECC内存要差的,这是因为纠错功能需要消耗性能的缘故。
参考
What Is ECC Memory in RAM? A Basic Definition | Tom's Hardware
-
操作系统内存介绍
2015-09-24 14:22:07内存的分类:物理内存、虚拟内存、共享内存,它们分别对应top命令输出中的RES、VIRT、SHR三列。 物理内存 系统的物理内存被划分为许多相同大小的部分,也称作内存页。 内存页的大小取决于CPU的架构和操作系统的... -
【Linux】Linux内存介绍以及C与C++内存管理
2013-06-06 09:45:36c与c++的内存管理进行介绍。Java在需要对象的时候直接new一个出来就好了,使用完之后一般不需要考虑内存释放的问题,GC会负责垃圾回收的善后工作,但是有时候它的回收工作做的并不是万无一失。在C语言中,想使用内存... -
JVM内存区域介绍
2018-02-08 10:28:22写博客的好处是,学过的知识及时记录下来,过段时间可以用来复习。...其中除了堆和方法区是线程共有的,其他几个区域都是线程私有的,下面分别介绍: 1、程序计数器 程序计数器是一块较小的内存,通过这个计数... -
常用内存数据库介绍
2018-07-09 23:18:551. 内存数据库简介 1.1 概念 一、什么是内存数据库 传统的数据库管理系统把所有数据都放在磁盘上进行管理,所以称做磁盘数据库(DRDB:Disk-Resident Database)。磁盘数据库需要频繁地访问磁盘来进行数据的操作,... -
电脑内存条如何选择 电脑内存条选择方法【详细介绍】
2021-07-27 01:31:28电脑内存是电脑的重要配件之一,由内存芯片颗粒,电路板,以及金手指等部件组成。而其中内存颗粒直接决定内存条成本,因此它的好坏也影响着内存条的性能好坏。 而现在高端内存三大产商,镁光,三星和海力士。那对于... -
虚拟内存介绍及MMU工作原理(一)
2012-03-22 20:35:54在linux,包括很多操作系统中,虚拟内存的概念十分重要。在使用windows的过程中,我们经常会遇到“虚拟内存不足”的提示,linux中有个所谓的swap分区,包括linux和uClinux的最大区别,就是有没有MMU。那么,究竟什么... -
电脑4g内存够用吗-电脑4g内存是否够用详情介绍 - 系统家园
2021-05-26 00:47:17小编整理了一下电脑4g内存是否够用的详情介绍,一起来看看吧~电脑4g内存够用吗:一、够用1、电脑内存是否够用取决于电脑的用途是什么的,如果只是简单的上网聊天,4g内存是足够的。二、不一定够用1、如果您是64位... -
optee的共享内存的介绍
2020-08-20 20:10:24share memory是一块内存区域, 用于non-secure world和secure world的通信. optee支持两种share memory: 连续的buffer,非连续的buffer optee是按照buffer来管理共享内存的,而不是按照pool来管理的. 每一个buf需要... -
linux swap 内存交换分区 详细介绍
2020-10-06 20:00:39Linux内存管理是一套非常复杂的系统,而swap只是其中一个很小的处理逻辑。 希望本文能让读者了解Linux对swap的使用大概是什么样子。阅读完本文,应该可以帮你解决以下问题: 1、swap到底是干嘛的? 2、swappiness... -
optee的内存管理的详细介绍
2020-09-22 20:37:49文章目录3.3、optee的内存管理3.3.1 optee的内存layout3.3.2 optee内存管理1、 相关宏的介绍(1)、__register_memory 注册一般的内存(2)、register_sdp_mem 注册sdp内存(3)、CFG_MMAP_REGIONS2、 内存Type3、 内存的... -
怎么给笔记本升级内存和硬盘 联想Y7000P加装内存和硬盘图文介绍
2021-02-05 21:35:33一般目前新买的笔记本电脑,大都是标配8GB内存和单块...下面以联想Y7000P笔记本为例,详细介绍下笔记本升级加装内存和硬盘方法,其它品牌型号产品方法大都也是如此。联想Y7000P加装内存和硬盘图文介绍:笔记本电脑笔... -
Java内存分析工具MAT(Memory Analyzer Tool)的介绍与使用
2021-06-25 08:45:22详细介绍了Java内存分析工具MAT(Memory Analyzer Tool)的常见使用方法,MAT可以帮助Java程序员快速进行内存分析,定位问题。 -
以太坊内存池mempool及作用介绍
2021-12-13 12:30:27一、什么是内存池 内存池是一组以太坊交易,这些交易已经广播到网络并等待被挖掘成一个区块。内存池服务提供对交易生命周期的直接洞察。交易被用户广播到节点后,在内存池中等待。从那里,交易将被挖掘到一个区块... -
C语言内存模型详细介绍_堆栈介绍
2018-05-22 18:31:28(命令行参数区其实就是在通过dos或shell脚本调用时传递的参数,比如:a.exe 123 123)上图是C语言内存模型,其实虽然说叫C语言内存模型,其实并不是叫C语言内存模型,而是C语言根据CPU处理器搭建出来的一个模型!... -
memtest86内存测试工具介绍
2020-02-03 16:01:05memtest86工具,如名字所述,就是X86平台下的测试内存的工具。 可以在官方网站下载到免费版本: https://www.memtest86.com/download.htm Free是免费版本,与PRO收费版本相比,测试效果没有任何折扣,仅仅不能... -
虚拟内存与物理内存的联系与区别
2018-08-30 11:55:36操作系统有虚拟内存与物理内存的概念。在很久以前,还没有虚拟内存概念的时候,程序寻址用的都是物理地址。程序能寻址的范围是有限的,这取决于CPU的地址线条数。比如在32位平台下,寻址的范围是2^32也就是4G。并且... -
JVM五大内存区域介绍
2019-07-11 11:40:21程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。 那么计数器记录虚拟机字节码指令的地址。如果为native【底层方法】,那么计数器为空。 这块内存区域是虚拟机规范中唯一没有... -
SAP内存和ABAP内存的简单介绍说明
2017-08-28 15:24:32前者的值是放在SAP内存,后者是放在ABAP内存。这两种内存的主要的区别就是作用范围了,SAP内存是对当前登录所有会话都可以访问到的,除非用户退出系统不然在整个登录期间都有效。ABAP内存只是针对一个会话,你用哪个... -
内存类型有哪些
2021-07-25 01:04:00内存大家都了解,那么,内存又分哪几种类型呢?大家都不是很清楚吧,没关系,学习啦小编在这里给大家讲述内存有什么类型。服务器内存服务器是企业信息系统的核心,因此对内存的可靠性非常敏感。服务器上运行着企业的... -
共享内存特点介绍。
2018-05-09 14:33:50本文根据如下几个问题来介绍共享内存的使用方法,共享内存的使用实现原理?然后共享内存段被映射进进程空间之后,存在于进程空间的什么位置?共享内存段最大限制是多少?根据这三个问题来介绍共享内存的使用信息。不... -
【高频内存与主板内存频率和CPU内存频率之间的关系】
2020-12-26 15:46:46很多小白经常问我说我的CPU标着内存频率支持2400啊,那我买3200是不是浪费了,还有的说我主板内存最高支持4000+啊,那我是不是要买4000+的内存啊。总之小白们都不清楚高频内存的频率到底是由谁决定的,又由谁限制,... -
关于free命令 内存的详细介绍以及如何手动释放内存
2018-09-29 19:37:08free命令可以显示当前系统未使用的和已使用的内存数目,还可以显示被内核使用的内存缓冲区。 total:去掉为硬件和操作系统保留的内存后剩余的内存总量。许多人奇怪自己的电脑安装了一共8G的内存,但是显示总共... -
Node中内存溢出的解决办法
2019-07-23 00:57:14趁着遇到这个问题,自己也翻了翻了关于node的内存管理,在这里做个总结以及遇到这种问题应该如何解决。 先说一下node的一些背景,大家都知道node和v8是分不开的。v8作为node的JavaScript的脚本引擎,node在享受v8的... -
Java内存区域介绍(附带JDK1.8后方法区的变化)
2019-07-13 13:51:23Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。根据《Java虚拟机规范(JavaSE7版)》的规定,java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图所示 程序计数器... -
Linux系统命令 - 查看内存使用情况
2020-04-29 23:55:08下面介绍查看内存使用情况的相关命令。 包括物理内存(RAM)和交换内存(swap)。 我们经常需要检查服务器的内存使用情况,以及每个进程占用的内存,避免发生资源短缺进而影响用户体验。 对于网站和Web服务,必须具有...