龙空技术网

深入理解JVM原理之JVM内存模型

后知后觉的中年老男人 200

前言:

眼前我们对“java的内存分区 哪些会发生oom”大致比较珍视,兄弟们都想要分析一些“java的内存分区 哪些会发生oom”的相关资讯。那么小编也在网摘上网罗了一些关于“java的内存分区 哪些会发生oom””的相关知识,希望兄弟们能喜欢,大家快快来了解一下吧!

一、前言

JVM一直是Java知识体系里面进阶阶段的重要部分,如果想深入了解Java进程运行时的整个过程,则JVM是避开不了的话题,本系列将通过简洁易读的方式,讲解JVM必要的知识点。

二、Java程序执行流程

Java的基本开发模式是我们写的所有Java源代码都保存在*.java的文件中,这些源代码,必须经过javac.exe命令将其编译成 *.class 文件,然后利用 java.exe 命令在 JVM 进程中中解释此程序。整个过程如下图:

当JVM将所需要的*.class文件经类加载器加载到JVM进程中,再经过jvm的内存空间调配,最终是由执行引擎完成*.class文件的执行。

在 java 中可以使用 native 实现 本地 C 函数的调用,Native Interface,但是这些都是属于程序的辅助手段,而真正的程序运行都在“运行时数据区”之中。

三、Java运行时数据区域

JVM运行时的数据区域主要分为5个部分:

1、程序计数器;

2、Java栈;

3、本地方法栈;

4、堆;

5、方法区;

其中方法区和Java堆,是各个线程共享的内存区域;而Java栈、本地方法栈、程序计数器是线程私有的内存区。

下面就详细介绍下这5个区域。

1、程序计数器

1.1、什么是程序计数器

程序计数器是一块较小的内存空间,可以把它看作当前线程正在执行的字节码的行号指示器。即程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。

如果当前线程正在执行的是一个本地方法,那么程序计数器就为空。

1.2、程序计数器的作用

作用一:字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,诸如:循环、分支、跳转、选择、异常处理等。

作用二:在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

1.3、程序计数器的特点

是一块较小的存储空间线程私有,每条线程都有一个程序计数器。是唯一一个不会出现OutOfMemoryError的内存区域。生命周期随着线程的创建而创建,随着线程的结束而死亡。

2、Java栈

2.1、什么是Java栈

Java栈是描述Java方法执行过程的内存模型。

虚拟机会为每一个即将运行的Java方法创建一个叫做"栈帧"的区域用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

当方法在运行过程中需要创建局部变量时,就将局部变量的值存入栈帧的局部变量表中,局部变量表以变量槽为最小单位,只允许保存32位长度的变量,如果超过32位则会开辟两个连续的变量槽(64位长度的long和double);

当这个方法执行完毕后,这个方法所对应的栈帧将会出栈,并释放内存空间,所以每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

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

如果虚拟机的实现中允许虚拟机栈动态扩展,当内存不足以扩展栈的时候,虚拟机将会抛出OutOfMemoryError异常。

2.2、Java栈的特点

局部变量表的创建是在方法被执行的时候,随着栈帧的创建而创建。而且,局部变量表的大小在编译时期就确定下来了,在创建的时候只需分配事先规定好的大小即可。此外,在方法运行的过程中局部变量表的大小是不会发生改变的。Java栈会出现两种异常:StackOverFlowError和OutOfMemoryError。

注意:StackOverFlowError和OutOfMemoryError的异同?

StackOverFlowError表示当前线程申请的栈超过了事先定好的栈的最大深度,但内存空间可能还有很多。 而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。

3、本地方法栈

与Java栈类似,不过Java栈是为虚拟机执行Java方法(字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间。也会抛出StackOverFlowError和OutOfMemoryError异常。

4、堆

4.1、什么是堆

Java堆是Java虚拟机所管理的内存中最大的一块,被进程的所有线程共享,在虚拟机启动时被创建。该区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存,随着JIT编译器的发展与逃逸分支技术逐渐成熟,栈上分配、标量替换等优化技术使得对象在堆上的分配内存变得不是那么“绝对”。

4.2、堆的特点

线程共享:整个Java虚拟机只有一个堆,所有的线程都访问同一个堆。而程序计数器、Java虚拟机栈、本地方法栈都是一个线程对应一个的。在虚拟机启动时创建。垃圾回收的主要场所。 堆的大小既可以固定也可以扩展。内存不足时也会抛出OutOfMemoryError。

5、方法区

5.1、什么是方法区

Java虚拟机规范中定义方法区是堆的一个逻辑部分。 用于存储被Java虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

5.2、方法区的特点

线程共享:方法区是堆的一个逻辑部分,因此和堆一样,都是线程共享的。整个虚拟机中只有一个方法区。永久代:方法区中的信息一般需要长期存在,而且它又是堆的逻辑分区,因此用堆的划分方法,把方法区称为永久代。内存回收效率低:方法区中的信息一般需要长期存在,回收一遍内存之后可能只有少量信息无效,对方法区的内存回收的主要目标是对常量池的回收 和 对类型的卸载。Java虚拟机规范对方法区的要求比较宽松,和堆一样,允许固定大小,也允许可扩展的大小,还允许不实现垃圾回收。 内存不足时也会抛出OutOfMemoryError。

5.3、运行时常量池

它是方法区的一部分。Class文件除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译期生成的各种字面量和符号引用。Java虚拟机运行期间,也可能将新的常量放入常量池(如String类的intern()方法)。

6、直接内存

直接内存是除Java虚拟机之外的内存,但也有可能被Java使用。

在NIO中引入了一种基于通道和缓冲的IO方式。它可以通过调用本地方法直接分配Java虚拟机之外的内存,然后通过一个存储在Java堆中的DirectByteBuffer对象直接操作该内存,而无需先将外面内存中的数据复制到堆中再操作,从而提升了数据操作的效率。

直接内存的大小不受Java虚拟机控制,但既然是内存,当内存不足时就会抛出OOM异常。

四、总结

Java虚拟机的内存模型中一共有两个“栈”,分别是:Java栈和本地方法栈。 两个“栈”的功能类似,都是方法运行过程的内存模型。并且两个“栈”内部构造相同,都是线程私有,只不过Java虚拟机栈描述的是Java方法运行过程的内存模型,而本地方法栈是描述Java本地方法运行过程的内存模型。Java虚拟机的内存模型中一共有两个“堆”,一个是原本的堆,一个是方法区。方法区本质上是属于堆的一个逻辑部分。堆中存放对象,方法区中存放类信息、常量、静态变量、即时编译器编译的代码。堆是Java虚拟机中最大的一块内存区域,也是垃圾收集器主要的工作区域。程序计数器、Java虚拟机栈、本地方法栈是线程私有的,即每个线程都拥有各自的程序计数器、Java虚拟机栈、本地方法区。并且他们的生命周期和所属的线程一样。 而堆、方法区是线程共享的,在Java虚拟机中只有一个堆、一个方法栈。并在JVM启动的时候就创建,JVM停止才销毁。

标签: #java的内存分区 哪些会发生oom