本文转载自美团技术团队发表的同名文章
https://tech.meituan.com/linux-jvm-memory.html
一, linux与进程内存模型
要理解jvm最重要的一点是要知道jvm只是linux的一个进程,把jvm的视野放大,就能很好的理解JVM细分的一些概念
下图给出了硬件系统进程三个层面内存之间的关系.
从硬件上看,Linux系统的内存空间由两个部分构成:物理内存和SWAP(位于磁盘)。物理内存是Linux活动时使用的主要内存区域;当物理内存不够使用时,Linux会把一部分暂时不用的内存数据放到磁盘上的SWAP中去,以便腾出更多的可用内存空间;而当需要使用位于SWAP的数据时,必须先将其换回到内存中。
从Linux系统上看,除了引导系统的BIN区,整个内存空间主要被分成两个部分:内核内存(Kernel space)、用户内存(User space)。
内核内存是linux自身使用的内存空间,主要提供程序调度,内存分配,链接硬件资源等程序逻辑使用.用户内存是提供给各个进程主要空间,linux给每个进程提供4G相同的虚拟内存空间;这种虚拟内存空间,是一种寻址的空间,进程之间都有相互独立的4G空间地址,实际用到多少,最终会通过页表写入真正的磁盘.因此进程之间也是相互独立的,互不干扰
虚拟内存空间分配如下,
从进程角度看进程能直接访问的用户内存被划分为五个部分(与jvm的内存划分类似,因为它也是linux的一个进程):代码区,数据区,堆区,栈区,未使用区,
代码区中存放应用程序的机器代码,运行过程中代码不能被修改,具有只读和固定大小的特点
数据区中存放应用程序的机器代码静态数据和一些常量字符串等,其大小也是固定的,
堆是运行时程序动态申请的空间,属于程序运行时直接申请释放的内存资源(jvm的GC也主要回收该部分空间)
栈区用来存放函数的传入参数,临时变量(jvm中的方法使用的栈帧),以及返回地址等数据,
二,进程与jvm内存模型
jvm本质是一个进程,因此其内部也有进程的一般特点,但是jvm不是一个普通的进程,其内存模型有一些新特点:1,jvm将许多本来属于操作系统管理范畴的东西,移植到了jvm内部,目的在于减少系统调用的次数;2,JavaNIO,目的在于减少用于读写IO的系统调用的销,jvm进程与普通进程内存模型比较图:
需要说明的是,这个模型的并不是JVM内存使用的精确模型,更侧重于从操作系统的角度而省略了一些JVM的内部细节(尽管也很重要)。下面从用户内存和内核内存两个方面讲解JVM进程的内存特点。
1,用户内存
上图特别强调了JVM进程模型的代码区和数据区指的是JVM自身的,而非Java程序的。普通进程栈区,在JVM一般仅仅用做线程栈。JVM的堆区和普通进程的差别是最大的,下面具体详细说明
首先是永久代,永久代本质上是Java程序的代码区和数据区,Java程序中类(class),会被加载整个区域的不同数据结构中去,包括常量池,域,方法数据,方法体,构造函数,以及类中的专用方法,实例初始化,接口初始化.永久代对于操作系统来说是堆的一部分;而对于Java程序来说,这是容纳程序本身及静态资源的空间
其次是新生代和老年代,新生代和老年代才是Java程序真正使用的堆空间,主要用于内存对象的存储;但是其管理方式和普通进程有本质的区别。
普通进程在运行时给内存对象分配空间时,比如C++执行new操作时,会触发一次分配内存空间的调用,由操作系统的线程根据对象的大小分配好空间后返回;同时程序释放对象时,比如c++执行delete操作时也会触发一次系统调用,通知操作系统对象所占用的空间可以回收了
jvm对于内存的使用和一般进程不同,jvm像操作系统申请一整段内存区域(具体大小可以在jvm参数调节)作为Java内存的堆(分为新生代和老年代);当Java程序申请内存空间,比如new操作时,jvm将在这段空间中按所需大小分配给Java程序,并且Java不负责通知回收该内存空间.
JVM的内存管理方式的优点是显而易见的,包括:第一,减少系统调用的次数,JVM在给Java程序分配内存空间时不需要操作系统干预,仅仅在Java堆大小变化时需要向操作系统申请内存或通知回收,而普通程序每次内存空间的分配回收都需要系统调用参与;第二,减少内存泄漏,普通程序没有(或者没有及时)通知操作系统内存空间的释放是内存泄漏的重要原因之一,而由JVM统一管理,可以避免程序员带来的内存泄漏问题。
最后是未使用区,未使用区是分配新内存空间的预备区域。对于普通进程来说,这个区域被可用于堆和栈空间的申请及释放,每次堆内存分配都会使用这个区域,因此大小变动频繁;对于JVM进程来说,调整堆大小及线程栈时会使用该区域,而堆大小一般较少调整,因此大小相对稳定。操作系统会动态调整这个区域的大小,并且这个区域通常并没有被分配实际的物理内存,只是允许进程在这个区域申请堆或栈空间。
2.内核内存
应用程序通常不直接和内核内存打交道,内核内存由操作系统进行管理和使用;不过随着linux对性能的关注及改进,一些新的特性使得应用程序可以使内核内存或者映射到内核空间.javaNIO正是在这种背景下诞生的,其充分利用了linux系统的新特性,提升了Java程序的IO性能
上图给出了NIO使用的内核内存在linux系统中的分布情况,NIObuffer主要包括:NIO使用各种channel时所使用的ByteBuffer,Java程序主动使用ByteBuffer,allocationDirector申请分配的Buffer.而在PageCache里面,NIO使用的内存主要包括FileChannel.map方式打开文件占用mapped,FileChanneltransferTo和FileChannel.transferFRrom
Linux和JavaNIO在内核内存上开辟空间给程序使用,主要是减少不必要的复制,减少IO操作系统调用的开销.例如将磁盘文件数据发送到网卡,使用普通方法和NIO时,数据流动比较
将数据在内核内存和用户内存之间拷贝是比较消耗资源和时间的事情,而从上图我们可以看到,通过NIO的方式减少了2次内核内存和用户内存之间的数据拷贝。这是Java NIO高性能的重要机制之一(另一个是异步非阻塞)。
从上面可以看出,内核内存对于Java程序性能也非常重要,因此,在划分系统内存使用时候,一定要给内核留出一定可用空间。
https://mp.weixin.qq.com/s?__biz=MzI4NTEzMjc5Mw==&mid=2650554698&idx=1&sn=7f08e278c1f6a7c0db0ba5418c442fa4&chksm=f3f833dcc48fbacaede8af14925b983f871e834e58ba079c3cb41557bd5fa5596817fcab1543#rd