Java内存区域
Java内存区域
1.Java内存区域划分
JVM在执行Java程序的过程中会把它管理的内存区域划分成两类用途不同的数据区:
-
线程共有(线程共享)区域——堆和方法区
-
线程私有(线程隔离)区域——程序计数器、虚拟机栈和本地方法栈
2.线程共享数据区
堆
用途 | 完成几乎所有Java实例对象的内存分配,进行垃圾回收的主要内存区域 |
---|---|
划分 | 年轻代8 : 1 : 1(Eden区 : From Survivor 0 : To Survivor 1 区) 、老年代 |
内存空间大小 | 大 |
垃圾回收模式 | 以主流的HotSpot为例,采用分代回收 策略,对于年轻代的对象采取复制算法,对老年代采取标记-清除或者标记-整理算法 |
异常情况 | 堆中没完成实例分配,且堆此时无法再扩展,JVM会抛出OOM 异常 |
对象分配策略
当有对象需要分配时,一个对象永远优先被分配在年轻代的 Eden 区
,等到 Eden 区域内存不够时,Java 虚拟机会启动垃圾回收。此时 Eden 区中没有被引用的对象的内存就会被回收,在一次新生代垃圾回收后,Eden区中所有存活的对象都会被复制
到To Survivor区,而在From Survivor区中,仍存活的对象会根据它们的年龄值决定去向
,年龄值达到年龄阀值
(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到To Survivor区。接着清空Eden区和From Survivor区,新生代中存活的对象都在To Survivor区。接着, From Survivor区和To Survivor区会交换它们的角色
,也就是新的To Survivor区就是上次GC清空的From Survivor区,新的From Survivor区就是上次GC的To Survivor区,总之,不管怎样都会保证To Survivor区在一轮GC后是空的。GC时当To Survivor区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。
方法区
用途 | 存储已被虚拟机加载的类的结构信息(字段和方法数据、构造方法)、常量、静态变量 、即时编译器编译后的代码缓存等数据,像是堆的一个逻辑部分 |
---|---|
异常情况 | 方法区无法满足新的内存分配需求时,抛出OOM异常 |
3.线程私有数据区
程序计数器
用途 | 指示当前线程所执行的字节码的行号指示器 。字节码解释器工作时就是通过改变程序计数器的值来选取下一条需要执行的字节码指令,程序控制中的分支、循环、跳转、异常处理、线程恢复 等都依赖程序计数器 |
---|---|
私有性质 | JVM多线程通过线程切换并分配CPU时间实现的,任一确定的时刻,一个CPU指挥执行一条线程中的指令。所以为了线程切换后能恢复到正确的执行位置,每个线程都需要一个独立的程序计数器。 |
内存空间大小 | 小 |
生命周期 | 随着线程的创建而创建,随着线程的消亡而消亡 |
真实值 | 如果执行的是Java方法 ,那么计数器记录的就是正在执行的JVM字节码指令的地址 ;如果执行的是本地(Native)方法 ,那么计数器的值则应该为空(Undefined ) |
异常情况 | 唯一一个不会出现 OutOfMemoryError(OOM) 的内存区域 |
虚拟机栈
含义 | 描述Java方法执行的线程内存模型 ,每个Java方法被执行时,虚拟机都会同步创建一个栈帧 |
---|---|
组成 | Java虚拟机栈由一个个栈帧 组成 |
栈帧 | 存储了局部变量表 、操作数栈、动态链接、方法出口等信息 |
局部变量表 | 主要存放了编译期可知的各种基本数据类型 (boolean、byte、char、short、int、float、long、double)、对象引用 (reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。 |
生命周期 | 和线程相同 |
异常情况 | 如果线程请求的栈深度大于虚拟机允许的最大深度,就会抛出StackOverflowError 异常(栈溢出异常);如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError 异常 |
本地方法栈
为虚拟机使用到的本地方法服务。和Java虚拟机栈发挥的作用非常相似,和虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈动态扩展失败时分别抛出StackOverflowError
异常和OutOfMemoryError
异常。