JVM内存模型


Java内存模型

由于计算机上的内存模型涉及到物理的主内存、高速缓存和寄存器等。这些不同的计算机不同的操场系统可能会存在差异,Java虚拟机规范中试图定义一种Java内存模型,来屏蔽掉各种硬件和操作系统的内存访问差异,让Java程序在各个平台下都能达到一致的访问效果。

主内存与工作内存

Java内存模型规定了所有变量都存储在主内存内(主内存包括方法区和堆),此处主内存隶属于Java虚拟机内存的一部分,而虚拟机内存是操作系统分配的。每条Java线程还有自己的工作内存,工作内存中保存了被该线程使用到的变量的主内存的副本,线程对变量的所有操作都在工作内存中进行,Java线程之间的变量值传递都通过主内存来完成。

内存间的交互

关于主内存和工作内存间的交互协议,即一个变量如何从工作内存拷贝到工作内存、又是如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了8种操作,这8种操作实现时必须保证每一种操作都是原子的、不可再分的,其中前4条是作用于主内存,后4条作用于工作内存:

  • lock锁定:将一个变量标识为线程独占状态
  • unlock解锁:将锁定状态的变量解除锁定,释放后的变量才可以被其他变量锁定
  • read读取:将变量从主内存传输到线程的工作内存中,待之后的load加载
  • write写入:把store操作从工作内存中得到的变量值写入主内存的变量中
  • load加载:将read后从主内存得到的变量值加载到工作内存的变量副本中
  • use使用:把工作内存中的一个变量值传递给字节码执行引擎,等待字节码指令使用
  • assign赋值:把一个从执行引擎接收到的值赋值给工作内存的变量
  • store存储:把工作内存中一个变量的值传送到主内存中,以便随后的write使用

volatile关键字

volatile关键字具有以下两种特性:

  • 保证此变量对所有变量的可见性
  • 禁止指令重排序优化

对于第一种特性,即volatile关键字保证了新值能立即同步到主内存,以及每个线程在每次使用volatile变量前都立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。

而对于第二种特性,volatile关键字本身就包含了禁止指令重排序的语义。

运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。其中程序计数器、JVM栈、本地方法栈是线程私有的,而方法区和堆是所有线程共享的。

运行时数据区域

程序计数器

一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器:如果线程正在执行一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行Native方法,这个计数器值则为空。

Java虚拟机栈

Java虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表:存放了编译器可知的各种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配。

动态链接:动态链接是指编译系统在链接阶段并不把目标文件和函数库文件链接在一起,是等到程序在运行过程中需要使用时才链接函数库。

操作数:参与运算的常量或者变量称为操作数。

该区域可能抛出以下异常:

  • 当线程请求的栈深度超过最大值,会抛出StackOverflowError异常。
  • 栈进行动态扩展时如果无法申请到足够内存,会抛出OutOfMemoryError异常。

本地方法栈

本地方法栈与Java虚拟机栈类似,它们的区别只不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈为本地方法服务。该区域可能抛出的异常与Java虚拟机栈一样。

Java堆

Java堆是Java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

Java堆是垃圾收集器管理的主要区域,还可以细分为新生代和老年代。

一般情况下,新创建的对象都会存放到新生代中。

在新生代每进行一次垃圾收集后,就会给存活的对象“加1岁”,当年龄达到一定数量的时候就会进入老年代,另外,比较大的对象也会进入老年代。

Java堆不需要物理上连续的内存空间,逻辑上连续即可。如果堆中没有内存完成实例分配且堆也无法再扩展时,将抛出OutOfMemoryError异常。

方法区

方法区常被称为“永久代”,也是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败将抛出OutOfMemoryError异常。

运行时常量池

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

相较于Class文件常量池,运行时常量池更具动态性,在运行期间也可以将新的变量放入常量池中,而不是一定要在编译时确定的常量才能放入,这种特性用的比较多的便是String类的intern()方法。

控制参数汇总

可以通过如下参数来控制各区域的内存大小:

-Xms设置堆的最小空间大小

-Xmx设置堆的最大空间大小

-XX:NewSize设置新生代最小空间大小

-XX:MaxNewSize设置新生代最大空间大小

-XX:PermSize设置永久代最小空间大小

-XX:MaxPermSize设置永久代最大空间大小

-Xss设置每个线程的堆栈大小

参考资料

  • 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值