JVM的内存区域划分

  • 方法区:存放常量池,类元信息等(JDK8之前),JDK8之后,方法区不存在了,常量池存放在堆中,类元信息存放在MetaSpace中
  • 程序计数器:记录了线程当前执行的代码行数
  • 虚拟机栈:存放一个个栈帧,每个栈帧就是一个方法的调用,栈帧中存放了局部变量表等信息
  • 本地方法栈:同虚拟机栈,不过这个是调用本地方法(native)的
  • 堆:存放对象,几乎所有的new出来的对象都存放在这里
  • 直接内存:NIO中allocteDirect这种方式分配的内存
  • 执行引擎

当一个Java进程启动后,

  • 首先会执行类加载,类的元信息会存放到方法区(metaSpace)
  • 接着会由字节码执行引擎来执行编译后的字节码指令
  • 然后找到入口方法main,此时会有一个程序计数器,用于标记当前线程执行的代码行数
  • 接着会有一个虚拟机栈,用于存放方法调用的栈帧,每次调用方法,就会将一个栈帧压入虚拟机栈,栈帧中存放了局部变量等信息
  • 当在方法中实例化对象时,被new出来的对象都会被放到中,同时栈帧中保存了实例对象的内存地址
  • 当方法调用完毕后就会将对应的栈帧出栈

线程隔离的区域

可以理解为不会发生线程安全问题的内存区域,即这些区域中不会有线程共享的数据

程序计数器

线程执行的行号指示器,标记了线程当前执行的代码行数

如果线程正在执行一个Java方法,那么该计数器记录的是虚拟机字节码指令的地址,如果线程正在执行一个本地方法,则该计数器的记录值为undefined

该区域是唯一一个不会发生OOM的区域

虚拟机栈

在Java中,线程每执行一个Java方法,都会向线程独有的虚拟机栈中压入一个栈帧,该栈帧用于存放如局部变量表操作数,动态链接方法出口等信息。当方法调用完毕之后,栈帧会出栈(虚拟机栈)

局部变量表

存放编译期可知的各种Java基本数据类型对象引用returnAddress类型,他们在局部变量表中的存放形式以局部变量槽(slot)来表示,64位长度的数据会占用两个槽

该区域内存在编译期就被分配好了

本地方法栈

与虚拟机栈类似,区别在于本地方法栈存放的是本地方法(native method)的相关信息

非线程隔离的区域

会产生线程共享的数据,会引发线程安全问题

方法区

方法区是某些Java虚拟机的实现(如:HotSpot虚拟机)中独有的概念,它用于存储类元数据,常量,静态变量,即时编译器编译后的代码缓存等数据

JDK8及之后的版本中,方法区被拆分,运行时常量池存放于堆中,类元数据(类的版本,字段,方法,接口等描述信息)存放于Metaspace(元空间),并且使用了物理内存

如果该区域空间不足,并且还有新的数据需要存放进来,并且空间无法被扩展,会抛出OOM异常

运行时常量池

方法区的一部分,用于存放编译期生成的各种常量与引用

该区域中如果内存不足以存放常量,静态变量等数据,也会抛出OOM异常

也被称为GC堆,在Java中,垃圾回收的最主要的区域就在堆中,一般是使用分代回收的算法来进行垃圾回收

几乎所有的Java对象都会被分配在堆中,便于JVM对其进行管理

根据分代回收算法的规定,整个Java堆被划分为两个区域,新生代和老年代,其中新生代又被划分为Eden区From Survivor区`和To Survivor区,堆中的新建对象首先会在新生代的Eden区被分配

从内存分配的角度来看,线程共享的Java堆又可以被划分出多个分配缓冲区TLAB(ThreadLocal Allocation Buffer),用于提升对象分配的效率

如果在分配对象内存时发现堆内存不足,并且通过垃圾回收,堆扩展后空间也不足以用于创建对象,那么就会抛出OOM异常

直接内存

该区域不属于Java虚拟机运行时数据区域的一部分,但是这部分内存也经常被使用,如nio中的allocateDirectBuffer方法就是使用了这块内存,并且这块内存也可能出现OOM异常