JVM—— 运行时内存结构
Posted dogelife
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM—— 运行时内存结构相关的知识,希望对你有一定的参考价值。
JVM 在执行 Java 程序的过程中会把它所管理的物理内存划分成不同的内存区域,每一个区域都存放着不同的数据。每个区域也都有不同的用途,以及创建和销毁的时机,根据虚拟机规定,可以获得这样的 JVM 内存结构。
1 程序计数器
程序计数器(Program Counter Register)是一块比较小的内存区域,可以看作是当前线程所执行的字节码的行号指示器。就是用于存放当前线程接下来将要执行的字节码指令、分支、循环、跳转、异常等信息。在任何时候,一个处理器只能执行一个线程中的指令,为了能让 CPU 时间片轮转切换上下文之后顺利回到正确的执行位置,每条线程都有自己的独立的程序计数器,这样各个线程之间就互不影响,因此这块区域被JVM设计为线程私有的。
此内存区域是唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
2 Java 虚拟机栈
与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,与线程的生命周期相同。虚拟机栈描述 Java方法执行的内存模型:每个方法在执行的同时都会创建一个独特的数据结构:栈帧(Stack Frame)。它主要用来存储局部变量表、操作数栈、动态链接、方法出口等信息。方法的调用就对应着栈帧在虚拟机栈中的压栈与出栈过程。
每一个线程在创建的时候,JVM 都会为其创建对应的虚拟机栈,同等大小的虚拟机栈如果局部变量表等内存信息越小,则被压入的栈帧就会越多,反之被压入的栈帧就会越少,一般可以将栈帧内存的大小称为宽度,栈帧的数量称为虚拟机栈的深度。可以把一个虚拟机栈想象成为面积固定的长方形,宽就是栈帧内保存的信息的大小,长就是栈帧的数量,也就是虚拟机栈的深度,当宽变小了,那么长就变大了,所以可以压入更多的栈帧(当然一般虚拟机栈的内存大小是指出动态扩展的)。如果某一时刻一个线程请求的栈深度大于虚拟机所允许的栈深度,则会抛出 StackOverFlowError 异常;如果虚拟机可以动态扩展,扩展时无法申请到足够的内存,就会抛出 OutOfMemoryError 异常。
3 本地方法栈
本地方法栈(Native Method Stack)和虚拟机栈所发挥的作用很相似,都是线程私有的,都可以抛出 StackOverFlowError 异常 和 OutOfMemoryError 异常。他们之间的区别就是虚拟机栈为虚拟机执行 Java 方法(字节码)服务;本地方法栈则为虚拟机使用的 Native 方法服务。Java 中提供了调用本地方法的本地接口(Java Native Interface),是一些 C/C++ 程序,在线程的执行过程中,会经常碰到调用 JNI 方法的情况,比如网络通信、文件操作的底层等。
4 Java 堆
堆内存是 JVM 所管理的内存中最大的一块(从我画的情况来看,看的出来对吧)。堆内存是被所有线程所共享的一块内存区域,在虚拟机启动的时候创建。堆和栈是我们在刚接触编程的时候最容易遇到,也是醉容易搞混的两个概念,他们有的时候可以代表数据结构中的堆(就是可以实现最小二叉堆的那个)和栈(就是 FILO 那个),有的时候是表示内存的结构,所以千万不要搞混了。经常有人把 Java 内存分为堆内存和栈内存,就是指的这里的Java堆内存和虚拟机栈内存。但这种分法是比较粗糙的,所以我们还是需要学习更加细腻的分法呀。
堆内存被用来存放对象的实例和数组对象,就是 new 出来的那些对象。
此外,堆内存还是垃圾收集器管理的主要区域,具体的垃圾回收内容我们后面再仔细研读研读。因此很多时候也被称为“GC堆”(Garbage Collected Heap)。从垃圾回收的角度看,现在的垃圾收集器基本都会采用分代收集算法,所以堆内存还可以被分为:新生代和老年代,更细致的可以分为 Eden 区、From Survivor 空间、To Surviver 空间等。
当在堆中没有内存完成分配实例,并且堆也无法完成扩展时,将会抛出 OutOfMemoryError 异常。
5 方法区
方法区(Method Area)和堆内存一样,是所以线程共享的内存,主要用来存储已被 JVM 加载的类信息、常量、静态变量、即时编译器(Just In Time,JIT)编译后的代码等数据。在 HotSpot 虚拟机里,垃圾收集分代收集扩展到了方法区。HotSpot 的垃圾收集器可以像管理 Java 堆一样管理这部分内存,但是对于其他虚拟机来讲,可以不实现这样,甚至可以选择不进行垃圾回收。
运行时常量池(Runtime Constant Pool)是方法区的一部分。用来存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的允许时常量池中存放。既然有内容,那肯定也需要被 GC了,回收的主要目标也就是针对常量池的回收以及对类型的卸载。但是这个区的回收效果比较难以让人满意,容易因为此区域内存没有完全回收而造成内存泄漏。
同样,根据 Java 虚拟机规定,当方法区无法满足内存分配需求时,将会抛出 OutOfMemoryError 异常。
以上是关于JVM—— 运行时内存结构的主要内容,如果未能解决你的问题,请参考以下文章