Linux内存基础知识
2020-12-21 10:11:03 1 举报
AI智能生成
Linux基础知识、JVM内存、Linux进程、JVM进程、用户虚拟地址空间、内核虚拟地址空间
作者其他创作
大纲/内容
技术发展流程
虚拟地址空间
为了在多进程环境下,使得进程之间的内存地址不受影响,相互隔离,于是操作系统就为每个进程独立分配一套的虚拟地址空间,
每个程序只关心自己的虚拟地址就可以,实际上大家的虚拟地址都是一样的,但分布到物理地址内存是不一样的。作为程序,也不用关心物理地址的事情。
每个程序只关心自己的虚拟地址就可以,实际上大家的虚拟地址都是一样的,但分布到物理地址内存是不一样的。作为程序,也不用关心物理地址的事情。
内存交换(Swap)技术
每个进程都有自己的虚拟空间,而物理内存只有一个,所以当启用了大量的进程,物理内存必然会很紧张,于是操作系统会通过内存交换技术,把不常使用的内存暂时存放到硬盘(换出),在需要的时候再装载回物理内存(换入)。
内存映射
背景
那既然有了虚拟地址空间,那必然要把虚拟地址「映射」到物理地址,这个事情通常由操作系统来维护。
那么对于虚拟地址与物理地址的映射关系,可以有分段和分页的方式,同时两者结合都是可以的。
那么对于虚拟地址与物理地址的映射关系,可以有分段和分页的方式,同时两者结合都是可以的。
分段
内存分段是根据程序的逻辑角度,分成了栈段、堆段、数据段、代码段等,这样可以分离出不同属性的段,同时是一块连续的空间。
但是每个段的大小都不是统一的,这就会导致内存碎片和内存交换效率低的问题。
但是每个段的大小都不是统一的,这就会导致内存碎片和内存交换效率低的问题。
分页
于是,就出现了内存分页,把虚拟空间和物理空间分成大小固定的页,如在 Linux 系统中,每一页的大小为 4KB。由于分了页后,就不会产生细小的内存碎片。同时在内存交换的时候,写入硬盘也就一个页或几个页,这就大大提高了内存交换的效率。
多级页表&TLB
再来,为了解决简单分页产生的页表过大的问题,就有了多级页表,它解决了空间上的问题,但这就会导致 CPU 在寻址的过程中,需要有很多层表参与,加大了时间上的开销。于是根据程序的局部性原理,在 CPU 芯片中加入了 TLB,负责缓存最近常被访问的页表项,大大提高了地址的转换速度。
Linux进程与JVM关系
虚拟地址空间分布图
JVM本质
它就是一个持有JVM实例的Linux进程
Linux进程分区
前题:每个进程都有用户地址空间和内核地址空间,每个进程只能直接访问用户地址空间,不能直接访问内核地址空间,
要想访问,需要通过系统调用。
要想访问,需要通过系统调用。
用户空间虚拟地址分区:
- 代码区中存放应用程序的机器代码,运行过程中代码不能被修改,具有只读和固定大小的特点。
- 数据区中存放了应用程序中的全局数据,静态数据和一些常量字符串等,其大小也是固定的。
- 堆是运行时程序动态申请的空间,属于程序运行时直接申请、释放的内存资源。
- 栈区用来存放函数的传入参数、临时变量,以及返回地址等数据。
- 未使用区是分配新内存空间的预备区域。
JVM进程分区
如图所示,有几点要重点说明下:
1. 上图特别强调了JVM进程模型的代码区和数据区指的是JVM自身的,而非Java程序的。普通进程栈区,在JVM一般仅仅用做线程栈。
2. JVM的堆区和普通进程的差别是最大的
1. 上图特别强调了JVM进程模型的代码区和数据区指的是JVM自身的,而非Java程序的。普通进程栈区,在JVM一般仅仅用做线程栈。
2. JVM的堆区和普通进程的差别是最大的
内核内存虚拟地址空间
应用程序通常不直接和内核内存打交道,内核内存由操作系统进行管理和使用;不过随着Linux对性能的关注及改进,一些新的特性使得应用程序可以使用内核内存,或者是映射到内核空间。Java NIO正是在这种背景下诞生的,其充分利用了Linux系统的新特性,提升了Java程序的IO性能。
JVM进程比普通Linux进程区别是什么?
1. 普通进程在运行时给内存对象分配空间时,比如C++执行new操作时,会触发一次分配内存空间的系统调用,由操作系统的线程根据对象的大小分配好空间后返回;同时,程序释放对象时,比如C++执行delete操作时,也会触发一次系统调用,通知操作系统对象所占用的空间已经可以回收。
2.JVM对内存的使用和一般进程不同。JVM向操作系统申请一整段内存区域(具体大小可以在JVM参数调节)作为Java程序的堆(分为新生代和老年代);当Java程序申请内存空间,比如执行new操作,JVM将在这段空间中按所需大小分配给Java程序,并且Java程序不负责通知JVM何时可以释放这个对象的空间,垃圾对象内存空间的回收由JVM进行。
3.未使用区是分配新内存空间的预备区域。对于普通进程来说,这个区域被可用于堆和栈空间的申请及释放,每次堆内存分配都会使用这个区域,因此大小变动频繁;对于JVM进程来说,调整堆大小及线程栈时会使用该区域,而堆大小一般较少调整,因此大小相对稳定。操作系统会动态调整这个区域的大小,并且这个区域通常并没有被分配实际的物理内存,只是允许进程在这个区域申请堆或栈空间。
JVM的Heap Memory和Native Memory
基础认知
一个进程到底占了多少内存,都占在了哪里,上面的图已经揭示了一个内存所在的虚拟地址空间,当然每个进程拥有相同的虚拟地址空间,一个进程所产生的虚拟地址址必然全部在这个虚拟地址空间里,如果不在这里,CPU也访问不到啊。结论就是:一个进程所占用的物理内存 ≈ 这个进程虚拟地址空间所映射的物理内存,为什么是『≈』,因为内核空间映射的一些Native库属于很多进程共享库,并不是该进程独享 ,所以不能都算作是这个进程的。
Heap Memory
是供Java应用程序使用的,在JVM生成时会申请一块连续内存区域
Native Memory
- 管理java heap的状态数据(用于GC);
- JNI调用,也就是Native Stack;
- JIT(即便编译器)编译时使用Native Memory,而且JIT的输入(Java字节码)和输出(可执行代码)也都是保存在Native Memory;
- NIO direct buffer。 对于IBM JVM和Hotspot,均可以经过-XX:MaxDirectMemorySize来设置nio直接缓冲区的最大值。默认是64M。
超过这个时,会按照32M自动增大。 - 对于IBM的JVM某些版本实现,类加载器和类信息都是保存在Native Memory中的。
为何会内存溢出?
简单理解java进程内存 = java heap + native memory。所以内存溢出时,首先要区分是堆内存溢出仍是本地内存溢出。Native Memory本质上就是由于耗尽了进程地址空间或者真的物理空间不够了。对于HotSpot JVM来书,不断的分配直接内存,会致使以下错误信息:Allocated 1953546760 bytes of native memory before running outlinux。
如果是普通Java Heap溢出,就是内存不够用了,因为JVM比普通进程多做的事情之一就是在进程生成时就去申请一大块连续的内存,这个大小是由JVM里的参数设置决定的。
如果是普通Java Heap溢出,就是内存不够用了,因为JVM比普通进程多做的事情之一就是在进程生成时就去申请一大块连续的内存,这个大小是由JVM里的参数设置决定的。
基础概念
虚拟地址空间
用户空间虚拟地址
内核空间虚拟地址
进程隔离与共享
不同进程间的用户空间是完全隔离的,内核空间是共享的
从实现角度而言,“隔离”的意思是不同进程的页表不同,“共享”的意思是不同进程的页表相同,仅此而已。
我们知道,页表反映的是虚拟地址和物理地址的映射关系。
从实现角度而言,“隔离”的意思是不同进程的页表不同,“共享”的意思是不同进程的页表相同,仅此而已。
我们知道,页表反映的是虚拟地址和物理地址的映射关系。
由于页表不同,因此最终映射的物理页也不同,这就是所谓的“进程隔离”。
而由于A/B进程的内核空间使用了同一张页表,所以只要他们使用相同的虚拟地址(位于内核空间),那么必然访问到同一个物理页。
而由于A/B进程的内核空间使用了同一张页表,所以只要他们使用相同的虚拟地址(位于内核空间),那么必然访问到同一个物理页。
0 条评论
下一页