内存结构图
内存分类
- 代码段test:存放可执行文件的操作指令,进程只读
- 数据段data: 已初始化的全局变量
- BSS段: 程序中未初始化的全局变量,初始值置为0
- heap: 动态分配的内存段
- stack: 栈空间,每个进程大小是有限的,
- mmap:映射空间,空间有限,在stack和heap之间的,
如图所示: 还可以看到heap 和stack 的增长的方向,注意一下右边还有一个 random brk offset, random mmap offset, rangdom stack offset, 这些是随机的,因为进程映射内存的时候,地址都是给的虚拟地址,random 这些offset,能够防止c指针的随便指向,导致安全问题.
java 内存结构
- java stack, 线程私有,程序计数器,本地方法栈,栈帧( 操作数栈,本地变量表,动态链接栈,环境栈,局部变量表,和下面寄存器对应)
-
heap,类对象实例
-
method方法区, 类定义,常量池,hotspot 改为永久区
-
四个寄存器 程序计数器(pc),optop 操作栈寄存器,lvars 本地变量表,envment 栈环境寄存器
和linux 内存对应关系
-
用户内存
代码区 和 数据区 是一样的,
代码区存放执行代码,只读,
数据区存放全局变量
栈区是 进程栈 但是jvm里面是线程栈
堆区是区别最大的,也是为啥jvm可以管理内存,申请一段连续空间,由用户态自行管理, -
内核内存
java nio 实现对内核态数据的映射,sendFile,mmap sync等,
内存操控
void *malloc(size_t size): 分配内存,返回内存头指针
void free(void *pointer): 释放内存
void *calloc (size_t num_elements,size_t element_size):分配内存,但是会将内存初始化
void realloc(void *ptr,size_t new_size): 将已经分配好的内存,重新调整内存大小,用于伸缩内存
void *memcpy(void *dst, void const *src, size_t length);
memcpy()函数从src的起始位置复制length个字节到dest的内存起始位,memcpy( )函数 并不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。
void *memmove(void *dst, void const *src,size_t length); 在memcpy 中如果dest 是src的话,会导致复制重复,memmove 会考虑此问题,从头尾复制
void *memset(void *a, int ch, size_t length); 将从a开始的length长度的值设为ch,一般用来初始化字符串数组
heap 申请
大家可以搜一下linux 对堆的申请的历史,里面也是从进程到多线程的一个演进,同时还有一些问题的解决思路.
ptmalloc2 arena 24,
ptmalloc2 是为了增加多线程操作
https://blog.csdn.net/maokelong95/article/details/51989081
malloc 介绍链接
- 堆
动态内存区,代码申请,malloc free操作 -
栈
编译器自动分配释放,连续内存空间 -
brk(),sbrk()
对 heap区数据的program break 的地址进行修改,用来扩展和缩小堆区数据,brk() 复位地址,当数据小于0x20ff8 可以通过sbrk 增加地址来申请空间,,大于此值就用mmap 来直接映射,
但是sbrk的使用,要在复用heap后,大部分我们使用的是malloc和free函数来分配和释放内存,
当里面的dunck不够后,才使用sbrk来扩展heap,来生成新的dunck
内存管理:free 不会立刻调用brk 释放,因为类似缓存池,free的内存会合并生成新的空闲的chunk,可以下次malloc使用。
mmap的申请chunk 使用munmap 释放,sbrk申请的,得看一下堆顶下面的的chunk是否大于128kB,如果大于brk 释放dunck,小于不释放(内存空洞),
maps
内存映射
https://www.jianshu.com/p/8203457a11cc
- linux 会为每个进程建立内存映射表,此为虚拟内存,里面是整个内存的地址空间从起始到结束,
进程访问访问数据的时候,会查页表,页表没有数据产生缺页中断,mmu从磁盘读取数据到内存中。
物理内存, 虚拟内存,磁盘,
虚拟内存的好处:主要是缺页中断,可以保证代码等部分加载,减少内存占用.
1. 基于映射,可以让进程认为是连续空间,但是实际物理地址是碎片话的,充分利用碎片
2. 多进程可以映射同一份代码段或者常量段,子进程的coap on write queue
3. 内存布局一致,可以充分使用链接
4. mmap
5. 用户内存延时分配
可以看这篇博文来总体看优缺点: https://www.cnblogs.com/jingyg/p/5069964.html
从上面的内存映射,我们可以看出,对于应用程序来说malloc 等申请的heap 在虚拟内存是连续的. 但是对于系统来说通过映射关系在物理内存可能是不连续,可能会跨页啥的.
线程与进程内存上的分配
linux 上是以进程来分配资源的,而线程是在linux上为轻量级进程,也有内存结构,但是在很多地方都是用的一份,比如下面的arena 和chunk 上来保证来多线程下堆分配问题.
arena
- arena
arena 数量和cpu核数有关系
main arean, arean 限制区,
– 竞争临界区,
main thread 竞争环境
chunk
https://www.jianshu.com/p/2fedeacfa797
https://introspelliam.github.io/2017/09/10/pwn/Linux%E5%A0%86%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E4%B8%8A/
- 结构
在heap 中,有个bins 数组 长度128,每个bins[index] 都是一个双向链表,里面都是chunk,chunk长度为index8 ,因此如果要申请内存就先根据长度找到index,然后遍历双向链表找chunk就行。
bins[0]没有用,bins[1]是sorted,bins[2]–bins[63]为smallbins,最大的chunk就是638
bins[64,127)是large_bin 最小就是512 ,所以small 和large 的分界线就是根据chunk是否是512。
在上面的结构中查找就很简单的:
index=申请内存大小/8
1. 如果内存大小<512,
就去small 找,smallbins[index]如果不为空就返回第一个,为空到第三步
2. 大于512 到largebins找,largebins[index] 如果不为空就找一个合适的chunk,将剩余内存返回到unsorted_list ,否则去3. unsorted_list,遍历list
3. 遍历unsorted_list,
- 最适伙伴算法
发表回复