JVM内存模型所指的是JVM运行时区域,该区域分为两大块
- 线程共享区域
堆内存、方法区,即所有线程都能访问该区域,随着虚拟机和GC创建和销毁
- 线程独占区域
虚拟机栈、本地方法栈、程序计数器,即每个线程都有自己独立的区域,该区域随着线程的生命周期创建和销毁
1. 堆
堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,该内存区域存放了对象实例及数组。
1.1 堆区域划分
堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )和永久代(jdk1.8之后移除了永久代,引入了元空间,元空间引用的是本地直接内存,而非虚拟的内存);新生代和老年代的比例是1:2,新生代 ( Young ) 又被划分为三个区域:伊甸区(Eden)、幸存区0(Survivor0)、幸存区1(Survivor1),三者的比例是8:1:1
1.2 堆GC过程
- 一般新创建的对象都会存放在伊甸区(Eden);
- 当Eden空间不够时,会进行Minor GC,GC后存活的对象会进入Survivor之一;
- 之后每次Minor GC,都会将Eden和Survivor中存活的对象一次性复制到空闲的Survivor,然后回清空掉之前的Survivor并且幸存的对象年龄+1,当对象年龄达到15的时候会进入到老年代;
- 当空闲中的Survivor空间不够存放活下来的对象时,这些对象会通过分配担保机制直接进入老年代
- 当老年代空间不足时会触发Full GC
2. 方法区
方法区包含类元信息、静态变量、常量池,在JDK1.8之前该区域叫永久代,是JVM内存中的一部分,在JDK1.8之后该区域叫元空间,该区域使用的是本地内存,静态变量和常量池已经移到堆内存中存放了
3. 虚拟栈
JVM的每一个线程对应一个线程栈,一个线程的每个方法会分配一块栈帧内存空间。栈帧中包含:局部变量表、操作数栈、动态链接和方法出口,虚拟机栈会抛出StackOverflowError 和OutOfMemoryError异常。
- 局部变量表:存储基本数据类型(int、float、byte等),如果是引用数据类型,则存储的是其在堆中的内存地址,也就是指向对象的一个指针。
- 操作数栈:操作数运算时一块临时的空间来存放操作数。
- 动态链接:将代码的符号引用转换为在方法区(运行时常量池)中的直接引用。
- 方法出口:存储了栈帧中的方法执完之后回到上一层方法的位置。
4. 本地方法栈
和虚拟机栈功能上类似,它管理了native方法的一些执行细节,而虚拟机栈管理的是Java方法的执行细节。
与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。
5. 程序计数器
在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,为了各条线程之间的切换后计数器能恢复到正确的执行位置,所以每条线程都会有一个独立的程序计数器。
当线程正在执行一个Java方法,程序计数器记录的是正在执行的JVM字节码指令的地址;如果正在执行的是一个Natvie(本地方法),那么这个计数器的值则为空(Underfined)。
程序计数器占用的内存空间很少,也是唯一一个在JVM规范中没有规定任何OutOfMemoryError(内存不足错误)的区域