Java虚拟机的内存讲解


Java虚拟机的内存结构,区别于侧重于多线程的Java内存模型(Java Memory Model)

  

  但在此之前,我们该思考一下:JVM的内存结构为什么要这样划分?

  我认为主要是依据于不同数据的更新频率、访问速度要求、垃圾收集管理由此划分的JVM的五大内存区-- PC寄存器、JVM Stack 、Native Method Stack 、Java Heap、方法区、共享区

  下面主要详细解释一下这五个内存区:

  1.PC寄存器

  PC寄存器(Program Counter Register,程序计数器),是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

  由于JVM的多线程是通过轮流切换分配CPU执行时间的方式来实现的,在某个特定时刻,一个CPU/内核只会执行一条线程的指令。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个私有、独立的PC寄存器,各线程间互不影响,我们称这类内存区域为“线程私有”的内存。
  如果线程当前执行的是一个Java方法,PC寄存器记录的是正在执行的虚拟机字节码指令的地址
  如果线程正在执行的是native方法,它的值则为空(undefined)

  2.JVM Stack

  和PC寄存器一样,JVM Stack也是线程私有的。它的生命周期与线程相同。JVM Stack描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个Frame用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的过程,就对应着一个Frame在JVM Stack中入栈到出栈的过程

  经常有人把Java内存分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分远比这复杂。其中所指“栈”就是现在讲的JVM Stack,或者说是JVM Stack中局部变量表部分(直接越过了Stack和Frame)。

  当线程请求分配的栈容量超过JVM允许的最大容量时,将抛出StackOverflowError;

  如果JVM可动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或在建立新的线程时没有足够的内存去创建对应的JVM Stack,将抛出OutOfMemoryError。
  StackOverflowError 表示 请求 > Stack.Max
  OutOfMemoryError 表示 请求 > 可分配内存

  3.本地方法栈

  本地方法栈与JVM Stack所发挥的作用是非常相似的,区别只是JVM Stack为虚拟机执行Java方法服务,而本地方法栈是为虚拟机使用到的Native方法服务。

  在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构没有强制规定,由实现自由选择。甚至有的虚拟机(如HotSpot)直接将本地方法栈和JVM Stack合二为一。与JVM Stack一样,也会抛出StackOverflowError和OutOfMemoryError异常


  

  4.Java Heap

  对大多数应用来说,Java堆(Java Heap)是JVM所管理的内存中最大的一块,它是被所有线程共享的一块内存区域,在虚拟机启动时创建。该区的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

  Java Heap是GC管理的主要区域。由于现在收集器基本都采用了分代收集算法,所以Java Heap还细分为:新生代和老年代;再细一点的 Eden空间、From Survivor空间、To Survivor空间等。不过无论怎么划分,都与存放内容无关,无论哪个区域存储的都是对象实例。

  在实现时,既可以是固定大小的,也可以是扩展的,不过主流的虚拟机都是按照可扩展来实现的(-Xmx和-Xms)。如果堆 中没有内存完成实例分配,并且堆无法再扩展时,就会抛出OutOfMemoryError

  5.方法区

  与Java Heap一样,方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然JVM规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫Non-Heap(非堆),目的应该是与Java Heap分区来的。

  Java虚拟机规范对方法区的限制非常宽松,可以选择不实现垃圾收集。垃圾收集行为在本区是比较少出现的,但非数据进入方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说,这个区域的回收“成绩”很难令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。

  

  而运行时常量池作为方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

  Java虚拟机对Class文件每一部分(自然也包括常量池)的格式都有严格规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。

  既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时抛出OutOfMemoryError异常。

  大概就是这样了,拜!
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页