Java内存区域介绍(附带JDK1.8后方法区的变化)


Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。根据《Java虚拟机规范(JavaSE7版)》的规定,java虚拟机所管理的内存将会包括以下几个运行时数据区域,如图1-1所示:
在这里插入图片描述

1.程序计数器(Program Counter Register)

  1. 程序计数器(Program CounterRegister) 是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里, 字节码解释器工作时就是通过改变这个计数器的值来选去下一跳需要执行的字节码指令, 分支, 循环, 跳转, 异常处理,线程恢复等基础功能都需要依赖这个计数器来完。
  2. 由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的, 在任何一个确定的时刻, 一个处理器(对于多核处理器来说是一个内核) 只会执行一条线程中的指令。 因此, 为了线程切换后能恢复到正确的执行位置, 每条线程都需要有一个独立的程序计数器, 各条线程之间的计数器互不影响, 独立存储, 我们称这类内存区域为"线程私有内存"。
  3. 如果线程正在执行的是一个Java方法, 这个计数器记录的是正在执行的虚拟机字节码指令的地址; 如果正在执行的是Native方法, 这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。

2.Java虚拟机栈

  1. 与程序计数器一样, Java虚拟机栈(Java Virtual Machine Stacks) 也是线程私有的, 它的生命周期与线程相同. 虚拟机栈描述的是Java方法执行的内存模型: 每个方法被执行的时候都会同时创建一个栈帧(Stack Frame 见图1-2-1) 用于存储 局部变量表, 操作栈, 动态链接, 方法出口等信息. 每一个方法被调用直至执行完成的过程, 就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程.
    在这里插入图片描述

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

  3. 其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot), 其余的数据类型只占用一个, 局部变量表所需的内存空间在编译期间完成分配, 当进入一个方法时, 这个方法需要在帧中分配多大的局部变量空间是完全确定的, 在方法运行期间不会改变局部变量表的大小。

  4. 如果线程请求的栈深度大于虚拟机所允许的深度, 将抛出StackOverflowError异常; 如果虚拟机栈可以动态扩展(当前大部分Java虚拟机都可动态拓展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当拓展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

3.本地方法栈

本地方法栈和虚拟机栈发挥的作用十分相似。同样是线程私有,它们之间的区别不过是虚拟机栈为Java 方法服务,而本地方法栈为虚拟机使用到的Native 方法服务。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError 异常和OutOfMemoryError 异常。

4.Java堆

  1. 对于大多数应用来说, Java堆(Java Heap) 是Java虚拟机所管理的内存中最大的一块。 Java堆是被所有线程共享的一块内存区域, 在虚拟机启动时创建。 此内存区域的唯一目的就是存放对象实例, 几乎所有的对象实例都在这里分配内存。 这一点在Java虚拟机规范中描述的是: 所有的对象实例以及数组都要在堆上分配, 但是随着 JIT编译器的发展与逃逸分析技术(通过分析若一个对象没有逃逸出一个方法,那么该对象在栈上分配空间,该对象随着栈的销毁而销毁)的逐渐成熟, 栈上分配, 标量替换优化技术(将部分字段使用标量存储)将会导致一些微妙的变化发生, 所有的对象都分配在堆上也逐渐变得不是那么"绝对"了。

  2. Java 堆是垃圾收集器管理的主要区域, 因此很多时候也被称做"GC堆"(Garbage Collected Heap), 如果从内存回收的角度看, 由于现在收集器基本都是采用的分代收集算法, 所以Java堆中还可以细分为: 新生代和老年代; 在细致一点的有Eden空间, From Survivor空间, To Survivor空间等。 如果从内存分配的角度看, 线程共享的Java对中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。 不过, 无论如何划分, 都与存放内容无关, 无论哪个区域, 存储的都仍然是对象实例, 进一步划分的目的是为了更好的回收内存, 或者更快的分配内存。

  3. 根据Java虚拟机规范的规定, Java堆上可以处于物理上不连续的内存空间中, 只要逻辑上是连续的即可(当空间中不连续的空间越来越多,可能会发生明明剩余空间大于需要申请的空间却申请失败), 就像我们的磁盘空间一样。 在实现时, 既可以实现成固定大小的, 也可以是可拓展的, 不过当前主流的虚拟机都是按照可拓展来实现的( 可设置-Xms 初始化堆, -Xmx 最大堆空间), 如果在堆中没有内存完成实例分配, 并且堆也无法在拓展时, 将会抛出OutOfMemoryError异常。

    有关java对象的生成和分布可以详细可以看我的另一篇文章:HotSpot虚拟机对象探秘(new之后虚拟机干了什么)

5.方法区

  1. 方法区(Method Area) 与Java堆一样, 是各个线程共享的内存区域, 它用于存储已被虚拟机加载的类信息, 常量, 静态变量, 即时编译器编译后的代码等数据。 虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分, 但是它却有一个别名叫做Non-Heap非堆, 目的应该是与Java Heap 区分开来。

  2. JDK1.7以前HotSpot虚拟机使用永久代来实现方法区,永久代的大小在启动JVM时可以设置一个固定值(-XX:MaxPermSize),不可变;

  3. JDK1.7中 存储在永久代的部分数据就已经转移到Java Heap或者Native memory。譬如符号引用(Symbols)转移到了native memory,原本存放在永久代的字符常量池移出。但永久代仍存在于JDK 1.7中,并没有完全移除。

  4. JDK1.8中进行了较大改动

    1. 移除了永久代(PermGen),替换为元空间(Metaspace);
    2. 永久代中的 class metadata 转移到了 native memory(本地内存,而不是虚拟机);
    3. 永久代中的 interned Strings 和 class static variables 转移到了 Java heap;
    4. 永久代参数 (PermSize MaxPermSize) -> 元空间参数(MetaspaceSize MaxMetaspaceSize)

6.运行时常量池

  1. 运行时常量池(Runtime Constant Pool) 是方法区的一部分(虚拟机规范)。 Class文件中除了有类的版本, 字段,方法, 接口等描述信息外, 还有一项信息是常量池(Constant Pool Table), 用于存放编译期生成的各种字面量和符号引用, 这部分内容将在类加载后存放到方法区的运行时常量池中
  2. 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性, Java语言并不要求常量一定只能在编译期产生, 也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池, 运行期间也可能将新的常量放入池中, 这种特性被开发人员利用的比较多的便是String类的intern() 方法。

7.直接内存

  1. JDK1.4 中新加入的NIO 类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O 形式,他可以使用Native 函数直接分配堆外内存,然后通过一个存储在Java 堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场所显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据
  2. 直接内存(Direct Memory) 并不是虚拟机运行时数据区的一部分, 也不是Java虚拟机规范中定义的内存区域, 但是这部分内存也被频繁地使用, 而且也可能导致OutOfMemoryError异常出现。 显然, 本机直接内存的分配不会受到Java堆大小的限制, 但是, 既然是内存, 则肯定还是会受到本机总内存的大小及处理器寻址空间的限制。 服务器管理员配置虚拟机参数时, 一般会根据实际内存-Xmx等参数信息, 但经常会忽略到直接内存, 使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制), 从而导致动态扩展时出现OutOfMemoryError异常。

参考文献:
《深入理解Java虚拟机》-周志明 著
《Java虚拟机规范》 -林德霍尔姆 著
本文仅对Java内存模型做简单介绍,主要是为了让大家有一个总体框架,这样在以后的深入学习中才会事半功倍。

  • 20
    点赞
  • 63
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值