JVM运行时数据区域

JVM运行时数据区域

Java虚拟机运行时数据区

程序计数器

  • 线程私有

  • 唯一一个没有规定 OutOfMemoryError 异常 的区域

  • 它可以看作是当前线程所执行的字节码的行号指示器

  • 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)

    (摘自网上)我们想象下,CPU是怎么知道记住之前A线程,执行到哪一处的?

    答案是,CPU根本就不会记住之前执行到哪里了,它只是埋头苦干;那是什么保证了切换线程的程序可以正常执行的;答案是 : 程序计数器 ;程序计数器里面保存的是 当前线程执行的字节码的行号(看着像行号,其实是指令地址);

    那么,我们需要几个程序计数器呢?如果,我们只有一个的话,切换B线程以后,程序计数器里面保存的就是B线程所执行的字节码的行号了,再切换回A线程,就蒙圈了,不知道执行到哪里了,因为,程序计数器里面保存的是B线程当前执行的字节码地址 ;因此,我们可以想象出,要为每个线程都分配一个程序计数器,因此,程序计数器的内存空间是线程私有的 ;这样即使线程 A 被挂起,但是线程 A 里面的程序计数器,记住了A线程当前执行到的字节码的指令地址了 ,等再次切回到A线程的时候,看一下程序计数器,就知道之前执行到哪里了!

    那么程序计数器,什么时候分配内存呢?我们试想下,一个线程在执行的任何期间,都会失去CPU执行权,因此,我们要从一个线程被创建开始执行,就要无时无刻的记录着该线程当前执行到哪里了!因此,线程计数器,必须是线程被创建开始执行的时候,就要一同被创建

    程序计数器,保存的是当前执行的字节码的偏移地址(也就是之前说的行号,其实那不是行号,是指令的偏移地址,只是为了好理解,才说是行号的,),当执行到下一条指令的时候,改变的只是程序计数器中保存的地址,并不需要申请新的内存来保存新的指令地址;因此,永远都不可能内存溢出的;因此,jvm虚拟机规范,也就没有规定,也是唯一一个没有规定 OutOfMemoryError 异常 的区域;

    当线程执行的是本地方法的时候,程序计数器中保存的值是空(undefined);原因很简单:本地方法是C++/C 写的,由系统调用,根本不会产生字节码文件,因此,程序计数器也就不会做任何记录

Java虚拟机栈

  • 线程私有
  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常
  • 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常;(HotSpot虚拟机的栈容量是不可以动态扩展的,以前的Classic虚拟机倒是可以。所以在HotSpot虚拟机上是不会由于虚拟机栈无法扩展而导致OutOfMemoryError异常——只要线程申请栈空间成功了就不会有OOM,但是如果申请时就失败,仍然是会出现OOM异常的)
  • -Xss5m: 设置5m的栈容量
  • 每个方法执行都会创建一个栈帧,栈帧包含局部变量表、操作数栈、动态连接、方法出口等

本地方法栈

  • 线程私有

  • 与Java虚拟机栈相似

  • 与Java虚拟机栈区别: Java虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

  • Hot-Spot虚拟机直接就把本地方法栈和虚拟机栈合二为一

  • 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常

Java堆

  • 线程共享
  • 所有的Java对象实例不一定都在Java堆上分配内存
  • Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx-Xms设定)。
  • 如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常
  • Java堆是垃圾收集器(Garbage Collected)管理的内存区域

方法区

  • 线程共享

  • 用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

  • 虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作**“非堆”(Non-Heap),目的是与Java堆区分开来**。

  • 在JDK1.6及之前,使用永久代来实现方法区.

    • -XX:MaxPermSize 设置永久代内存上限
    • -XX:PermSize 设置永久代内存
  • JDK1.7把字符串常量池类的静态变量(class statics)转移到了java heap,但是永久代还是存在,主要放一些类信息(运算时常量池)等.

  • JDK1.8彻底移除永久代,方法区采用本地内存中实现的元空间(Meta-space)来代替,将JDK1.7中永久代的信息移到了元空间,像字符串常量池静态变量还是存在Java Heap中

  • 如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常