JVM原理之内存模型

一、JVM是什么

JVM :Java Virtual Machine,就是我们耳熟能详的 Java 虚拟机。它只认识 .class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,JVM 是 Java 能够跨平台的核心。

JRE :Java Runtime Environment,Java 运行时环境。它主要包含两个部分,JVM 的标准实现和 Java 的一些基本类库。它相对于 JVM 来说,多出来的是一部分的 Java 类库。

JDK :Java Development Kit,Java 开发工具包。JDK 是整个 Java 开发的核心,它集成了 JRE 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。

所以,这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM。

二、JVM的组成结构

JVM由以下三部分组成,他们之间的配合即JVM的运行流程。

  • 类加载器,将 .class 字节码文件装载进内存(运行时数据区)
  • 运行时数据区,相当于JVM在内存中的数据结构区域
  • 执行引擎,解析字节码指令,将运行时数据区里的数据进行执行

三、JVM内存数据结构

运行时数据区,根据线程共享和私有分为两大部分:

  • 线程共享区域=堆内存+元数据区(方法区)
  • 线程私有区域=程序计数器+本地方法栈+虚拟机方法栈

3.1  线程私有区域

程序计数器:指向当前线程正在执行的字节码指令的地址(行号)。

本地方法栈:用栈的这种数据结构来存储native方法的一块线程私有的内存区域。

虚拟机栈:同本地方法栈,区别在于栈里的方法是Java方法。由一些栈桢组成。

每运行一个方法就创建一个栈桢。方法的调用,实际上是先将方法入栈到栈底的栈桢,调用完毕返回时,实际上是将方法从栈里弹出。所以如果代码里递归方法层数很多,则有可能造成栈溢出StackOverFlowError,因为虚拟机栈的容量是固定的(默认1M,也可通过JVM参数来修改,如-Xss=128k),所以递归次数足够多的话,总会有栈溢出的时候。

一个解决方法是增大虚拟机栈大小以容纳更多方法,但是治标不治本,特别是对于多线程的情形,将有可能造成内存溢出OutOfMemoryError;另一个方法是优化自己的源代码,避免使用不恰当的递归,另外需要精简方法体里面的变量定义等,例如去掉方法中无用的局部变量的定义,因为这些无用的局部变量会存储在栈桢中的局部变量表中(参考下图),所以相当于直接增大了栈桢的容量。

下图为虚拟机栈中栈桢的结构:

栈桢的组成部分
组成名称 描述
局部变量表 用来存放方法定义的局部变量、方法的参数。

包含编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)

操作数栈 用来存放操作数。局部变量表中的变量是不可直接使用的,如需使用必须通过相关指令将其加载至操作数栈中作为操作数使用。
动态连接 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
返回地址

一个方法开始执行后,只有两种方式可以退出这个方法。

第一种:执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者,是否有返回值和返回值的类型将根据遇到的何种方法返回指令来决定,这种退出的方式称为正常完成出口。

另一种:方法执行过程中遇到了异常,并且这个异常没有在方法体内得到处理,无论是Java虚拟机内部产生的异常,还是代码中使用athrow字节指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出称为异常完成出口。

3.2  线程共享区域

Java堆:所有线程共享,所有的对象实例和数组在堆上分配,由JVM进行分配和管理,属于"用户态"。

元数据区:旧称方法区(永久代),保存类信息、常量、编译后的代码信息。

JVM运行时数据区之外,还有一块内存可通过NIO的方式高效操作,叫做堆外内存或直接内存:

直接内存:堆外内存,这部分内存不是由JVM管理和回收的。需要我们手动地回收。是由操作系统管理的,属于"内核态"。

发布了62 篇原创文章 · 获赞 22 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/songzehao/article/details/104595286
今日推荐