jvm内存模型
JVM的五大内存区域
1、程序计数器
2、方法区(也称为永久代,后续被Metaspace取代)
3、虚拟机栈
4、本地方法栈
5、堆
1、程序计数器
记录线程的执行位置,线程私有内存
在多线程的情况下,线程被切换回来的时候能够知道该线程上次运行到哪儿了
2、方法区
方法区是所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量
放在永久代中经常会出现内存溢出,即PermGen Space异常,所以用元空间取而代之
元空间直接使用本地内存,理论上电脑有多少内存它就可以使用多少内存,所以不会再出现PermGen Space异常
java虚拟机对方法区比较宽松,可以选择不实现垃圾收集
3、虚拟机栈(stack)
每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡.
Java虚拟机栈会为每一个即将运行的Java方法创建“栈帧”,用于存储局部变量表、操作栈、动态连接、方法返回地址等
每个方法从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程
如果方法A调用了方法B,那么A就会先入栈创建一个栈桢,接着B再入栈成为栈顶,B执行完先出栈,接着A执行完出栈。
Java虚拟机栈可能出现两种类型的异常:
1、线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
2、虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。
4、本地方法栈
本地方法栈和Java虚拟机栈实现的功能与抛出异常几乎相同
只不过虚拟机栈是为虚拟机执行Java方法服务,本地方法栈则为虚拟机使用到的本地方法服务.
线程开始调用本地方法时,会进入一个不再受JVM约束的世界
本地方法可以通过JNI(Java Native Interface)来访问虚拟机运行时的数据区
5、堆(heap)
堆区是所有线程共享的内存区域
堆是OOM故障最主要的发源地,它存储着几乎所有的实例对象
通常情况下,它占用的空间是所有内存区域中最大的,但如果无节制地创建大量对象,也容易消耗完所有的空间
堆的内存空间既可以固定大小,也可运行时动态地调整,通过如下参数设定初始值和最大值
-Xms256M. -Xmx1024M
其中,-X表示它是JVM运行参数,ms是memorystart的简称 最小堆容量,mx是memory max的简称 最大堆容量
但是在通常情况下,服务器在运行过程中,堆空间不断地扩容与回缩,势必形成不必要的系统压力,
所以在线上生产环境中,JVM的Xms和Xmx设置成一样大小,避免在GC后调整堆大小时带来的额外压力
堆分成两大块:新生代和老年代(默认空间比例为1:2)
新生代分为三块:Eden区、From Survivor区和To Survivor区(默认空间比例为8:1:1)
对象产生之初在新生代,步入暮年时进入老年代,但是老年代也接纳在新生代无法容纳的超大对象
Survivor区每个对象都有一个计数器,每次YGC都会加1,默认加到15次,就要移送到老年代
Eden区 -> Survivor 0区 -> Survivor 1区 -> 老年区的过程
1、所有对象都在伊甸园出生,当伊甸园占满时,开始进行一次Young GC,此次GC会将已存活的对象复制到S0区中
2、伊甸园区又被占满时,此时又进行一次Young GC,伊甸园存活的对象又复制到S0区。
3、在若干次Young GC后,幸存区S0也满了,此时Young GC会对伊甸园和幸存区S0的做一次垃圾回收,将两个区存活的对象复制到幸存区S1中,再把伊甸园和S0清空,最后把S1的内存与S0交换,此时S1又腾空了,S0剩下一些老对象。
4、又经历若干次GC,幸存区S0已经放满了经历过N次GC都回收不了的老对象,此时会将老对象复制到老年代中,腾空伊甸园和幸存区。
(并非当幸存区被老对象占满才复制到老年代中,当老对象年龄达到15岁,即经历过15次GC都还活着的,也会复制到老年代中)
(另外伊甸园中如果诞生了一个比幸存区还大的对象,那么该对象回收不了时,也会直接送入到老年代中)
5、又经历过若干次GC后,老年代也满了,那么此时它会进行一次Full GC。
6、当执行Full GC后,老年代还是放不下新来的对象时,就会抛出OOM
堆特点
Java虚拟机所需要管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建
堆是垃圾回收的主要区域,所以也被称为GC堆.
当线程请求分配内存,但堆已满,且内存已满无法再扩展时,就抛出OutOfMemoryError