Java虚拟机(JVM)——运行时数据区域

运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域:

方法区、堆、虚拟机栈(Java栈)、本地方法栈、程序计数器。

其中方法区、堆是由线程共享的数据区,虚拟机栈、本地方法栈、程序计数器是线程隔离的数据区。

一、程序计数器

程序计数器是当前线程所执行的字节码的行号指示器。

java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来工作的,当线程切换后需要恢复到正确的执行位置,每条线程需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,这类内存区域为“线程私有”的内存。

如果线程正在执行java方法,程序计数器记录是正在执行的虚拟机字节码指令的地址。

如果线程正在执行的事Natvie(非java方法),程序计数器值则为空。

程序计数器内存区域在Java虚拟机规范中不会出现OutOfMemoryError(OOM异常)情况。

二、虚拟机栈

虚拟机栈是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

虚拟机栈也是线程私有的,它的生命周期与线程相同。

局部变量表:存放编译器可知的各种基本数据类型、对象引用、返回地址类型(returnAddress类型)。

关于returnAddress类型,是指向字节码指针的值的类型,是存在于字节码层面,我们编码层面不直接跟returnAddress类型打交道,所以对于此类型可能会比较陌生。我也是参考此博客解惑:深入理解 JVM 中的 returnAddress_墨城之左的博客-CSDN博客_returnaddress类型

操作数栈:如上图中,int a=1; jvm需要先找一块内存空间把1存储起来,这块内存空间就是操作数栈。然后再找一块内把a存储起来(局部变量表),然后再把1赋值给a;

方法出口(returnAddress):如上图中,当我们在执行demo2.abc();后,要回到System.out.println(a);这个位置继续执行任务,但是线程怎么才能知道需要跳转到System.out.println(a);这里呢,所以把需要返回的位置标记开辟一块空间存储,这块内存空间就是方法出口。

注意:

        1.方法出口在不同的教程中叫法不同,有的叫方法出口,有的叫返回地址(returnAddress),但基本解释的都是一个意思。

        2.局部变量表中的returnAddress类型,跟方法出口(returnAddress),里面都有一个"returnAddress",但完全不是一个意思,我也是迷糊了好久,查了很多博客跟教程,如果发现我的理解有误,请及时纠正,一起学习。

动态链接:如上图中,两个new出来的MyDemo(),是存放在堆中的,但是不管我们执行md1.abc();还是执行md2.abc();最终执行的是我们java文件中的代码块,也就是MyDemo.class中的abc();而这个MyDemo的类信息只有一个,存放在方法区中,我们要调用MyDemo.class中的abc();需要在栈帧中开辟对某些符号引用的空间,这块内存空间就是动态链接。

异常:

1.如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。

2.如果虚拟机栈扩展无法申请到足够的内存时会抛出OutOfMemoryError异常。

三、本地方法栈

本地方法栈为虚拟机使用到的Native方法服务,与虚拟机栈的作用非常相似。

HotSpot虚拟机直接把本地方法栈与虚拟机栈合二为一。

本地方发栈也会抛出StackOverflowError和OutOfMemoryError异常。

四、堆

堆内存区域的唯一目的就是存放对象实例(几乎所有对象实例都在堆分配内存,但并不是所有,比如JIT编译器逃逸分析技术分配堆内存不是绝对分配)。

堆是Java虚拟机所管理的内存中最大的一快,在虚拟机启动时创建。

堆内存区域被所有线程共享。

堆是垃圾收集器管理的主要区域,因此也被称“GC堆”。

由于现在收集器基本都是采用分带收集算法,所以Java堆可细分为:新生代、老年代。

新生代又分为:Eden空间、From Survivor空间、To Survivor空间。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

五、方法区

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

方法区内存区域被所有线程共享。

当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

方法区一般被称为“永久代”(本质上两只并不等价,HotSpot虚拟机把GC分代收集扩展至方法区,或者说使用永久代来实现方法区而已。其他虚拟机并不一定存在永久代概念,例如:BEA JRockit、IBM J9等)。

注意:垃圾收集行为在方法区是比较少见的,但并非数据进入了方法区就如永久代的名字一样“永久”存在的。方法区的内存回收目标主要是针对常量池的回收和对类型的卸载。

(了解)Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它有一个别名叫做Non Heap(非堆),目的应该是与Java堆区分开来。

a.运行时常量池

运行时常量池是方法区的一部分

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

Java语言并不要求常量一定只能在编译期产生,运行期间也可能将新的常量放入常量池中,例如:String类的intern()方法。

当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但这部分区域也被频繁使用,而且可能导致OutOfMemoryError异常。

在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

虚拟机内存+直接内存等各个内存区域总和大于物理内存(服务器总内存),从而导致动态扩展时出现OutOfMemoryError异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值