jvm内存区域

JVM 内存区域包括 PC计数器Java虚拟机栈本地方法栈方法区运行时常量池直接内存

JVM内存区域

JVM内存区域也称为 Java运行时数据区域。其中包括:程序计数器虚拟机栈本地方法栈静态方法区静态常量池等。

注意:程序计数器、虚拟机栈、本地方法栈属于每个线程私有的;堆和方法区属于线程共享访问的

PC计数器

程序计数器( ProgramCounterRegister)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码行号指示器

  1. 当前线程所执行的字节码行号指示器
  2. 每个线程都有一个自己的 PC计数器。
  3. 线程私有的,生命周期与线程相同,随 JVM启动而生, JVM关闭而死。
  4. 线程执行 Java方法时,记录其正在执行的虚拟机字节码指令地址
  5. 线程执行 Native方法时,计数器记录为( Undefined)。
  6. 唯一在 Java虚拟机规范中没有规定任何 OutOfMemoryError情况区域。

Java虚拟机栈

线程私有内存空间,它的生命周期和线程相同。线程执行期间,每个方法执行时都会创建一个栈帧(Stack Frame) ,用于存储 局部变量表操作数栈动态链接方法出口 等信息。

本地方法栈

本地方法栈Java虚拟机栈发挥的作用非常相似,主要区别是 Java虚拟机栈执行的是 Java方法服务,而本地方法栈执行 Native方法服务(通常用C编写)。

Java堆是被所有线程共享最大的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

Java中,堆被划分成两个不同的区域:新生代 ( YoungGeneration) 、老年代( OldGeneration) 。新生代 ( Young) 又被划分为三个区域:一个 Eden区和两个Survivor区 - FromSurvivor区和 ToSurvivor区。

简要归纳:新的对象分配是首先放在年轻代 ( YoungGeneration) 的 Eden区, Survivor区作为 Eden区和 Old区的缓冲,在 Survivor区的对象经历若干次收集仍然存活的,就会被转移到老年代 Old中。

方法区

方法区和 Java堆一样,为多个线程共享,它用于存储类信息常量静态常量即时编译后的代码等数据。

运行时常量池

运行时常量池是方法区的一部分, Class文件中除了有类的版本字段方法接口等描述信息外, 还有一类信息是常量池,用于存储编译期间生成的各种字面量符号引用

直接内存

直接内存不属于虚拟机运行时数据区的一部分,也不是 Java虚拟机规范中定义的内存区域。 JavaNIO允许 Java程序直接访问直接内存,通常直接内存的速度会优于Java堆内存。因此,对于读写频繁、性能要求高的场景,可以考虑使用直接内存。

常见内存溢出异常

除了程序计数器外, Java虚拟机的其他运行时区域都有可能发生 OutOfMemoryError的异常,下面分别给出验证:

Java堆溢出

Java堆能够存储对象实例。通过不断地创建对象,并保证 GCRoots到对象有可达路径来避免垃圾回收机制清除这些对象。 当对象数量到达最大堆的容量限制时就会产生 OutOfMemoryError异常。

设置 JVM启动参数: -Xms20M设置堆的最小内存20M-Xmx20M设置堆的最大内存最小内存一样,这样可以防止 Java堆在内存不足时自动扩容-XX:+HeapDumpOnOutOfMemoryError参数可以让虚拟机在出现内存溢出异常时 Dump内存堆运行时快照。

虚拟机和本地方法栈溢出

关于虚拟机栈和本地方法栈,分析内存异常类型可能存在以下两种:

  • 如果现场请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,可能会抛出 OutOfMemoryError异常。

可以划分为两类问题,当栈空间无法分配时,到底时栈内存太小,还是已使用的栈内存过大

方法区和运行时常量池溢出

位置存储

运行时常量字面量都存放于运行时常量池中,常量池又是方法区的一部分,因此两个区域的测试是一样的。

方法区存放 Class相关的信息,比如类名访问修饰符常量池字段描述方法描述等。