文章目录
前言
最近不能出门,就在B站看了尚硅谷宋红康老师关于JVM的视频,学习了一下JVM的内容,讲的很清楚,现在做一个总结,也给大家一个参考。
JVM概述
首先,我们应该清楚虚拟机分为系统虚拟机和程序虚拟机,而JVM就是典型的程序虚拟机。也可以理解为JVM是一个应用程序,需要运行在操作系统上,而不是直接和硬件打交道。
JVM不关心使用的是什么语言,只关心.class文件,只要这种语言的编译器可以提供符合格式的.class文件,就可以在JVM上面运行,所以JVM是跨语言的平台。
所以JVM在计算机当中的位置应该如下图所示:
JVM架构模型
JVM是基于栈的指令集架构,这种架构的特点是:
- 设计和实现更加简单,适合资源受限的系统;
- 避开了寄存器的分配难题,使用零地址指令方式分配;
- 执行过程依赖操作栈,指令集小,但相同的代码转化为指令数量更多;
- 不需要硬件支持,可移植性好,可以跨平台,这也是Java跨平台特性的原因。
JVM生命周期
JVM生命周期为:启动、执行、退出
JVM内存结构图
类加载子系统
类加载子系统分为加载、链接、初始化三个阶段。
加载阶段
类加载器分为两类:引导类加载器(由C/C++编写的)和自定义类加载器(包括拓展类加载器、系统类加载器、用户自己编写的加载器,都是用Java编写的)
这几种加载器的关系:引导类加载器包含拓展类加载器,拓展类加载器包含系统类加载器、系统类加载器包含用户自己编写的加载器。
加载器充当快递员的角色,只负责加载文件,能不能运行由执行引擎决定。
加载:
- 通过一个类的全限定名获取定义此类的二进制字节流;
- 将这个字节流代表的静态存储结构转化为方法区的运行时数据结构;
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
链接阶段
验证:
- 目的是确保.class文件符合JVM的要求,保证加载类的正确性,确保不会危害JVM的安全
- 包括:文件格式验证、元数据验证、字节码验证、符号引用验证
准备:
- 为类变量分配内存和默认初始值;不会为实例变量分配初始值;类变量分配在方法区,实例变量分配在堆中;
- 不包括用final修饰的static,因为final在编译的时候就会分配,准备阶段会显示初始化
解析:
- 将常量池内的符号引用转换为直接引用的过程;
- 事实上,解析操作往往会伴随着JVM在执行完初始操作之后再执行。
初始化阶段
初始化阶段就是执行类构造器方法()的过程,此方法不需要定义,系统自动运行。
双亲委派机制
- 原理:一个类加载器收到类加载请求时,会先把这个请求委托给父类加载器执行,一次向上委托,最终到达引导类加载器。之后再看父类能不能完成加载,可以的话就加载,不可以的话向下递给子类加载。
- 目的:避免类重复加载,保护程序安全,防止核心API被篡改
运行时数据区
方法区和堆共享,其他是线程私有。
堆
GC的主要地点。
方法区
程序计数器
是对物理程序计数器的一种抽象模拟,是一块很小的内存区间,也是运行速度最快的存储区域。
程序计数器用来存储指向下一条指令的地址,由执行引擎读取下一条指令。
每个线程都有自己私有的程序计数器,保证线程切换回来之后可以继续运行。
程序计数器不存在GC,也不存在OOM异常。
虚拟机栈
在JVM中,虚拟机栈和本地方法栈合为一体。
每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧,对应一次次方法调用。
虚拟机栈不存在GC,但存在OOM异常。
下面这个图画的有点错误,不是虚拟机栈,而是栈帧,一个个栈帧入栈出栈组成虚拟机栈。
局部变量表
是一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,包括8种基本数据类型,对象引用,以及返回地址类型。
由于局部变量表是线程私有,所以不存在数据安全问题。
局部变量表的容量大小在编译器就确定了。
局部变量表的基本存储单元是slot(变量槽),32位的数据只占用一个slot,64位的数据(long和double)占用两个slot。而且slot可以重复利用,如果一个局部变量过了其作用域,那么在其作用域之后声明的新的局部变量有可能会复用过期局部变量的槽位,以节约资源。
操作数栈
操作数栈主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
动态链接
即指向运行时常量池的方法引用,动态链接的作用是将符号引用转换为调用方法的直接引用。
常量池的作用是提供一些符号和常量,便于指令的识别。
本地方法栈
Java虚拟机栈用于管理Java方法的调用,本地方法栈用于管理本地方法的调用。
本地方法不是Java编写的,是C语言。
当某个线程调用本地方法时,它就进入了一个全新的不再受虚拟机限制的世界,它和虚拟机拥有同样的权限。本地方法可以通过方法接口来访问虚拟机内部的运行时数据区,甚至可以直接使用本地处理器的寄存器(C语言可以 面向底层硬件),直接从本地内存的堆中分配任意数量的内存
本地方法接口
Java使用起来非常方便,但有些层次的任务用Java实现起来效率不高,所以使用本地方法。
比如与Java环境外交互时、与操作系统交互等等。现在本地方法使用的越来越少了,除非是与硬件相关的应用。