【JVM学习总结】堆/非堆与运行时数据区域

431 阅读4分钟

堆/非堆

根据虚拟机规范,JVM的运行时内存空间存在需要用专门的对象分配和管理程序控制的区域以及不需要专门的对象分配和管理程序控制的区域。其中,堆需要专门的的对象分配和管理程序(但是规范没有规定管理程序的具体实现),而其他部分则不需要。所以可以将运行时数据区域简单划分为堆和非堆。

在这里,专门的对象分配和管理程序又称垃圾回收器。而我们的JVM的调优,也主要集中在对堆空间的控制和垃圾回收器的设定上。

运行时数据区域

  • 程序计数器
  • 方法区
  • 常量池

总体说来,程序计数器和栈是线程私有的,二者的数量等同于线程数,不同线程之间该区域相互不可见。而堆,以及多数时候被实现在堆上的方法区和常量池是线程共有的部分,对象则会被分配在堆上。(当然事不绝对,如果启用了逃逸分析和栈上分配,则通过逃逸分析的不逃逸对象会优先尝试被分配在栈上,使用TLAB优化后,分配在堆上的非共享对象会优先被创建在堆上线程私有的TLAB区域中。)

程序计数器

  • 每个虚拟机线程都有自己私有的程序计数器
  • 任何时候虚拟机线程都在执行当前方法,如果方法不是native的,那么计数器将存放当前执行指令的地址,如果方法是native的计数器就处于未定义状态
  • 计数器有足够大小存放返回的地址或者本地指针,所以计数器不会发生OOM

  • 每个虚拟机线程都有自己私有的虚拟机栈,栈会随着线程创建的同时被开辟出来 栈中存放栈帧
  • 由于虚拟机栈从不被直接操作,除非压入和弹出帧,所以帧有可能在堆中被分配 连续空间对于栈而言不是必须的
  • 虚拟机规范允许栈大小可变或者固定,如果栈大小固定,则每个栈的大小可以在创建时被选定,如果是可变的,则需要指出最大值和最小值
  • 如果线程中的计算需要的栈空间比允许值大,则虚拟机会抛出StackOverflowError
  • 如果栈空间是可以动态拓展,且拓展时发现没有足够的可用内存,或者分配新的栈时发现没有足够的可用内存,则虚拟机会抛出OutOfMemoryError

  • 堆空间为虚拟机所有线程共享,用于分配类实例和数组
  • 堆随着虚拟机启动而一同被创建
  • 堆空间内存管理由自动存储管理系统控制,又称垃圾收集器,对象不允许被直接清理,应由垃圾收集器负责清理
  • 虚拟机不强制指定某个专门的垃圾收集器,可以由虚拟机实例自行配置。
  • 堆空间可以被固定,也可以伸缩,堆占用的内存地址不需要连续
  • 如果运算需要的对空间超过了可分配空间,虚拟机会抛出OutOfMemoryError

方法区

  • 方法区为虚拟机所有线程共享
  • 方法区存放类的所有信息
  • 方法区随虚拟机启动一同创建
  • 方法区是堆空间上的一个逻辑分区
  • 规范不要求方法区是否应该被垃圾收集器管理,也不要求方法区是固定大小还是可变大小,更不要求方法区必须放在堆空间上,一切以虚拟机实例的具体实现为准
  • 分配方法区的内存地址不要求连续
  • 方法区不能安全分配空间时,虚拟机会抛出OutOfMemoryError

常量池

  • 运行时常量池是类文件中常量池表的每个类或每个接口的运行时表示(原文:A run-time constant pool is a per-class or per-interface run-time representation of the constant_pool table in a class file (§4.4).)
  • 它包含编译时可知的数字、文本到必须在运行时解析的方法和字段引用
  • 运行时常量池从方法区上分配
  • 运行时常量池随着类或者接口被虚拟机创建而一通创建
  • 当创建类或者接口时,如果运行时常量池需要的空间大于方法区可用空间,虚拟机会抛出OutOfMemoryError