JVM内存区域
一、内存区域分布图
JVM的内存区域分为线程私有区域(程序计数器、虚拟机栈、本地方法区)、线程共享区域(堆、方法区)和直接内存。
- 线程私有区域随着线程的启动而创建,随线程的结束而销毁。
- 线程共享区域随虚拟机的启动而创建,随虚拟机的关闭而销毁。
二、区域详解
- 1.程序计数器
程序计数器是线程私有的一块内存空间,用于存储当前运行的线程所执行的字节码的行号指示器,每一个运行的线程都有一个独立的程序计数器,是唯一没有内存溢出的一块区域。
- 2.虚拟机栈
虚拟机栈是描述java方法的执行过程的内存模型,它在当前栈帧存储了局部变量表、操作数栈、动态链接、方法出口以及运行时数据及其数据结构等信息。每个运行中的线程当前只有一个栈帧处于活动状态。
- 3.本地方法区
本地方法区也是线程私有的,与虚拟机栈的作用类似,区别是虚拟机栈为执行Java方法服务,本地方法栈为Native方法服务。
- 4.堆(运行数据区)
在JVM运行过程中创建的对象和产生的数据都被存储在堆中,是被线程共享的区域,也是垃圾收集器进行垃圾回收的最主要的内存区域。因此从垃圾回收(GC)的角度来看,Java堆还可以被细分为:新生代、老年代和永久代
- 5.方法区
- 方法区是线程共享的,也被称为永久代。方法区常被用来存储常量、静态变量、类信息、即时编译后机器码、运行时常量池等数据。
- 永久代的内存回收主要针对常量池的回收和类的卸载,因此可回收的对象很少。
三、JVM的运行时内存
- JVM的运行时内存也叫做JVM堆,从GC的角度可以将JVM堆分为新生代、老年代和永久代。其中新生代默认占1/3堆内存空间,老年代默认占2/3堆内存空间,永久代占很少的堆内存空间。
- 新生代又分为Eden区(默认占8/10新生代空间)、SurviorFrom区(占1/10)、SurviorTo区(占1/10新生代空间)。
(1)新生代:
- Eden区:Java新创建的对象首先会被存放在Eden区,如果创建的对象属于大对象,则直接将其分配到老年代。在Eden区的内存空间不足时会触发MinorGC,对新生代进行一次垃圾回收。
- SurviorTo区:保留上一次MinorGC时的幸存者。
- SurviorFrom区:将上一次的MinorGC的幸存者作为这次MinorGC的被扫描者。
新生代的垃圾回收规程叫作MinorGC,采用复制算法实现,具体过程如下:
- 把Eden区和SurviorFrom区中存活的对象复制到SurviorTo区。如果某对象的年龄达到老年代的标准则将其复制到老年代,同时把这些对象的年龄都加1;如果SurviorTo区的内存空间不够,则也可以将其直接复制到老年代去;如果属于大对象,也可以直接复制到到老年代。
- 清空Eden区和SurviorFrom区中的对象。
- 将SurviorFrom区和SurviorTo区互换,原来的SurviorTo区作为下一次GC的From区。
(2)老年代:
老年代主要存放的生命周期长的对象和大对象。老年代的GC叫作MajorGC。老年代的对象比较稳定,不会频繁回收。在进行MajorGC前首先会进行一次MinorGC,如果MinorGC后仍然出现老年代空间不足或无法找到足够大的连续内存空间分配给大对象的情况时会触发MajorGC,释放JVM内存空间。
(3)永久代:
永久代主要存储的是Class和Meta(元数据)的信息。Class在类加载的时候被放入永久代。永久代不会在程序运行期间对内存进行清理,这也就导致了永久代的内存会随着加载的Class文件的增加而增加,过多时会抛出Out Of Memory异常。
在Java8时,永久代已经被元数据区取代。二者最大的区别在于,元数据区并没有使用JVM的内存,而是直接使用的是操作系统的本地内存。(所以能加载多少元数据的信息和由操作系统的实际可用内存空间决定。)