JVM内存模型(JMM)

目录

一、运行时数据区域划分 

​编辑

二、线程私有的 

 1、程序计数器

2、虚拟机栈(VM Stack) 

3、本地方法栈 

三、线程公有的 

1、堆

2、元空间 

 

Java程序把内存控制权利交给JVM虚拟机,一旦出现内存泄漏和溢出方法的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务,所以我们就需要来了解一下JVM内存模型。

一、运行时数据区域划分 

JVM虚拟机在执行Java程序的过程中会把它管理的内存划分成若干个不同的数据区域,JDK1.8前和JDK1.8后又有不同的划分

如图所示JDK1.8之前分为:

  • 线程共享(Heap堆区、Method Area方法区);
  • 线程私有(虚拟机栈、本地方法栈、程序计数器)

JVM内存模型(JMM)_第1张图片

 如图所示为JDK1.8之后:

  • 线程共享(Heap堆区、MetaSpace元空间)
  • 线程私有(虚拟机栈、本地方法栈、程序计数器)

JVM内存模型(JMM)_第2张图片

二、线程私有的 

 1、程序计数器

程序计数器是一块较小的内存空间,是当前线程所执行的字节码的行号指示器

 程序计数器的作用:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候,能够知道当前线程的运行位置。

注意:程序计数器是唯一一个不会出现OutOfMemoryError 的内存区域,它随着线程的创建而创建,随着线程的结束而死亡。

2、虚拟机栈(VM Stack) 

 它的生命周期和线程相同,用于描述Java方法执行时的内存模型,每次方法调用的数据都是通过栈传递的

JMM内存区域可以粗略的区分为堆内存和栈内存。其中栈就是VM Stack虚拟机栈,或者说是虚拟机栈中局部变量表部分。

Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。 

JVM内存模型(JMM)_第3张图片

 每一次方法调用都会有一个对应的栈帧被压入 VM Stack 虚拟机栈,每一个方法调用结束后,代表该方法的栈帧会从VM Stack 虚拟机栈中弹出来

在活动线程中, 只有位于栈顶的帧才是有效的, 称为当前活动栈帧,代表正在执行的当前方法。

JVM执行引擎运行时, 所有指令都只能针对当前活动栈帧进行操作。虚拟机栈通过 poppush的方式,对每个方法对应的活动栈帧进行运算处理,方法正常执行结束,肯定会跳转到另一个栈帧上。

JVM内存模型(JMM)_第4张图片 

Java方法有两种返回方式,不管哪种返回方式都会导致当前活动栈帧被弹出

  • return 语句
  • 抛出异常 

Java虚拟机栈会出现两种错误:StackOverFlowErrorOutOfMemoryError 

  • StackOverFlowError 当线程请求栈的深度超过 JVM虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。
  • OutOfMemoryError JVM的内存大小可以动态扩展, 如果虚拟机在动态扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

3、本地方法栈 

native关键字修饰的本地方法被执行的时候,在本地方法栈中也会创建一个栈帧,用于存放该native本地方法的局部变量表、操作数栈、动态链接、方法出口信息。方法执行完毕后,相应的栈帧也会出栈并释放内存空间。也会出现 StackOverFlowErrorOutOfMemoryError两种错误。 

三、线程公有的 

1、堆

Heap堆是JVM 所管理的内存中最大的一块区域,被所有线程共享的一块内存区域。堆区中存放对象实例,“几乎”所有的对象实例以及数组都在这里分配内存。

堆区又可以划分为新生代老年代 。目的是更好的回收内存,或者更快的分配内存。

JVM内存模型(JMM)_第5张图片

因为新生代是由 Eden + s0 + s1 组成的,所以按照上述默认比例,如果 Eden 区内存大小是 40M,那么两个 Survivor 区就是 5M,整个新生代区就是 50M,然后可以算出 老年代Old 区内存大小是 100M,堆区总大小就是 150M。 

创建对象的内存分配 

创建一个新对象,在堆中的分配内存。

大部分情况下,对象会在 Eden 区生成,当 Eden 区装填满的时候,会触发 Young Garbage Collection,即 YGC垃圾回收的时候,在 Eden 区实现清除策略,没有被引用的对象则直接回收。

依然存活的对象会被移送到 Survivor 区。Survivor 区分为 s0 s1 两块内存区域。每次 YGC的时候,它们将存活的对象复制到未使用的Survivor 空间(s0s1),然后将当前正在使用的空间完全清除,交换两块空间的使用状态。每次交换时,对象的年龄会加+1

如果 YGC 要移送的对象大于 Survivor 区容量的上限,则直接移交给老年代。一个对象也不可能永远呆在新生代,在 JVM 中 一个对象从新生代晋升到老年代的阈值默认值是 15,可以在 Survivor区交换 14 次之后,晋升至老年代。

JVM内存模型(JMM)_第6张图片

堆区最容易出现的就是 OutOfMemoryError错误,这种错误的表现形式会有以下两种:

  1. OutOfMemoryError: GC Overhead Limit Exceeded JVM花太多时间执行垃圾回收,并且只能回收很少的堆空间时,就会发生此错误。
  2. OutOfMemoryError: Java heap space假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发此错误。

 

2、元空间 

用于存放类信息、常量、静态变量、JIT即时编译器编译后的机器代码等数据等。例如:java.lang.Object类的元信息、Integer.MAX_VALUE常量等。

元空间的本质和永久代类似,都是对JVM规范中方法区的一种具体实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过运行参数来指定元空间的大小。

Java 8PermGen永久代为什么被移出 HotSpot JVM

  • 由于 PermGen 内存经常会溢出,容易抛出 java.lang.OutOfMemoryError: PermGen错误;
  • 移除 PermGen 可以促进 HotSpot JVMJRockit VM 的融合,因为 JRockit 没有永久代;

 

你可能感兴趣的:(jvm)