JVM以内存区域总结

概述


  对于Java程序员来讲,在虚拟机自动内存管理机制的帮助下,再也不须要像C/C++程序员那样要为每个new操做去写配对的delete/free代码,不容易出现内存泄漏和内存溢出的问题,由虚拟机管理内存,这一切看起来很美好。不过,也正是由于Java程序员把内存控制的权利交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,若是不了解虚拟机是怎样使用内存的,那么排查错误将是一项异常艰难的工做。html

运行时数据区域


1. Java程序执行过程概述

在讨论JVM内存区域以前,先来看一下Java程序的执行过程:java

  如上图所示,首先Java源文件(.java后缀文件)会被Java编译器变异为字节码文件(.class后缀文件),而后由JVM中的类加载器加载各个类的字节码文件,加载完毕以后,交由 JVM执行引擎执行。在整个程序执行的过程当中,JVM会用一段空间来存储程序执行期间须要用到的数据和相关信息,这段空间通常被称做为Runtime Data Area(运行时数据区域),也就是咱们常说的JVM内存。

2. 运行时数据区分区概述

  根据《Java虚拟机规范》的规定,运行时数据区一般包括这几个部分:程序计数器(Program Counter Register)、Java虚拟机栈(JVM Statck)、本地方法栈(Native Method Stack)、Java堆(Java Heap)、方法区(Method Area)。详细见下图:程序员

3. 运行数据区各分区到底存储了什么?

  • 程序计数器(Program Counter Register)

  程序计数器,也有称做为PC寄存器。了解计算机组成原理的同窗对这个概念应该不陌生,在计算机组成原理中程序计数器是指CPU中的寄存器,它保存的是当前执行的指令地址(也能够说是保存下一条指令的所在存储单元的地址),当CPU须要执行指令时,须要从程序计数器中获得当前须要执行的指令所在存储单元的地址,而后根据获得的地址获取到指令,在获得指令以后,程序计数器便自动加1或根据转移指针获得下一天指令的地址,如此循环,直至执行完全部的指令。数组

  虽然JVM中的程序计数器并不像计算机中的程序计数器同样是物理概念上的PC寄存器,可是JVM中的程序计数器的功能跟计算机中的程序计数器的功能逻辑是等同的, 也就是说是用来指示执行哪条指令的。数据结构

  因为在JVM中,多线程是经过线程间的轮流切换并分配处理器执行时间的方式来实现的, 在任何一个肯定的时刻,一个处理器(对多核处理器来讲是一个内核)都会执行一条线程的指令。所以,为了线程切换后还能恢复到正确的执行位置,每条线程都须要一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储,咱们称这类内存区域为 “线程私有” 的内存。多线程

注: 在JVM规范中规定,若是线程执行的是非native方法,则程序计数器中保存的是当前须要执行的指令的地址;若是线程执行的是native方法,则程序计数器中的值是undefined。函数

  因为程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,所以,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。spa

  • Java虚拟机栈(JVM Statck)

  虚拟机栈描述的是Java方法执行的内存模型:每一个方法在执行的过程当中同时会建立一个栈帧(Stack Frame)用于存储局部变量表、操做数栈、动态连接、方法出口等信息,并将创建的栈帧压栈,当方法执行完毕以后,便会将栈帧出栈。(由此可知,Java栈中存放的是一个个栈帧,线程当前执行的方法所对应的栈帧一定位于Java栈的顶部。)简单来讲,每个方法从调用直至执行的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。线程

局部变量表
复制代码

用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就能够肯定其大小了,所以在程序执行期间局部变量表的大小是不会改变的。设计

操做数栈
复制代码

想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想一想一个线程执行方法的过程当中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。所以能够这么说,程序中的全部计算过程都是在借助于操做数栈来完成的。

指向运行时常量池的引用
复制代码

由于在方法执行的过程当中有可能须要用到类中的常量,因此必需要有一个引用指向运行时常量。

方法返回地址
复制代码

当一个方法执行完毕以后,要返回以前调用它的地方,所以在栈帧中必须保存一个方法返回地址。

注:因为每一个线程正在执行的方法可能不一样,所以每一个线程都会有一个本身的Java栈,互不干扰。即,为线程私有内存区,生命周期:伴随着线程的产生于死亡。

  • 本地方法栈(Native Method Stack)

  本地方法栈与Java栈的做用和原理很是类似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并无对本地方发展的具体实现方法以及数据结构做强制规定,虚拟机能够自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

  • Java堆(Java Heap)

  在C语言中,堆这部分空间是惟一一个程序员能够管理的内存区域。程序员能够经过malloc函数和free函数在堆上申请和释放空间。那么在Java中是怎么样的呢?

  Java中的堆是用来存储对象自己以及数组(固然,数组引用是存放在Java栈中的)。只不过和C语言中的不一样,在Java中,程序员基本不用去关心空间释放的问题,Java的垃圾回收机制会自动进行处理。所以这部分空间也是Java垃圾收集器管理的主要区域。另外,堆是被全部线程共享的,在JVM中只有一个堆。

  • 方法区(Method Area)

  方法区在JVM中也是一个很是重要的区域,它与堆同样,是被线程共享的区域。在方法区中,存储了每一个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等

  在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

  在方法区中有一个很是重要的部分就是运行时常量池(Runtime Constant Pool),它是每个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被建立出来。固然并不是Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,好比String的intern方法。

  在JVM规范中,没有强制要求方法区必须实现垃圾回收。不少人习惯将方法区称为“永久代”,是由于HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器能够像管理堆区同样管理这部分区域,从而不须要专门为这部分设计垃圾回收机制。不过自从JDK7以后,Hotspot虚拟机便将运行时常量池从永久代移除了。

参考资料


相关文章
相关标签/搜索