Java语言 图文详解JVM内存及其区域划分

初始JVM内存区域划分

在Java编程语言中,一个Java程序最终是交由JVM(Java Virtual Machine Java虚拟机)执行,所以当我们在谈Java内存区域划分的时候,事实上是指JVM内存区域的划分。而JVM内存实际上是运行时数据区。

点击 Java程序执行过程阅读

下面从一段完整的Java程序执行过程中,了解JVM内存是什么?
在这里插入图片描述

如上图所示,首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。
因此,在Java中我们常常说到的内存管理和内存区域划分就是针对这段空间进行管理(如何分配和回收内存空间)。
在这里插入图片描述

JVM内存区域划分

根据《Java虚拟机规范》的规定,JVM内存(运行时数据区)通常包括5个部分:程序计数器(Program Counter Register)、Java虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、(Heap)、方法区(Method Area)、运行时常量池(Runtime Constant Pool)。具体如图所示:

在这里插入图片描述

看图得知,程序计数器、虚拟机栈、本地方法栈每个线程(Thread)都有一份(一个Java程序中可能存在多个线程),而堆、方法区、运行时常量池只有一份,所有线程共享。(一个Java程序只能有一份)
  (如上图所示,在JVM规范中虽然规定了程序在执行期间运行时数据区应该包括这几部分,但是至于具体如何实现并没有做出规定,不同的虚拟机厂商可以有不同的实现方式。)

JVM内存区域详解

程序计数器

程序计数器(Program Counter Register),也有称作为PC寄存器。只是一个很小的空间,保存下一条执行的指令的地址
 
 在汇编语言中,程序计数器是指CPU中的寄存器,他保存的是程序当前执行的指令的地址(也可以说是保存下一条指令所在的存储单元的地址),当CPU需要执行指令时需要从程序计数器中得到当前需要执行的指令所在的存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便会自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。
 
虽然JVM中的程序计数器并不像汇编语言中的程序计数器一样是物理概念上的CPU寄存器,但是JVM中的程序计数器的功能跟汇编语言中的程序计数器的功能在逻辑上是等同的,也就是说是用来指示 执行哪条指令的。

由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。

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

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

虚拟机栈

Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈,重点存储局部变量表(当然也有其他信息(基本类型的变量和对象的引用变量))。事实上,Java栈是Java方法执行的内存模型。
栈内存储的除了基本类型的变量(String, int 这种类型的变量)还会存储对象的引用变量。
栈(Stack)是先进后出的结构,其元素类型是栈帧(Frame),一个栈桢对应一个方法,代表该方法的一次执行过程,栈帧中主要保存的是局部变量。
 由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。
 
 Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。
 **当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。**讲到这里,大家就应该会明白为什么在使用递归方法的时候容易导致栈内存溢出的现象了以及为什么栈区的空间不用程序员去管理了(在Java中,程序员基本不用关系到内存分配和释放的事情,因为Java有自己的垃圾回收机制),这部分空间的分配和释放都是由系统自动实施的。对于所有的程序设计语言来说,栈这部分空间对程序员来说是不透明的。下图表示了一个Java栈的模型:
 在这里插入图片描述
Tips:下面对栈帧中的具体内容介绍

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

操作数栈:栈最典型的应用就是用来对表达式求值。一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根结底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
指向运行时常量池的引用:因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
方法返回地址:当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

本地方法栈

本地方法栈(Native Method Stack),本地方法栈与Java栈的作用和原理非常相似。Java栈是为执行Java方法服务的,而本地方法栈保存的内容是Native方法的局部变量,是为执行本地方法(Native Method)服务的。
 在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。
 
tips:Native方法
JVM是一个基于C++实现的程序。在Java程序的执行过程中本质上也需要调用C++提供的一些函数进行和操作系统底层进行一些交互。因此在Java开发中也会调用一些C++实现的的函数。
这里的Native方法就是指这些C++实现的,再由Java来调用的函数。

堆(Heap):JVM所管理的最大内存区域。使用new创建的对象都在堆上保存。 例如,数组new int[] {1,2,3}
也可以说,Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)。在Java中,程序员基本不用去关心空间释放的问题,Java的垃圾回收机制会自动进行处理。因此这部分空间也是Java垃圾收集器管理的主要区域。另外,和栈不一样的是:堆是被所有线程共享的,在JVM中只有一个堆。

方法区

方法区(Method Area)在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。方法区用于存储已经被虚拟机加载的类信息(包括类的名称、方法信息、字段信息)、常量、static静态变量、即时编译器编译后的代码等数据。方法编译出的字节码就是保存在这个区域。
在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

运行时常量池

运行时常量池(Runtime Constant Pool):是方法区的一部分,存放字面量(字符串常量)与符号引用。(注意:从JDK1.7开始,运行时常量池在堆上)

运行时常量池,它是每一个类或接口的常量池运行时的表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

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

·局部变量和引用保存在栈上,new出的对象保存在堆上。
·堆的空间非常大,栈的空间比较小。(栈是有大小的,一般1M或2M)
·堆是整个JVM共享一个,而栈是每个线程具有一份(一个Java程序中可能存在多个栈)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值