文章有参看其他博主资料
- 深入详解JVM原理:https://blog.csdn.net/know9163/article/details/80574488
- JVM完整深入解析:https://segmentfault.com/a/1190000014395186
- Java虚拟机你只需看这一篇就够了:https://blog.csdn.net/qq_41701956/article/details/81664921
- 百度百科:JVM相关概念
- 选择JDK1.8的理由之JVM内存变化:https://www.jianshu.com/p/4b4cecc8e262
- JDK8废弃永久代:https://www.cnblogs.com/dennyzhangdd/p/6770188.html
一、概述
1.什么是JVM
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
2.JVM的本质
- Java虚拟机有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统
- Java虚拟机本质是就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令
二、JVM结构
JVM虚拟机在Java8之前基本没有过多改动,自1.8之后有些许重要改动,下面分别介绍
1.JVM结构及介绍
图1 JVM基本结构图
图2 运行时数据区结构图
可以看到:运行时数据区(Runtime Data Areas)包括方法区、堆、虚拟机栈、本地方法栈和程序计数器,其中方法区和堆线程之间可以共享,其他则线程之间是相互隔离、互不影响的
(1)方法区MethodArea
-
方法区有时也称为永久代(Permanent Generation),(Java8之前)
-
方法区由所有Java线程共享
-
存放被虚拟机加载了的类的信息(名称、修饰符等)、类中的静态常量、final类型的常量、类的Field信息(即定义的字段)、类中的方法信息
-
当方法区使用的内存超过了其运行的大小时,会抛出OutOfMemoryError等错误信息
(2)常量池ConstantPool
-
常量池是方法区的一部分
-
存放两类数据:字面量和引用量
字面量:字符串、final变量等
引用量:类名、接口名、方法名、字段名和描述符等
-
常量池在编译过程中就被确定,并保存在已编译的.class文件中
(3)堆Heap
- 堆是虚拟机管理的内存中最大的一块
- 堆由所有线程共享
- Java对象存储的地方,几乎所有对象实例内存都在这里分配,new生成的对象和数组等
- Java堆是垃圾收集器管理的内存区域,因此有时称为“GC堆”
- 堆空间分为年轻代、老年代。刚创建的对象存 放在年轻代,而老年代中存放生命周期长久的实例对象
(4)栈(虚拟机栈或Java栈)Java Virtual Machine Stack
-
JVM栈线程私有,每个线程创建的同时都会创建自己的JVM栈,互不干扰
-
栈是Java方法执行的一种内存模型,也就是执行方法的一种体现。Java栈存放的是若干个栈帧,每个栈帧对应一个被调用的方法
-
栈帧包括局部变量表、操作数栈、指向当前方法所属的类运行时常量池的引用、方法返回地址和其他附加信息
局部变量表:存储方法中的局部变量(包含方法形参),对于基本类型变量值存储它的值,对于引用类型变量存储的是指向对 象的引用。局部变量表的大小在编译期间就被确定,因此程序执行期间是不会改变的
操作数栈:可以简单的理解为计算的过程。在一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是 进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
指向常量池的引用:方法执行过程中可能会用到类中的常量,所以要存在一个引用指向运行常量
方法的返回地址:方法执行完毕后要返回到它被调用的地方,因此栈帧中保存了一个方法的返回地址
-
执行方法时会创建一个栈帧并压栈,方法执行完毕后会将栈帧弹出,称为出栈
(5)程序计数器 Program Counter Register
- 线程私有,即每一个线程都会有一个线程计数器
- 程序计数器是用来记录线程执行的位置(内存地址),以保证线程来回切换后还能按照正确的地址继续执行
- 程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此不会出现OOM现象
(6)本地方法栈 Native Method Stacks
- 用来支持本地方法的执行,此区域执行的是每个native方法调用的状态,本地方法栈和Java栈非常相似
- Native本地方法指的是由底层语言编写的,例如Windows平台的Java Native方法是由C/C++ 等实现的
2.Java8的JVM变化
(1)Java8的内存模型
图3 Java8的运行时数据区结构图
- 可以看出,元空间取代了方法区,方法区在Java8中已经被移除,官方给出的说法是一方面:移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。另一方面则是现实中使用由于永久代内存经常不够用或发生内存泄漏,容易造成OOM(java.lang.OutOfMemoryError)
- 方法区被移除后,原来存在于方法区中的常量池被移到了堆中
- 使用本地内存来存储类元数据信息并称之为:元空间
- 元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
(2)Java8堆空间的变化
可以看出,基本上和Java8之前一样,只是多了一个常量池
三 、JVM堆空间分代
1.为什么会存在分代现象
对于堆进行分代,其实是为了优化GC性能,堆不分代也可以完成它所作的事情,但是对于GC回收来说效率会很低,因为它会扫描整个堆区域
2.对于分代的理解
可以参见这篇博客:https://www.cnblogs.com/wuhan729/p/8376615.html