Java内存区域总结(堆、栈、方法区等)

1. JVM 运行时数据区

1. 程序计数器

  • 程序计数器(Program Counter Register)是一块较小的内存空间,它能够看作是当前线程所执行的字节码的行号指示器。字节码解释器工做时就是经过改变这个计数器的值来选取下一条须要执行的字节码指令。java

  • 字节码指令、分支、循环、跳转、异常处理、线程恢复等基础功能都要依赖这个计数器来完成。数组

  • 每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储。如上图所示,咱们称这类内存区域为 : 线程私有内存。数据结构

  • 若是线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;若是正在执行的是 Native 方法,这个计数器值则为空(Undefined)。post

  • 此内存区域是惟一一个在 Java 虚拟机中没有规范任何 OutOfMemoryError 状况的区域。.net

2. Java 虚拟机栈

  • Java 虚拟机栈也是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)线程

  • 若是线程请求的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异常;设计

  • 若是虚拟机栈能够动态扩展,若是扩展时没法申请到足够的内存,就会抛出OutOfMemoryError异常;(当前大部分 JVM 均可以动态扩展,只不过 JVM 规范也容许固定长度的虚拟机栈)code

  • Java 虚拟机栈描述的是 Java 方法执行的内存模型:每一个方法执行的同时会建立一个栈帧。 对于咱们来讲,主要关注的 stack 栈内存,就是虚拟机栈中局部变量表部分。cdn

    局部变量表

  • 定义
    局部变量表(Local Variable Table)是一组变量值存储空间,用于存放方法参数方法内部定义的局部变量对象

  • 编译器肯定容量
    在Java程序编译为class文件时,就在方法的Code属性的 max_locals 数据项中肯定了 该方法所须要分配的局部变量表的最大容量。

  • 最小单位为变量槽(Slot)
    一个Slot 能够存放一个32位之内的数据类型,包括基本数据类型 (boolean、byte、char、short、int、float、long、double)「String 是引用类型」,对象引用 (reference 类型) 和 returnAddress 类型(它指向了一条字节码指令的地址)。

3. 本地方法栈

  • 与JVM栈区别
    本 地方法栈(Native Method Stack)与虚拟机栈所发挥的做用是很是类似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈为虚拟机使用到的 Native 方法服务。

  • 自由实现
    Java 虚拟机规范对本地方法栈使用的语言、使用方法与数据结构并无强制规定,所以能够由虚拟机自由实现。例如:HotSpot 虚拟机直接将本地方法栈和虚拟机栈合二为一。

  • 异常
    同虚拟机栈相同,Java 虚拟机规范对这个区域也规定了两种异常状况StackOverflowError 和 OutOfMemoryError异常。

4. 堆

  • 对于大多数应用来讲,Java 堆 (Java Heap) 是 JVM所管理的内存中最大的一块。

  • Java 堆是被全部线程共享的一块内存区域,在虚拟机启动时建立。

  • 此内存区域的惟一目的就是存放对象实例,几乎全部的对象实例都在这里分配内存。

  • 数组引用变量是存放在内存中,数组元素是存放在内存中。

  • Java 堆是垃圾收集器管理的主要区域,所以不少时候也被称做为 "GC 堆"。

  • 从内存回收的角度看,Java 堆中还能够细分为: 新生代老年代

  • 程序新建立的对象都是重新生代分配内存,新生代由 Eden Space 和两块相同大小的 Survivor Space(一般又称 S0 和 S1 或 From 和 To) 构成。
    详见JVM常见参数设置

  • 从内存分配角度,线程共享的 Java 堆可能划分出多个线程私有的分配缓冲区(TLAB)。

  • Java 堆能够处于物理不连续的内存空间中,只要逻辑是连续的便可,就像咱们的磁盘空间同样。

  • 在实现时,便可以实现成固定大小的,也能够是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的 (经过 -Xmx 和 -Xms 控制)。

  • 若是堆上没有内存完成实例分配,而且堆也没法再扩展时,将会抛出 OutOfMemoryError异常。

5. 方法区

  • 方法区 (Method Area) 与 Java 堆同样, 是各个线程共享的内存区域。

  • 它用于存储已经被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据

  • 运行时常量池 (Runtime Constant Pool) 是方法区的一部分。

  • 虽然 JVM规范把方法区描述为堆的一个逻辑部分, 可是它却又一个别名叫作 Non-Heap(非堆), 目的应该是与 Java 堆区分开来.

  • 方法区 和 永久代(Permanent Generation), 本质上二者并不相等。

    仅仅是由于 HotSpot 虚拟机的设计团队选择把 GC 分代收集扩展至方法区, 或者说使用永久代来实现方法区而已。

    这样 HotSpot 的垃圾收集器能够像管理 Java 堆同样管理这部份内容, 可以省去专门为方法区编写内存管理代码的工做。

    所以, 对于 HotSpot 虚拟机, 根据官方发布的路线图信息, 如今也有放弃永久代并逐步采用 Native Memory 来实现方法区的规划了, 在目前已经发布的 JDK1.7 的 HotSpot 中, 已经把本来放在永久代的字符串常量池移出

  • JVM规范对方法区的限制很是宽松

    和堆同样, 容许固定大小, 也容许可扩展的大小, 还能够选择不实现垃圾回收。 相对而言, 垃圾收集行为在这个区域是比较少出现的, 可是并不是数据进入了方法区就如同进入永久代的名字同样” 永久” 存在了。

    这区域的内存回收目标主要是针对常量池的回收和对类型的卸载, 通常来讲, 这个区域的回收” 成绩” 比较难以使人满意, 尤为是对类型的卸载, 条件至关苛刻, 可是这部分区域的回收确实是存在必要的。

    在 Sun 公司的 BUG 列表里, 曾出现过的若干个严重的 BUG 就是因为低版本的 HotSpot 虚拟机对此区域未彻底回收而致使内存泄漏

  • 当方法区没法知足内存分配需求时, 将抛出 OutOfMemoryError 异常。

    内存泄露和内存溢出

    内存泄露: 指程序中动态分配内存给一些临时对象,可是对象不会被 GC 所回收,它始终占用内存。即被分配的对象可达但已无用,可用内存愈来愈少。

    内存溢出: 指程序运行过程当中没法申请到足够的内存而致使的一种错误。内存溢出一般发生于老年代或永久代垃圾回收后,仍然无内存空间容纳新的 Java 对象的状况。

    内存泄露是内存溢出的一种诱因,不是惟一因素。

    运行时常量池

  • Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池 (Constant Pool Table),用于存放编译期生成的字面量和符号引用,这部份内容(也能够称为 .Class 文件中的静态常量池)将在类加载后进入方法区的运行时常量池中存放。

    字面量
    比较接近 Java 语言层面的常量概念,如文本字符串、声明为 final 的常量值等。(final 修饰的成员变量和类变量【类变量:静态成员变量】)

    符号引用
    符号引用就是字符串,这个字符串包含足够的信息,以供实际使用时能够找到相应的位置。
    你好比说某个方法的符号引用,如:“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息,方法名,方法参数等信息。
    当第一次运行时,要根据字符串的内容,到该类的方法表中搜索这个方法。
    运行一次以后,符号引用会被替换为直接引用,下次就不用搜索了。
    直接引用就是偏移量,经过偏移量虚拟机能够直接在该类的内存区域中找到方法字节码的起始位置。

  • 除了保存 Class 文件中描述的符号引用外,还会把编译出来的直接引用也存储在运行时常量池中。

  • Java 语言并不要求常量必定只有编译期才能产生,也就是并不是置入 Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的即是 String 类的 intern() 方法。

  • 运行时常量池受方法区内存的限制,当常量池没法再申请到内存时也会抛出OutofMemoryError异常。

6. 变量总结



参考来源:
周志明 《深刻理解Java虚拟机》
Java 内存区域——堆,栈,方法区等
知乎网友Intopass分享

相关文章
相关标签/搜索