浅析虚拟机内存管理模型

Posted Java小吴吴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅析虚拟机内存管理模型相关的知识,希望对你有一定的参考价值。

Java虚拟机在执行Java程序的过程中会把Java程序所管理的内存划分为若干个不同的数据区域,这些区域可以划分为5各部分:虚拟机栈、堆、方法区、本地方法栈、程序计数器,如图:

虚拟机栈

Java虚拟机栈(Java Virtual Machine Stack)是线程私有的,它的生命周期与线程相同。也就是,每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧 (Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。下面讲解一下虚拟机栈中的内容:

局部变量表

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。请读者注意,这里说的“大小”是指变量槽的数量,虚拟机真正使用多大的内存空间(譬如按照1个变量槽占用32个比特、64个比特,或者更多)来实现一个变量槽,这是完全由具体的虚拟机实现自行决定的事情。

returnAddress类型目前已经很少见了,它是为字节码指令jsr、jsr_w和ret服务的,指向了一条字节码指令的地址。虽然long以及double是分配在两个变量槽中,但是由于在线程内部,所以不会有数据竞争和线程安全问题。

操作数栈

操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到Code属性的max_stacks数据项之中。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。譬如在做算术运算的时候是通过将运算涉及的操作数栈压入栈顶后调用运算指令来进行的,又譬如在调用其他方法的时候是通过操作数栈来进行方法参数的传递。举个例子,例如整数加法的字节码指令iadd,这条指令在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会把这两个int值出栈并相加,然后将相加的结果重新入栈。

写个小案例:

 

package com.courage; public class DeOperandStack public static void main(String[] args) int i = 1; int j = 2; int k = i + j;

此时DeOperandStack类中只有一个线程(main),局部变量表中拥有的变量:

默认args为0号变量,所以这个线程中有四个局部变量,那么是如何利用操作数栈进行加减的呢?

首先将第一个常数压入栈,然后存储局部变量表1号变量,然后将第二个常数压入栈,然后存储局部变量表2号变量,然后将局部变量表1,2两个数值加载进栈,弹出相加之后将结果压入栈,在将栈顶数据存储到3号变量。

动态连接

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。我们知道Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。

方法出口

当一个方法开始执行后,只有两种方式退出这个方法:

  • 遇到方法返回的字节码指令
  • 遇到了异常

第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者(调用当前方法的方法称为调用者或者主调方法),方法是否有返回值以及返回值的类型将根据遇到何种方法返回指令来决定,这种退出方法的方式称为“正常调用完成”(Normal Method Invocation Completion)。

另外一种退出方式是在方法执行的过程中遇到了异常,并且这个异常没有在方法体内得到妥善处理。无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为“异常调用完成(Abrupt Method Invocation Completion)”。

一个方法使用异常完成出口的方式退出,是不会给它的上层调用者提供任何返回值的。无论采用何种退出方式,在方法退出之后,都必须返回到最初方法被调用时的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层主调方法的执行状态。

Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java
世界里“几乎”所有的对象实例以及数组都在这里分配内存。

从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java堆细分的目的只是为了更好地回收内存,或者更快地分配内存。

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外,甚至还可以选择不实现垃圾收集,这区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收有时又确实是必要的,此区域未完全回收会导致内存泄漏。

方法区、永久代、元空间的关系

之所以将这三个放一起,是这儿很容易混淆,对于Hotspot虚拟机,JDK6、JDK7 时方法区是 PermGen(永久代),JDK8 时,方法区是 Metaspace(元空间),怎么回事呢?

方法区 是JVM的规范,所有虚拟机必须遵守的。常见的JVM虚拟机Hotspot 、JRockit(Oracle)、J9(IBM)

PermGen space则是 HotSpot 虚拟机 基于 JVM 规范对 方法区 的一个落地实现, 并且只有 HotSpot 才有 PermGen space。而如 JRockit(Oracle)、J9(IBM) 虚拟机有 方法区 ,但是就没有 PermGen space。

PermGen space 是JDK7及之前, HotSpot 虚拟机 对 方法区 的一个落地实现,在JDK8被移除。

Metaspace(元空间)是 JDK8及之后, HotSpot 虚拟机对方法区 的新的实现。

永久代以及元空间,可以用来存放堆中存活很久的对象。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存

类信息

每一个类有一个Class对象,编译期生成,保存在同名的.class文件中。这些Class对象包含了这个类型的父类、接口、构造函数、方法、属性等详细信息,这些class文件在程序运行时会被ClassLoader加载到JVM中,在JVM中就表现为一个Class对象,JVM使用该Class对象创建该类的所有常规对象,而这个对象的信息则保存在方法区的类信息中。

常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

静态变量区

静态变量也叫类变量,类的所有实例都共享,这个区专门存放静态变量和静态块。

static 修饰的 在JVM运行时就加载到内存中了 所以不需要实例类。

静态变量在类加载的准备阶段分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配,但必须注意到方法区本身是一个逻辑上的区域,在JDK 7及之前,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的;而在JDK 8及之后,类变量则会随着Class对象一起存放在Java堆中,这时候“类变量在方法区”就完全是一种对逻辑概念的表述了,关于这部分内容,笔者已在4.3.1节介绍并且验证过。

程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在Java虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

由于Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

说明:本文限于篇幅,故而只展示部分的面试内容,完整的Java面试学习文档小编已经帮你整理好了,有需要的朋友私信我(需要)免费领取Java、大厂面试学习资料哦!

 

 

浅析VMware虚拟化平台内存管理

虚拟化平台三种内存模式
a.主机物理内存(Host Physical Memory)
主机物理内存是ESXi在一个物理主机上检测到的内存值,该值是主机实际物理安装的内存值。例如,一台X86服务器配置16根32GB内存,则该主机的物理内存为512GB。
b.虚拟机物理内存(Guest Physical Memory)
登录虚拟机操作系统可以看到分配给虚拟机的内存,例如创建虚拟机的时候分配4GB内存,则操作系统能识别到4GB内存,这就是虚拟机物理内存
c.虚拟机虚拟内存(Guest Virtual Memory)
是指虚拟机操作系统分配给应用程序的可用内存,这是虚拟机操作系统映射到虚拟机物理内存的虚拟内存地址空间,是虚拟机操作系统提供给他的应用程序使用的内存地址空间,在非虚拟化环境中也是如此。
2,内存三种模式的工作方式
下图显示了三层内存的相互映射方式,虚拟机虚拟内存映射到虚拟机物理内存,虚拟机物理内存被虚拟机管理器(hypervisor)映射到ESXi主机物理内存。
技术图片
举例说明:如果你有一个虚拟机,在虚拟机里面开启一个微信应用程序,微信如果想要运行就会向虚拟机索要内存,虚拟机会提供内存给微信应用使用,这个时候内存的流向是Guest virtual memory 向 guest physical?memory申请内存。此时,虚拟机管理器会收到guest physical memory向Host physical memory申请内存,申请完成后微信应用程序才能正常运行。总结一下就是一个ESXi主机为其上运行的每个虚拟机创建一个虚拟内存地址空间,这些虚拟内存地址空间向下映射到主机内存地址,向上映射到虚拟机作为虚拟机的物理内存。当微信使用完后,虚拟机会将这部分内存标记为“free”,此时其他应用程序可以使用这部分内存,但是虚拟机管理器不会关心这些被标记为“free”的内存,并且认为这些内存一直分配给了虚拟机。也就是说,一旦管理程序将内存分配给一个虚拟机,它没办法分辨分配给虚拟机的内存是被虚拟机操作系统分配给了应用程序还是已经被释放。所以当虚拟机的操作系统释放虚拟机物理内存时,管理程序不能从虚拟机回收主机物理内存。下图表示了这种属性。
技术图片
有人会问如果这样的话,虚拟机会不会一直请求内存,但是一直不释放,造成物理内存占满的情况。其实是不会出现这种状况的,当一个虚拟机分配的虚拟机物理内存已被完全占用时,管理程序不会再向虚拟机分配更多的物理主机内存,即使内存限制设置为无限制。
3.虚拟机的内存回收
内存分配容易回收难,因为内存回收的时候,管理程序必须必须保留足够的物理主机内存来支持虚拟机物理内存分配和虚拟机运行的内存开销。但是在日常工作中我们经常会遇到ESXi主机内存过量分配的情况,ESXi通过使用透明页共享(Transparent Page Sharing)、内存膨胀(Ballooning)、管理程序内存交换(hypervisor swapping)及内存压缩(memory compression)等机制保证能使内存过量分配。
a.透明页共享
当ESXi主机运行多个虚拟机时,这些虚拟机可能已加载相同的应用程序或组件,或者包含公用数据。使用透明页共享技术可以消除内存页的冗余副本,只保留一份,其余的内存可以重新分配给其它虚拟机。
b.内存膨胀
如上所述,当虚拟机的客户操作系统释放内存时,ESXi不能简单地回收主机物理内存页,虚拟机操作系统也不知道ESXi主机内存的真实使用率。当ESXi主机内存处于压力状态时,主机可以通过内存膨胀机制来回收虚拟机的物理内存页,触发内存膨胀的驱动程序在安装vmware tools的时候已经安装在操作系统中,驱动程序的名称为vmmemctl。
技术图片
虚拟机操作系统内存膨胀
当主机的物理内存处于压力状态,主机需要从虚拟机中回收物理内存时,管理程序会给vmmemctl设置一个须达到的目标值,vmmemctl收到目标值后,虚拟机首先将被标记为“free”的内存“填充气球”,如果这些被标记为“free”的内存不能满足的情况下,可能触发内存交换的情况,以达到需要回收目标值。最理想的状态是,在不进行内存交换的情况下就能满足目标值,这样就不会影响虚拟机的性能。为了避免在进行内存交换的时候有可以交换的磁盘可用,所以在虚拟机进行配置时,要有足够的交换空间。当然也可以设置限制可使用内存膨胀回收的内存量,甚至可以禁用内存膨胀,但是一般情况下不建议这样做,因为内存膨胀是在保障虚拟机操作系统性能的情况下做出的选择,要比其他方式温柔的多,所以不建议禁用内存膨胀。
c.内存压缩
内存压缩就是指那些需要交换到磁盘的内存页不进行磁盘交换而是进行压缩,这种时候就是上面内存膨胀需要回收内存时,如果“free”内存不能满足回收的目标值,虚拟机会将不会内存交换到磁盘,在交换到磁盘之前管理程序会判断是进行压缩对虚拟机性能影响小,还是进行交换对虚拟机性能影响小。并不是所有的内存都适合压缩,如果一个内存页压缩比在50%以上,ESXi主机将会压缩这些内存。如果内存页压缩比在50%以下,则会通过管理程序将内存交换到磁盘。
d.管理程序内存交换
当ESXi主机使用透明页共享、内存膨胀、内存压缩等机制都不能回收内存时,才会使用管理程序内存交换。在进行内存交换时,管理程序不会判断虚拟机里面哪些内存适合交换,也就是说它不会判断页面对于虚拟机的重要程度,所以会随机选择内存页面进行交换,这就会对虚拟机造成不可预知的影响,内存交换到磁盘审核,也会严重影响虚拟机的性能。在ESXi5.5以上版本中可以设置SSD缓存交换,以较少对性能的影响。
4.使用什么机制进行回收?
根据ESXi主机实际可用内存量的不同,ESXi有4种内存状态,分别是High、Soft、Hard以及Low,具体定义阀值如下表:
技术图片
可以使用esxtop命令查看当前处于哪种状态。
技术图片
主机内存处于不同的状态,会触发相应的内存机制进行内存回收,具体如下表:
技术图片
5.怎么知道内存是否存在性能问题?
查看内存的使用情况有很多种,你可以通过vSphere网页客户端进行查看,可以通过使用VMware vRealize Operations Manager,也可以通过esxtop命令进行查看。这里简单介绍下通过esxtop命令进行分析。
SSH登录到ESXi主机,然后输入esxtop命令,输入m进入内存页面,默认5s中更新一次,可以使用s来更改更新间隔(输入s会看到Seconds to delay: 输入你想要的刷新频率,单位为秒,然后回车确认),可以使用f选择要查看的字段,具体详细命令可以参考官方手册,这里只介绍几个比较重要的参数及判断标准。
下图是一个输出截图
技术图片
PMEM/MB
显示服务器的计算机内存统计信息
VMKMEM/MB
显示ESXi VMkernel的计算内存统计信息。这里面要重点注意主机的内存状态。当状态为Soft的时候说明要关注内存情况,说明主机已经存在内存压力。
PSHARE/MB
显示ESXi页共享信息
SWAP/MB
显示ESXi交换使用量统计信息
r/s 由ESXi系统从磁盘换入内存的速率,如果该值大于0,主机物理内存量过量,主机尝试回收内存,并且仅仅通过内存膨胀这一机制无法满足
w/s 由ESXi系统将内存交换到磁盘的速率,如果该值大于0,可能主机物理内存已严重不足
ZIP/MB
显示ESXi内存压缩统计信息
MEMCTL/MB
curr,使用vmmemctl模块回收的物理内存总量
target,ESXi主机尝试使用vmmemctl模块回收的物理内存总量
max,ESXi主机可以使用vmmemctl模块回收的最大物理内存量
如果该值比0大,或者主机内存过量,或者虚拟机已设置了一个比当前配置内存小的内存限制时,主机开始从虚拟机回收内存。
SWCUR 该资源或虚拟机当前使用的交换量
SWTGT ESXi主机预期资源池或虚拟机交换使用量的目标
SWR/s 同SWAP/MB里面的r/s
SWW/s 同SWAP/MB里面的w/s
SWCUR
表示当前使用内存交换交换到磁盘的内存量,如果该值大于0,主机物理内存吃紧,主机尝试回收内存并且仅仅通过内存膨胀一无法满足
CACHEUSD
当前内存压缩所使用的压缩高速缓存量,该值大于0表示主机正在进行内存压缩,主机内存不足
ZIP/s
主机内存压缩率,如果该值大于0,说明主机正在进行内存压缩
UNZIP/s
如果该值大于0,那么被压缩的内存正在被主机或者虚拟机访问,此时虚拟机性能将收到影响。转载至——明辰智航云安网络与虚拟化性能管理系统

以上是关于浅析虚拟机内存管理模型的主要内容,如果未能解决你的问题,请参考以下文章

浅析VMware虚拟化平台内存管理

虚拟化平台内存原理知多少?---浅析VMware虚拟化平台内存管理

JVM JVM内存模型

深入理解Java虚拟机(jvm性能调优+内存模型+虚拟机原理)视频教程

01Java 虚拟机内存模型

阿里架构技术分享 JAVA虚拟机,JVM内核-原理,诊断与优化+内存模型