nars官网,深化JVM 与 Linux 的内存联系-优德88手机网址

admin7个月前372浏览量

来历:Java技能栈

链接:https://mp.weixin.qq.com/s/daeEBytec7incqjyQbrsyw

在一些物理内存为8g的服务器上,首要运转一个Java服务,体系内存分配如下:Java服务的JVM堆巨细设置为6g,一个监控进程占用大约 600m,Linux自身运用大约800m。

从表面上,物理内存应该是满足运用的;但实践运转的状况是,会发作很多运用SWAP(阐明物理内存不行运用 了),如下图所示。因为SWAP和GC一起发作会致使JVM严峻卡顿,所以咱们要诘问:内存终究去哪儿了?

要剖析这个问题,了解JVM和操作体系之间的内存联系非常重要。接下来首要就Linux与JVM之间的内存联系进行一些剖析。

一、Linux与进程内存模型

JVM以一个进程(Process)的身份运转在Linux体系上,了解Linux与进程的内存联系,是了解JVM与Linux内存的联系的根底。下图给出了硬件、体系、进程三个层面的内存之间的概要联系。

从硬件上看,Linux体系的内存空间由两个部分构成:物理内存和SWAP(坐落磁盘)。物理内存是Linux活动时运用的首要内存区域;当物理内存不行运用时,Linux会把一部分暂时不必的内存数据放到磁盘上的SWAP中去,以便腾出更多的可用内存空间;而当需求运用坐落SWAP的数据时,有必要 先将其换回到内存中。JVM运转时区域详解,引荐咱们看下。

从Linux体系上看,除了引导体系的BIN区,整个内存空间首要被分红两个部分:内核内存(Kernel space)、用户内存(User space)。

内核内存是Linux自身运用的内存空间,首要供给给程序调度、内存分配、衔接硬件资源等程序逻辑运用。

用户内存是供给给各个进程首要空间,Linux给各个进程供给相同的虚拟内存空间;这使得进程之间彼此独立,互不搅扰。完成的办法是选用虚拟内存技能:给每一个进程必定虚拟内存空间,而只有当虚拟内存实 际被运用时,才分配物理内存。

如下图所示,关于32的Linux体系来说,一般将0~3G的虚拟内存空间分配做为用户空间,将3~4G的虚拟内存空间分配 为内核空间;64位体系的区分状况是相似的。

从进程的视点来看,进程能直接拜访的用户内存(虚拟内存空间)被区分为5个部分:代码区、数据区、堆区、栈区、未运用区。

代码区中寄存应用程序的机器代码,运转进程中代码不能被修正,具有只读和固定巨细的特色。

数据区中寄存了应用程序中的大局数据,静态数据和一些常量字符串等,其巨细也是固定的。

堆是运转时程序动态请求的空间,归于程序运转时直接请求、开释的内存资源。

栈区用来寄存函数的传入参数、暂时变量,以及回来地址等数据。

未运用区是分配新内 存空间的准备区域。

二、进程与JVM内存空间

JVM实质便是一个进程,因而其内存空间(也称之为运转时数据区,留意与JMM的差异)也有进程的一般特色。关于JVM内存办理,可参阅如下文章:

技能进阶:搞透JVM内存结构 & Java内存模型& Java目标模型

教育笔记:5分钟让你了解Java内存模型

可是,JVM又不是一个一般的进程,其在内存空间上有许多簇新的特色,首要原因有两 个:

1.JVM将许多原本归于操作体系办理领域的东西,移植到了JVM内部,意图在于削减体系调用的次数;

2. Java NIO,意图在于削减用于读写IO的体系调用的开支。JVM进程与一般进程内存模型比较如下图:

需求阐明的是,这个模型的并不是JVM内存运用的准确模型,更侧重于从操作体系的视点而省掉了一些JVM的内部细节(虽然也很重要)。下面从用户内存和内核内存两个方面解说JVM进程的内存特色。

1.用户内存

上图特别强调了JVM进程模型的代码区和数据区指的是JVM自身的,而非Java程序的。一般进程栈区,在JVM一般只是用做线程栈。JVM的堆区和一般进程的差别是最大的,下面详细详细阐明:

首先是永久代。永久代实质上是Java程序的代码区和数据区。Java程序中类(class),会被加载到整个区域的不同数据结构中去,包含常量池、域、办法数据、办法体、结构函数、以及类中的专用办法、实例初始化、接口初始化等。这个区域关于操作体系来说,是堆的一个部分;而关于Java程序来 说,这是包容程序自身及静态资源的空间,使得JVM能够解说履行Java程序。

其次是新生代和老时代。新生代和老时代才是Java程序真实运用的堆空间,首要用于内存目标的存储;可是其办理办法和一般进程有实质的差异。

一般进程在运转时给内存目标分配空间时,比方C++履行new操作时,会触发一次分配内存空间的体系调用,由操作体系的线程依据目标的巨细分配好空间后返 回;一起,程序开释目标时,比方C++履行delete操作时,也会触发一次体系调用,告诉操作体系目标所占用的空间现已能够收回。

JVM对内存的运用和一般进程不同。JVM向操作体系请求一整段内存区域(详细巨细能够在JVM参数调理)作为Java程序的堆(分为新生代和老时代);当Java程序请求内存空间,比方履行new操作,JVM将在这段空间中按所需巨细分配给Java程序,而且Java程序不担任告诉JVM何时能够开释这 个目标的空间,废物目标内存空间的收回由JVM进行。

JVM的内存办理办法的长处是清楚明了的,包含:榜首,削减体系调用的次数,JVM在给Java程序分配内存空间时不需求操作体系干涉,只是在 Java堆巨细变化时需求向操作体系请求内存或告诉收回,而一般程序每次内存空间的分配收回都需求体系调用参加;第二,削减内存走漏,一般程序没有(或许 没有及时)告诉操作体系内存空间的开释是内存走漏的重要原因之一,而由JVM统一办理,能够防止程序员带来的内存走漏问题。

最终是未运用区,未运用区是分配新内存空间的准备区域。关于一般进程来说,这个区域被可用于堆和栈空间的请求及开释,每次堆内存分配都会运用这个区 域,因而巨细变化频频;关于JVM进程来说,调整堆巨细及线程栈时会运用该区域,而堆巨细一般较少调整,因而巨细相对安稳。操作体系会动态调整这个区域的 巨细,而且这个区域一般并没有被分配实践的物理内存,只是答应进程在这个区域请求堆或栈空间。

2.内核内存

应用程序一般不直接和内核内存打交道,内核内存由操作体系进行办理和运用;不过跟着Linux对功能的重视及改善,一些新的特性使得应用程序能够使 用内核内存,或许是映射到内核空间。Java NIO正是在这种布景下诞生的,其充分利用了Linux体系的新特性,提升了Java程序的IO功能。

上图给出了Java NIO运用的内核内存在linux体系中的散布状况。nio buffer首要包含:nio运用各种channel时所运用的ByteBuffer、Java程序自动运用 ByteBuffer.allocateDirector请求分配的Buffer。

而在PageCache里边,nio运用的内存首要包 括:FileChannel.map办法翻开文件占用mapped、FileChannel.transferTo和 FileChannel.transferFrom所需求的Cache(图中标明 nio file)。

经过JMX能够监控到NIO Buffer和 mapped 的运用状况,如下图所示。不过,FileChannel的完成是经过体系调用运用原生的PageCache,进程关于Java是通明的,无法监控到这部分内存的运用巨细。

Linux和Java NIO在内核内存上拓荒空间给程序运用,首要是削减不要的仿制,以削减IO操作体系调用的开支。例如,将磁盘文件的数据发送网卡,运用一般办法和NIO时,数据活动比较下图所示:

将数据在内核内存和用户内存之间复制是比较耗费资源和时刻的工作,而从上图咱们能够看到,经过NIO的办法削减了2次内核内存和用户内存之间的数据复制。这是Java NIO高功能的重要机制之一(另一个是异步非堵塞)。

从上面能够看出,内核内存关于Java程序功能也非常重要,因而,在区分体系内存运用时分,必定要给内核留出必定可用空间。

三、事例剖析

1.内存分配问题

经过上面的剖析,省掉比较小的区域,能够总结JVM占用的内存:

JVM内存 ≈ Java永久代 + Java堆(新生代和老时代) + 线程栈+ Java NIO

回到文章最初提出的问题,本来的内存分配是:6g(java堆) + 600m(监控) + 800m(体系),剩下大约600m内存未分配。

现在剖析这600m内存的分配状况:

  1. Linux保存大约200m,这部分是Linux正常运转的需求,
  2. Java服务的线程数量是160个,JVM默许的线程栈巨细是1m,因而运用160m内存,
  3. Java NIO buffer,经过JMX查到最多占用了200m,
  4. Java服务运用NIO很多读写文件,需求运用PageCache,正如前面剖析,这个暂时欠好定量预算巨细。

前三项加起来现已560m,因而能够判定Linux物理内存不行运用。

仔细的人会发现,引言中给出两个服务器,一个SWAP最多占用了2.16g,别的一个SWAP最多占用了871m;可是,好像咱们的内存缺口没有那么大。事实上,这是因为SWAP和GC一起进行形成的,从下图能够看到,SWAP的运用和长时刻的GC在同一时刻发作。

SWAP和GC一起发作会导致GC时刻很长,JVM严峻卡顿,极点的状况下会导致服务溃散。原因如下:JVM进行GC时,时需求对相应堆分区的已用 内存进行遍历;假设GC的时分,有堆的一部分内容被交换到SWAP中,遍历到这部分的时分就需求将其交换回内存,一起因为内存空间缺乏,就需求把内存中堆 的别的一部分换到SWAP中去;所以在遍历堆分区的进程中,(极点状况下)会把整个堆分区轮流往SWAP写一遍。Linux对SWAP的收回是滞后的,我 们就会看到很多SWAP占用。上述问题,能够经过削减堆巨细,或许添加物理内存处理。

因而,咱们得出一个定论:布置Java服务的Linux体系,在内存分配上,需求防止SWAP的运用;详细怎么分配需求归纳考虑不同场景下JVM对Java永久代 、Java堆(新生代和老时代)、线程栈、Java NIO所运用内存的需求。

2.内存走漏问题

另一个事例是,8g内存的服务器,Linux运用800m,监控进程运用600m,堆巨细设置4g;体系可用内存有2.5g左右,可是也发作了很多的SWAP占用。

剖析这个问题如下:

  1. 在这个场景中, Java永久代 、Java堆(新生代和老时代)、线程栈所用内存基本是固定的,因而,占用内存过多的原因就定位在Java NIO上。
  2. 依据前面的模型,Java NIO运用的内存首要散布在Linux内核内存的System区和PageCache区。检查监控的记载,如下图,咱们能够看到发作SWAP之前,也便是 物理内存不行运用的时分,PageCache急剧缩小。因而,能够定位在System区的Java NIO Buffer发作内存走漏。



  1. 因为NIO的DirectByteBuffer需求在GC的后期被收回,因而接连请求DirectByteBuffer的程序,一般需求调用 System.gc(),防止长时刻不发作FullGC导致引用在old区的DirectByteBuffer内存走漏。剖析到此,能够揣度有两种或许的 原因:榜首,Java程序没有在必要的时分调用System.gc();第二,System.gc()被禁用。
  2. 最终是要排查JVM发动参数和Java程序的DirectByteBuffer运用状况。在本例中,检查JVM发动参数,发现启用了-XX:+DisableExplicitGC导致System.gc()被禁用。

四、总结

本文详细剖析了Linux与JVM的内存联系,比较了一般进程与JVM进程运用内存的异同点,了解这些特性将对Linux体系内存分配、JVM调优、Java程序优化有协助。限于篇幅联系只是罗列两个事例,期望起到抛砖引玉的效果。

最新评论