JVM和Linux之间的详细内存关系
在一些具有8g物理内存的服务器上,主要运行Java服务。系统内存分配如下:Java服务的JVM堆大小设置为6g,监视过程大约需要600m,Linux本身使用大约800m。
从表面上看,物理记忆应该足够;但实际操作是会发生大量SWAP(表明物理内存不足),如下图所示。由于同时发生SWAP和GC会导致JVM严重卡住,我们不得不问:内存在哪里?
JVM和Linux之间的详细内存关系
JVM和Linux之间的详细内存关系
要分析此问题,了解JVM与操作系统之间的内存关系非常重要。下一步是对Linux和JVM之间的内存关系进行一些分析。
首先,Linux和进程内存模型
JVM作为Linux系统上的进程运行。了解Linux和进程之间的内存关系是理解JVM和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位系统的划分是类似的。 。
JVM和Linux之间的详细内存关系
从过程的角度来看,进程可以直接访问的用户存储器(虚拟存储空间)分为五个部分:代码区,数据区,堆区,堆栈区和未使用区。
应用程序的机器代码代码存储在代码区域中。代码在运行过程中无法修改,具有只读和固定大小的特征。
数据区存储应用程序中的全局数据,静态数据和一些常量字符串等,其大小也是固定的。
堆是动态应用运行时程序的空间,属于程序运行时直接应用和释放的内存资源。
堆栈区域用于存储传入参数,临时变量和返回地址等数据。
未使用区域是用于分配新存储空间的备用区域。
第二,进程和JVM内存空间
JVM本质上是一个进程,因此它的内存空间(也称为运行时数据区,注意JMM之间的差异)也具有该进程的一般特征。在Java中深入解释JVM内存管理,这个参考。
但是,JVM不是一个普通的进程,它在内存空间中有许多新功能。主要有两个原因:
1. JVM将属于操作系统管理范围的许多东西移植到JVM,以减少系统调用的次数;
2. Java NIO,目的是减少读写IO系统调用的开销。将JVM进程与正常进程内存模型进行比较,如下所示。:
JVM和Linux之间的详细内存关系
应该注意的是,该模型不是JVM内存使用的准确模型,而是从操作系统的角度关注JVM的内部细节(尽管很重要)。下面从两个方面描述JVM进程的内存特征:用户内存和内核内存。
用户记忆
上图突出显示了JVM进程模型的代码区和数据区是指JVM本身,而不是Java程序。正常的进程堆栈区域通常仅用作JVM中的线程堆栈。 JVM堆区域和正常进程之间的差异是最大的,详情如下:
第一个是永久性的一代。永久生成本质上是Java程序的代码和数据区域。 Java程序中的类被加载到整个区域的不同数据结构中,包括常量池,字段,方法数据,方法体,构造函数以及类中的专用方法,实例初始化和接口初始化。该区域是操作系统堆的一部分;对于Java程序,这是容纳程序本身和静态资源的空间,允许JVM解释Java程序的执行。其次是新一代和老年。新一代和老一代是Java程序实际使用的堆空间,主要用于存储对象的存储;但管理方法与普通流程有本质区别。
当正常进程在运行时向内存对象分配空间时,例如,当C ++执行新操作时,它会触发分配内存空间的系统调用,并且操作系统的线程根据对象的大小分配空间,回报;同时,当程序释放对象时例如,当C ++执行删除操作时,它还会触发系统调用,以通知操作系统对象可以回收操作系统对象占用的空间。
JVM对内存的使用与一般过程不同。 JVM将一个完整的内存区域(特定的大小可以在JVM参数中调整)作为一堆Java程序(分为新一代和老一代)应用于操作系统;当Java程序申请内存空间时,例如执行新操作,JVM将在这里。段空间根据所需大小分配给Java程序,并且Java程序不负责在释放对象空间时通知JVM。垃圾对象内存空间由JVM回收。
JVM内存管理方法的优点是显而易见的,包括:首先,减少系统调用的数量,JVM在为Java程序分配内存空间时不需要操作系统干预,只需要向操作系统申请内存当Java堆大小更改时。或者通知回收,每次分配和回收内存空间时,正常程序需要系统调用才能参与;二,减少内存泄漏,正常程序没有(或不及时)通知释放操作系统内存空间是内存泄漏的重要原因之一。并且由JVM管理,可以避免程序员造成的内存泄漏。
最后,有一个未使用的区域,这是一个用于分配新内存空间的备用区域。对于正常进程,此区域可用于堆和堆栈空间请求和释放。该区域用于每个堆内存分配,因此大小经常变化;对于JVM进程,使用堆大小和线程堆栈。这个区域虽然堆大小一般调整较少,所以尺寸相对稳定。操作系统动态调整此区域的大小,并且此区域通常不分配实际物理内存,只允许进程在此区域中应用堆或堆栈空间。
2.内核内存
应用程序通常不直接处理内核内存,内核内存由操作系统管理和使用;然而,由于Linux专注于性能和改进,新功能允许应用程序使用内核内存或映射到内核空间。 Java NIO就是在这种背景下诞生的,它充分利用了Linux系统的新特性,提高了Java程序的IO性能。JVM和Linux之间的详细内存关系
上图显示了Java NIO在Linux系统中使用的内核内存的分布。 Nio缓冲区主要包括:nio在使用各种通道时使用的ByteBuffer,Java程序主动使用ByteBuffer.allocateDirector来申请分配的Buffer。
在PageCache中,nio使用的内存主要包括:FileChannel.map模式打开文件占用映射所需的Cache,FileChannel.transferTo和FileChannel.transferFrom(图中未显示)。
可以通过JMX监视NIO Buffer和映射的使用,如下图所示。但是,FileChannel的实现是通过系统调用使用本机PageCache。该过程对Java是透明的,无法监视这部分内存的使用情况。
JVM和Linux之间的详细内存关系
Linux和Java NIO为程序打开内核内存空间,主要是为了减少不必要的复制,以减少IO操作系统调用的开销。例如,使用常规方法和NIO将数据从磁盘文件发送到网卡时,将比较数据流,如下图所示:
JVM和Linux之间的详细内存关系
在内核内存和用户内存之间复制数据是浪费资源和时间。从上图中,我们可以看到NIO方法减少了内核内存和用户内存之间的数据副本两次。这是Java NIO高性能的重要机制之一(另一种是异步非阻塞)。
从上面可以看出,内核内存对Java程序性能也非常重要。因此,在划分系统内存使用时,必须为内核留出一些空闲空间。
三,案例分析
1.内存分配问题
通过以上分析,省略较小的区域,可以总结JVM占用的内存:
JVM内存≈Java永久代+ Java堆(新一代和老一代)+线程堆栈+ Java NIO
回到本文开头提出的问题,原来的内存分配是:6g(java堆)+ 600m(监控)+ 800m(系统),剩下的600m内存未分配。
现在分析这个600m内存的分配:
Linux保留了大约200米,这是Linux正常运行的必要条件。
Java服务的线程数为160.JVM的默认线程堆栈大小为1m,因此使用160m内存。Java NIO缓冲区,JMX最长可达200米。
Java服务使用NIO大量读写文件,您需要使用PageCache。如前面的分析所述,这不是一个好的定量估计。
前三项总计达560米,因此您可以得出Linux物理内存不足的结论。
细心的人会发现这两个服务器在介绍中给出。一个SWAP最多占用2.16g,另一个SWAP占用871m。然而,似乎我们的记忆差距并不大。实际上,这是由于同时实施SWAP和GC。从下图中可以看出,SWAP的使用与长期GC同时发生。
JVM和Linux之间的详细内存关系
JVM和Linux之间的详细内存关系
SWAP和GC的同时发生将导致GC时间长,严重的JVM以及极端的服务崩溃。原因如下:当JVM执行GC时,需要遍历相应堆分区的已用内存;如果GC,堆的某些部分交换到SWAP,并且在遍历到此部分时需要将其交换回内存。同时,由于内存空间不足,有必要将内存中堆的另一部分切换为SWAP;在遍历堆分区的过程中,(在极端情况下)整个堆分区将依次写入SWAP。 Linux对SWAP的恢复滞后,我们将看到很多SWAP用法。可以通过减小堆大小或增加物理内存来解决上述问题。
因此,我们得出结论,部署Java服务的Linux系统需要避免在内存分配中使用SWAP;如何分配它取决于JVM for Java permanent generation和Java heap(新一代和老一代)在不同的场景中。 ,线程堆栈和Java NIO的内存使用情况。
2.内存泄漏问题
另一种情况是8g内存服务器,Linux使用800m,监控进程使用600m,堆大小设置为4g;系统可用内存约为2.5g,但很多SWAP也被占用。
对此问题的分析如下:
1在这种情况下,Java永久生成,Java堆(新一代和老一代),线程堆栈使用的内存基本上是固定的,因此占用过多内存的原因位于Java NIO上。
2根据以前的模型,Java NIO使用的内存主要分布在Linux内核内存的System区域和PageCache区域。查看监控记录,如下图所示,我们可以看到在SWAP发生之前,也就是说,当物理内存不足时,PageCache会急剧缩小。因此,位于系统区域的Java NIO缓冲区中可能会发生内存泄漏。JVM和Linux之间的详细内存关系
JVM和Linux之间的详细内存关系
3由于NIO的DirectByteBuffer需要稍后在GC中回收,因此连续申请DirectByteBuffer的程序通常需要调用System.gc()以避免FullGC长期失效导致旧区域中的DirectByteBuffer内存泄漏。在分析了这一点之后,可以推断出有两个可能的原因:首先,Java程序在必要时不调用System.gc();第二,System.gc()被禁用。
4最后,有必要检查JVM启动参数和Java程序的DirectByteBuffer用法。在此示例中,查看JVM启动参数并发现启用-XX: + DisableExplicitGC会导致System.gc()被禁用。
上一篇: 没有了
下一篇: 没有了
分享到: