一图解千愁,jvm内存从来没有这么简单过!

16 篇文章 0 订阅
5 篇文章 0 订阅

在这里插入图片描述

了解了这张图,会让你对JVM内存的划分有更深入的理解,而不仅限于什么虚拟机栈、程序计数器等比较浅显的认知。

那么这张图有什么用呢?在进行内存排查的时候,我们需要了解到底是哪一个部分除了问题。如果你找不对地方,肯定切入就比较困难,这会耗费你大量的精力。

一台4GB的机器,一般使用Xmx分配给JVM的,肯定不能太多。比如3.5GB之类的。这就太贪婪了,很容易造成JVM异常死亡。这是为什么呢?

这个比较好理解,因为在操作系统上,运行的不仅仅你的JVM应用,还会有其他一些守护进程,比如各种日志收集工具、监控工具、安全工具等。它们虽然占用的内存不是很多,但累加起来还是比较可观的。JVM内存和操作系统的剩余内存是一个此消彼长的关系,这些小内存挤占了JVM的发挥空间,就容易出问题。

在这里插入图片描述
VM是我们的主体,所以要把它放在主人公的位置。这种划分方式,就可以把整个内存搞成JVM内存、操作系统物理内存、SWAP三个部分。

当JVM和其他程序占满了物理内存,接着占满了SWAP内存(交换分区一般不开,这个一会在说),当在需要申请内存空间的时候,操作系统发现:完蛋了,没有可用的内存空间了。

这个时候,Linux会启动oom-killer,杀死占用内存最大的进程,这个时候大概率是你可爱的JVM宝贝进程。

这里的oom,指的是操作系统的,而不是JVM的。所以你会发现:你的java进程死了,但是什么都没有留下。就这么静悄悄的去了。

这些信息,只能通过dmesg命令找到,属于操作系统范畴。

那么接下来,我们就上一下最主要的一张图,然后解释一下这十几部分都是干什么的。

我们依然把内存分为上面的三部分,但是对JVM的进程内存进行更细致的划分。

在这里插入图片描述
首先,对于JVM的内存,有堆内内存和堆外内存之分。

对于堆内内存,是我们平常打交道最多的地方,因为我们大部分Java对象,都是在堆上分配的。一旦有溢出问题,使用jmap + mat等一系列猛如虎的操作,就可以方便快捷的发现问题。

这是一个Java好手都能掌握的技能。

关键就是堆外内存那一部分,就十分的蛋疼了。因为杂七杂八的东西都在这里,很容易搞混。

可以看到,对于这部分的内存问题,即使是JVM界最权威的周老师的书籍,依然也有相关的错误。

在这里插入图片描述
这段代码的运行结果其实是错误的,这里的unsafe,并不是直接内存。

那我们就盘点一下里面都有些啥。

第一,元空间

元空间是jdk8以后才加入的,用来替换原来的永久代。也就是说,原perm区(永久代)中的方法区,也在这里。从它原来的名字就可以看出来,永久代指的就是那些变动很少的数据,稳定为主。比如我们在jvm启动时,加载的那些class文件;以及在运行时,动态生成的代理类。

比较坑的是,元空间的大小,默认是没有上限的。极端情况下,会一直挤占操作系统的剩余内存。

第二、CodeCache

很多文章对着一部分的介绍非常少,但其实这也是非常重要的一个非堆区域。因为JIT是JVM一个非常重要的特性,CodeCahe存放的,就是即时编译器所生成的二进制代码。当然,JNI的代码也是放在这里的。

这个空间在不同的平台,大小都是不一样的,但一般够用了。也有同学手贱把这个区域调的非常的小,这种情况下,JVM不会溢出,这个区域也不会溢出,但是会退化成解释型执行模式,速度和JIT不可同日而语,慢个数量级也是可能的。

本地内存

其实,在聊天的时候,我们相互谈到的堆外内存,大部分指的是这里,大部分出问题的,也是这里。它有更细致的划分。

(1)网络内存

网络连接也是要占用很多内存的。这个连接就非常有意思,你可以认为它是操作系统内核所占用的内存,也可以认为是JVM进程占用的内存。

如果你的系统并发非常高,这部分内存的占用也是比较多的。因为连接一般对应着网卡的数据缓冲区,还有文件句柄的耗费。

(2)线程内存

同样的,如果你造的线程非常多,JVM除了占用Thread对象本身很小的一部分堆内存,大部分是以轻量级进程的方式存在于操作系统。

这同样是一个积少成多的内存区域,但一般不会发生问题。

(3)JNI内存

上面谈到CodeCache存放的JNI代码,JNI内存就是指的这部分代码所malloc的具体内存。

比如Java的zip库,就不是在JVM的堆里完成的,而是开辟了一个堆外的缓冲池进行运算。

(4)直接内存

直接内存,指的是使用了Java的直接内存API,进行操作的内存。这部分内存可以受到JVM的管控,比如ByteBuffer类所做的事情。

ByteBuffer底层是用的unsafe,但unsafe是不受直接内存的管控的,它们不是一个东西。

上面提到的书中直接使用unsafe程序,并不会造成JVM直接内存溢出,反而会造成操作系统内存溢出。

那这些内存我们如何看到呢?

linux下有一个命令lsof,可以看到JVM进程所关联的所有句柄信息,一般可作为参考。

近一步,使用pmap函数,即可观测到具体的内存分布。但是不要怕,有很多是共享内存。

如果你了解了图中这些内存划分,就会很容易了解,为什么NMT工具无法显示JNI内存的统计。

接下来,我们总结一下,这些内存区域,哪些参数能够控制它们。

在这里插入图片描述

  • 堆 -Xmx-Xms
  • 元空间 -XX:MaxMetaspaceSize-XX:MetaspaceSize
  • 栈 -Xss
  • 直接内存 -XX:MaxDirectMemorySize
  • JIT编译后代码存放 -XX:ReservedCodeCacheSize

其他堆外内存 无法控制!随缘吧。
可以看到,堆外内存的占用,其实还是比较多的。如果你太贪婪,整个内存很容易就玩玩。

一般的,我们使用操作系统的2/3作为堆空间,是比较合理的。这是一个经验值。比如6GB的内存,你分配给JVM的,最好不要超过4GB。

还有,我们上面谈到的swap交换分区,在高并发应用中,一般是关掉的。因为它会造成频繁的页交换,在GC的时候,会引起严重的卡顿。

但要辩证的思维看待问题。对于低频的,对内存大小有非常大的依赖的情况下,SWAP不仅要开,还要开的大一些。

欢迎各位大佬进群共同交流学习,我们的交流分享群:1149778920 暗号:CSDN
博主在这里给大家整理了包括但不限于:JAVA基础和进阶类、Spring、Spring boot、Spring MVC、MyBatis、MySQL、JVM等各种资料有,免费分享给各位进群的小伙伴

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
1.何为内存模块 (Memory Module)? 内存模块是指一个印刷电路板表面上有镶嵌数个记忆体芯片chips,而这内存芯片通常是DRAM芯片,但近来系统设计也有使用快取隐藏式芯片镶嵌在内存模块上内存模块是安装在PC 的主机板上的专用插槽(Slot)上镶嵌在Module上DRAM芯片(chips)的数量和个别芯片(chips)的容量,是决定内存模块的设计的主要因素。 2.什么是Parity? 早先所使用的存储器数据检错方式是Parity Check,其是以每8比特增加1比特的方式进行检错。因此若是具备Parity的存储器,其数据宽度将比非parity的存储器为大。不过若以 Parity的检错方式看,其多出的奇偶位事实上只用于分辨奇数或偶数个比特数。这种方式仅能得知是某一位发生错误,并无法确定是哪一个位置发生错误,而且无法运用在双数位的检错上。 3.什么是ECC? ECC是另一种更为进步的存储器数据检错机制。其工作的方式与Parity不同,并不是采用单一比特的方式来进行检错,而是采用数据块(block)概念与复杂的演算方法来修正数据。因此不仅能检测多位比特错误,还能进行修正单一比特的错误。 4.SPD的作用是什么? 为Serial Presence Detect 的缩写,它是烧录在EEPROM内的码,以往开机时BIOS必须侦测memory,但有了SPD就不必再去作侦测的动作,而由BIOS直接读取 SPD取得内存的相关资料。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值