Java 内存区域

时间:2019-02-19
本文章向大家介绍Java 内存区域,主要包括Java 内存区域使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
  • 以下内容为阅读 深入理解Java虚拟机(第2版)后的笔记及对 JDK8 的实践补充。看完这本书后最大的感觉就是,,,再看一遍,很多原来理解不了的知识点就可以看懂了,因为很多内容是前后呼应的。有兴趣的可以去阅读这本书,强推。

运行时数据区域

程序计数器

  • 线程私有
  • CPU 一般是分片运行的,线程转换的时候需要记录当前指令的位置,以便下一次运行时从中断处继续,这个就是程序计数器的作用。每一个线程有一个独立的程序计数器。
  • 对于 Java 方法,计数器记录的是当前指令的地址;对于本地方法,则为 null。所以程序计数器不会发生 OutOfMemoryError

Java 虚拟机栈

  • 线程私有
  • 虚拟机栈描述的是 Java 方法(不包括本地方法) 执行的内存模型

每个方法执行时都会产生一个栈帧,用于存储 局部变量表,操作数栈 等信息。方法的执行就是其相对应的栈帧在虚拟机栈中入栈和出栈的过程

本地方法栈

  • 对应于 Java 虚拟机栈,用来执行本地方法
  • 很多虚拟机实现将 Java 虚拟机栈和本地方法栈合并,如 HotSpot

Java 堆

  • 线程共享,但可以在堆中分配线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB)
  • 存放 Java 对象的位置,同时也是垃圾收集的主要区域(收集器现采用分代收集算法,将 Java 堆分成新生代和老年代等)
  • Java 堆是物理上不连续,但逻辑上连续的内存区域,类似磁盘
JVM options 作用
-Xms 堆的起始内存
-Xmx 堆的内存上限
-Xmn 堆的新生代所占内存大小,通常为堆的 1/4
-XX:+/-UseTLAB 分配缓冲区的开关,server 模式默认开启

方法区

  • 线程共享
  • 方法区和堆很像,堆存放的是普通实例对象,而方法区存放的是 Class 对象 (类的加载)。为了与堆区分开来,方法区也被称为 Non-Heap。
  • 方法区也用来存储虚拟机加载的类信息,常量,静态变量等数据。
  • 运行时常量池 是方法区中一个很重要的区域,用来存储常量 (常量可以是编译 Class 文件时产生的,也可以是运行时产生的,所以成为运行时常量池)

早期 HotSpot 使用 永久代 来实现方法区,但很容易遇到内存溢出的错误,因为永久代是固定的,可以通过 -XX:MaxPermSize 来设置上限,但不方便。如今多采用 Native Memory 来实现方法区,上限为本地内存的容量(如 32 位系统的容量为 4G),Java8 已经废弃了 永久代,改而使用 元空间 来实现方法区。

直接内存

  • 直接内存很特殊,它不算运行时数据区,但是很重要

Java 中的 nio 执行时使用的是本地内存,然后在虚拟机中通过对象来直接操作这部分内存,可理解为代理模式。这样可以避免数据在本地内存与堆内存的来回复制。这部分内存就被称为 直接内存


HotSpot 中的对象

ps:HotSpot 是 JDK 自带的虚拟机

对象创建

  1. 类加载检查

虚拟机遇到一个 new 指令时,先去常量池中检查是否含有这个类的符号引用,并且检查这个类是否已经被加载,解析与初始化过。(如果没有则先进行这些步骤)

  1. 为创建的对象分配内存
  • 对象所需要的内存大小在类加载的时候就能够确定。
  • 内存分配方式跟具体的虚拟机堆是否规范有关(如果堆内存把已用的放一边,空闲的放另一边,则堆是规整的;如果堆内存已用的和空闲的交错分布,则为不规整的。堆是否规整与收集器是否支持压缩整理功能有关,再深一点地讲,是否规整与收集器使用什么算法有关,使用复制算法和标记-整理算法为规整的,使用标记-清除算法为不规整的)。如果堆是规范的可直接将指针移动相应的大小(指针碰撞);如果堆是不规范的可在堆中维护一个空闲列表,记录堆中所有空闲的内存区域。
  • 对象在虚拟机中创建是一种非常频繁的行为,即使只是最简单的修改指针的值,在多并发情况下还是很容易出错。常见的解决方法有:
    * 通过 CAS(比较与交换)和错误重启的方式来保证更新操作的原子性;
    * 通过 TLAB(本地线程分配缓冲区)来分配内存
  1. 初始化对象中内部对象的零值,如 int 变量为 0,Object 变量为 null 等
  2. 初始化对象头(Object Header)
  3. 最后执行 <init> 方法,初始化对象结束。

对象结构

  • 对齐补充,起占位符的作用,因为 HotSpot 自动内存管理要求对象的大小是 8 个字节的整数倍。
  • 对象头
  • Mark Word,用来记录对象自身的运行时数据,包括哈希码,GC 分代年龄,锁状态标志等信息。
  • 类型指针,用来指向方法区中与对象相对应的 Class 对象。(在 使用句柄来表示引用 的虚拟机中可以不要这部分数据,而 HotSpot 使用的是直接指针,所以需要类型指针。)

    ps(句柄与直接指针):
  • 句柄中保存着 实例对象的引用类型对象的引用
  • 垃圾回收之后经常会移动对象,如果使用句柄来表示引用的话,这个时候只需要改变句柄中指向实例对象的引用。但同时因为是间接的访问引用,所以不如直接指针快。
  • 两种方式各有优缺,HotSpot 使用直接指针,也有大多数虚拟机使用句柄。
  • 实例数据