(一)程序运行时,内存到底是如何进行分配的?

148 阅读5分钟

JVM 中的内存区域划分


Ciqah158SbeAQdCSAAEEJ1oi53Y731.png

主要分为:程序计数器、虚拟机栈、本地方法栈、堆、方法区


程序计数器(Program Counter Register)
  1. 用于记录当前线程执行的位置
  2. 没有规定任何 OutOfMemoryError 情况(内存溢出)
  3. 每条线程内部都有一个私有程序计数器
  4. 生命周期随着线程的创建而创建,随着线程的结束而死亡

一些我们熟悉的恢复线程、分支操作、循环操作、跳转、异常处理等也都需要依赖这个计数器来完成


虚拟机栈
  1. 用来描述 Java 方法执行的内存模型
  2. 每个方法被执行的时候,JVM 都会在虚拟机栈中创建一个栈帧
  3. 虚拟机栈也是线程私有的,与线程的生命周期同步

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,每一个线程在执行某个方法时,都会为这个方法创建一个栈帧

Ciqah157F6SAJiwFAACto3B4htg907.png

一个线程包含多个栈帧,而每个栈帧内部包含局部变量表、操作数栈、动态连接、返回地址等

局部变量表
  1. 局部变量表是变量值的存储空间
  2. 方法内部创建的局部变量都保存在局部变量表中

系统不会为局部变量赋予初始值(实例变量和类变量都会被赋予初始值)

操作数栈
  1. 操作数栈(Operand Stack)也常称为操作栈,它是一个后入先出栈(LIFO)
  2. 栈中的元素可以是任意Java数据类型

当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的。在方法执行的过程中,会有各种字节码指令被压入和弹出操作数栈

动态链接
  1. 主要目的是为了支持方法调用过程中的动态连接(Dynamic Linking)

Java 虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的就是为了支持方法调用过程中的动态连接(Dynamic Linking)

返回地址
  1. 用来帮助当前方法返回到方法被调用的位置

一般来说,方法正常退出时,调用者的 PC 计数值可以作为返回地址,栈帧中可能保存此计数值。而方法异常退出时,返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息。


本地方法栈

本地方法栈和虚拟栈基本相同,只不过是针对本地(native)方法。在开发中如果涉及 JNI 可能接触本地方法栈多一些,在有些虚拟机的实现中已经将两个合二为一了(比如HotSpot)。


  1. 该区域唯一目的就是存放对象实例
  2. 是 JVM 管理的内存中最大的一块
  3. 是 Java 垃圾收集器(GC)管理的主要区域
  4. 被各个线程共享的内存区域

它也是所有线程共享的内存区域,因此被分配在此区域的对象如果被多个线程访问的话,需要考虑线程安全问题。

Cgq2xl57GDCAVkHYAABVMCBYUEE302.png

按照对象存储时间的不同,堆中的内存可以划分为新生代(Young)和老年代(Old),其中新生代又被划分为 Eden 和 Survivor 区。不同的区域存放具有不同生命周期的对象,这样可以根据不同的区域使用不同的垃圾回收算法,从而更具有针对性,进而提高垃圾回收效率


方法区
  1. 方法区主要是存储已经被 JVM 加载的类信息(版本、字段、方法、接口)、常量、静态变量、即时编译器编译后的代码和数据
  2. 被各个线程共享的内存区域

异常
StackOverflowError 栈溢出异常

递归调用是造成StackOverflowError的一个常见场景。

如果在方法中,递归调用了自身,并且没有设置递归结束条件,则会产生StackOverflowError。

原因就是每调用一次方法时,都会在虚拟机栈中创建出一个栈帧。

因为是递归调用,方法并不会退出,也不会将栈帧销毁,所以必然会导致StackOverflowError。

因此当需要使用递归时,需要格外谨慎。

OutOfMemoryError 内存溢出异常

1.理论上,虚拟机栈、堆、方法区都有发生OutOfMemoryError的可能。

2.实际项目中,大多发生于堆当中

例如在一个无限循环中,动态的向ArrayList中添加新的HeapError对象。

这会不断的占用堆中的内存,当堆内存不够时,必然会产生OutOfMemoryError


总结

Ciqah157GD2AYLFtAADxheNgCA0454.png

JVM的运行时内存结构中一共有两个“栈”和一个“堆”

分别是:Java虚拟机栈和本地方法栈,GC堆 和 方法区,还有程序计数器

JVM 内存中只有堆和方法区是线程共享的数据区域,其它区域都是线程私有的

程序计数器是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域

程序计数器、虚拟机栈、本地方法栈 3 个区域随线程而生,随线程而灭

栈中的栈帧随方法的进入和退出进行出栈和入栈操作