【Java虚拟机】Java内存模型

Java内存模型-JMM

1、JMM的概念

Java内存模型,即JMM(java memory model),它是JVM的内存规范,定义了JVM的内存模型,它是屏蔽了各种硬件和操作系统的访问差异性,保证了Java程序在各个平台下对内存的访问都能效果一致的一种规范及机制。
简而言之,JMM定义了JVM的内存模型,屏蔽了不同硬件和操作系统的差异性,它的主要目的是解决多线程通过共享内存进行通讯时,本地内存数据不一致、编译器会对指令进行重排序,处理器会对代码乱序执行所带来的的问题,JMM可以保证并发场景中的原子性,可见性和一致性问题。

2、Java 虚拟机内存的五大区域

java虚拟机将其管理的内存分为五大部分,包括堆,方法区,程序计数器,本地方法区和Java虚拟机栈。其中堆和方法区是线程共享的,堆、方法区、程序计数器是线程独有的。
内存模型划分

2.1 程序计数器

程序计数器是一块较小的内存区域,它可以看作当前线程的字节码行号指示器,是线程私有的。由于Java虚拟机的多线程是通过线程轮流切换并分配处理器时间来实现的,因此在任何确定时间内,一个处理器(多核的话就是一个内核)都只会执行一条线程中指令。因此,为了使线程切换后能够恢复到正确的执行位置,每一个线程都要有一个程序计数器,各个线程之间的程序计数器互相独立互不影响,我们称这块内存区域为“线程独立”的。
注意,如果线程正在执行的是一个Java方法,那么程序计数器内记录的是正在执行的字节码指令的地址,如果线程执行的是native方法,那么程序计数器内记录的的数值为空(undefined)。此区域是JMM中唯一一块没有规定任何OutOfMemoryError情况的区域

2.2 Java虚拟栈

Java虚拟机栈跟程序计数器一样是线程私有的,它的生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时会同时创建一个栈帧用于存储局部变量表,操作数栈,动态链接和方法出口等信息。每一个方法从开始执行到执行完毕,就对应着一个栈帧从Java虚拟机栈入栈到出栈的过程。
大多数程序员通常将Java的内存区域分为“堆内存”和“栈内存”,当然这并不准确,虚拟机内存区域的划分远比这复杂的多。这样划分的原因是因为程序员对这两块区域接触的较为频繁,这里所说的“栈内存”,即Java虚拟机栈,或者说是Java虚拟机栈的局部变量表部分。
局部变量表中存储了编译器可知的各种基本数据类型(byte,int,short,long,double,float,char,boolean),引用类型reference(reference类型,并不指对象本身,它可能是一个指向某一对象的起始位置的指针,也可能指向一个代表此对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向一条字节码指令的地址)
局部变量表中最小的空间单位为Slot,虚拟机并没有明确一个Slot的具体大小,但在JVM中除了long、double这两个占64字节的基本类型占2个Slot之外,其他基本类型固定占一个Slot大小。
局部变量表所需的内存空间在编译期已经完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量表是完全确定的,方法执行期间不会改变局部变量表的内存大小。
JMM对这个区域规定了两种异常情况:如果线程请求的栈深度大于JVM允许的最大栈深度,会抛出StackOverFlowError异常;如果Java虚拟栈大小可以动态扩展,且如果线程请求的Java虚拟栈的大小无法得到满足,或者无法请求到足够的内存空间时,抛出OutOfMemoryError异常。

2.3 Java堆

对于大多数应用来说,Java堆是Java虚拟机所管理的最大的一块内存区域。它是所有线程共享的区域,随着虚拟机的启动而创建。Java堆唯一的目的就是存放对象实例,几乎所有的实例都要在Java堆上分配内存。
Java堆是垃圾回收器管理的主要区域,因此很多时候也被称为“GC堆”。从内存回收的角度上来看,由于现在的收集器基本上都采用分代回收算法,所以Java堆中还可以细分为新生代和老年代;新生代再细致一点还可分为Eden空间、From Survivor空间和To Survivor空间等。从内存分配的角度上来看,Java堆可划分出多个线程私有的“分配缓冲区”。但是无论如何划分,这块内存区域存储的都是对象实例,进一步划分的目的是为了更好的进行回收或者更快的分配内存。
根据Java虚拟机的规定,Java堆可以在物理上不连续的内存空间上,但是逻辑上必须是连续的。在实现时,既可以是固定大小的,也可以是可扩展大小的,但是大多数虚拟机都是按照可扩展来实现的。当Java堆中没有内存完成实例分配,且堆空间无法再扩展时,就会抛出OutOfMemoryError异常。

2.4 方法区

方法区和Java堆一样,都是线程共享的内存区域,它用于存储已被虚拟机加载的常量、静态变量、类信息、即时编译器编译的代码等数据。
很多开发者喜欢把方法区叫做“永久代”,其实本质上两者并不等价。仅仅是因为HotSopt虚拟机的设计团队把GC分代收集扩展到了方法区,或者说用永久代来实现方法区,而其他虚拟机上是不存在永久代的概念的。用永久代实现方法区有一定的好处,它可以使垃圾回收器像管理Java堆一样管理方法区内存,能省去专门为这块内存编写内存管理代码的工作,但是也有一定的缺陷,因为这样更容易遇到内存溢出的问题(永久代有 -XX:MaxPermSize的内存上限,而其他虚拟机如J9,JRockit只要不触碰到进程可用的内存上限,就不会出现问题),而且有些方法(如String.intern())会因为这个原因在不同的虚拟机下有不同的表现。
Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要物理上连续的内存和可以固定大小和可扩展的大小外,还可以选择不实现垃圾回收。
根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,就抛出OutOfMemory异常。

2.5 运行时常量池

运行时常量池时方法区的一部分。Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种字面量和符号引用,这部分内容会在类被加载后进入方法区的运行时常量池中存储。
Java虚拟机对Class文件的每一个部分的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行。但对运行时常量池,Java虚拟机并没有做任何细节的要求,不同的虚拟机可以按照自己的需求来实现这个区域。不过,一般来说,除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池。

本文部分内容节选自《深入理解Java虚拟机》,推荐读者阅读。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值