JAVA虚拟机之-JVM内存模型

JAVA虚拟机之之-JVM内存模型

java虚拟机在运行过程中会将申请到的内存划分为不同的区域,以便更好的管理,以下是Java虚拟机规范中包含的运行时数据区模型:

在这里插入图片描述

一、程序计数器(Program Counter Register):线程私有

这是一个较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器。由于在多线程环境下,线程之间通过轮流获取CPU时间片执行,每个CPU核心同一时刻只能运行一个线程,所以每条线程都有自己独立的程序计数器,以便在线程切换后能恢复到正确的执行位置。如果线程正在执行的是一个Java方法,则这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,则这个计数器的值为空(Undefined)。同时这也是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError异常的区域。

二、虚拟机栈(Java Virtual Machine Stacks):线程私有

该区域也是线程私有的,每个线程都会创建自己的栈用来执行该线程的方法,每个方法在执行的同时都会创建一个栈帧(Stack Frame),用于保存局部变量表操作数栈动态链接方法出口等信息,每个方法从调用到执行完毕,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。虚拟机栈不会发生垃圾回收,只有入栈和出栈。

在这里插入图片描述

局部变量表:存放编译时可知的各种基本数据类型(boolean、byte、chat、short、int、float、long、double)、对象引用(reference)和returnAddress类型(指向一条字节码指令的地址)。其中long和double类型会占用两个局部变量空间(Slot),其余的数据类型只占一个。局部变量表的大小在编译时即可确定,且在运行期间不会发生改变。

操作数栈:用于在方法执行过程中根据字节码指令将值写入栈或取出栈,例如求和操作,需要将两个值写入操作数栈,然后通过特定的字节码指令取出两个值计算后再将结果入栈。

动态连接:指向运行时常量池中该栈帧所属方法的引用。目的是为了实现动态链接,也就是找到正确的方法入口,动态链接发生在栈帧完全入栈之前,也就是局部变量表创建之前。

方法出口:

方法正常结束:调用者(可能也是一个方法)的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。如果该方法有返回值,则压入方法调用者的操作数栈中。

方法异常结束:方法异常退出时,返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息。

在虚拟机规范中虚拟机栈有两种异常:

StackOverflowError:线程请求的栈深度大于虚拟机允许的深度,即一个线程创建了大量的栈帧,例如递归深度过大,每次递归都需要添加一个栈帧,当虚拟机栈放不下新增的栈帧,则会出现该异常。

OutOfMemoryError:虚拟机栈在动态拓展的过程中无法申请到足够的内存,或者在创建新的线程时内存中没有足够的内存来创建对应的虚拟机栈时,则会抛出该异常。

三、本地方法栈(Native Method Stack):线程私有

该区域与虚拟机栈基本相同,只不过虚拟机栈执行的是Java方法,而本地方法栈可用于执行虚拟机自身的Native方法。在Java虚拟机规范中并没有对本地方法栈中执行的方法使用的语言、使用方式与数据结构做出强制规定,可有虚拟机实现者自己决定。甚至有的虚拟机(Sun HotSpot虚拟机)直接将本地方法栈和虚拟机栈合二为一。Java虚拟机规范中该区域同样会抛出StackOverflowErrorOutOfMemoryError异常。

四、堆(Java Heap):线程共享

往往是Jvm运行时最大的一块区域,该内存区域唯一的作用就是存放对象的实例,几乎所有的对象实例都在这里分配内存。也就是 new 出来的对象都存在堆中,同时该区域也是垃圾回收器的主要管理区域,由于现在垃圾回收器基本采用分代收集算法,所以堆也可被细分为多个逻辑区域。例如:新生代、老年代。区域的划分跟存储的内容无关,存的都是对象的实例,之所以分区是能更好的回收内存,或者更快的分配内存。

堆空间可通过(-Xmx和-Xms)配置最大和初始大小,当达到最大值以后,再创建对象的话会抛出OutOfMemoryError异常。
在这里插入图片描述

五、方法区(Method Area):线程共享

用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它却又一个别名叫做Non-Heap(非堆)。

在hotspot虚拟机中,在jdk1.7以前,方法区的实现是“永久代”,主要是为了能与堆使用同样的垃圾回收器,不用特意为方法区编写特定的垃圾回收机制。而到了jdk1.8以后则变为了“元空间”,其实无论是永久代还是元空间,都是对Java虚拟机规范中“方法区”的一种实现方式。最大的区别是将字符串常量池由方法区移动到了堆,同时元空间的内存区域也从堆中划分出来,使用直接内存的方式来实现“元空间”,该区域的垃圾回收频率较低,且回收效率不高,主要是由于该区域主要存储类加载后的数据,而类卸载的条件比较“苛刻”,需要满足以下条件:

1.堆中不存在该类或子类的实例。

2.加载该类的类加载器已被回收。

3.该类的java.lang.Class对象没有被其他地方引用,即无法通过反射创建该类的实例。

在这里插入图片描述

5.1运行时常量池(Runtime Constant Pool):方法区的一部分

Class文件中除了有类的版本、字段、方法、接口等描述信息,还有一项信息就是常量池,用于存放各种编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中,运行期间也可将新的常量放入其中,例如String类的intern()方法,由于该区域是方法区的一部分,所以大小受方法区空间限制,当常量池无法申请到内存时抛出OutOfMemoryError异常。

六、直接内存(Direct Memory)

直接内存并不是虚拟机运行时数据区的一部分,Java虚拟机规范中也没有对应的定义,只是由于这部分内存也被频繁的使用。

在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)于缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

这部分空间不会受到Java虚拟机内存的大小的限制,但由于使用的是直接内存,所以会受到物理机内存大小的限制。

和Native堆中来回复制数据。

这部分空间不会受到Java虚拟机内存的大小的限制,但由于使用的是直接内存,所以会受到物理机内存大小的限制。
参考文献:《深入理解Java虚拟机_JVM高级特性与最佳实践 第2版》

如果觉得文章对你有帮助的话,可以微信关注我的公众号:码农小诚
关注我的公众号更多技术文章分享实时更新,与你共同成长,回复【555】还有55本java技术书籍,免费送,回复【zk】还能获取更多zookeeper相关技术书籍。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农小诚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值