java进阶1——JVM
java进阶——JVM
1、JVM概述
作用
Java 虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对 应平台上的机器码指令行,每一条 java 指令,java 虚拟机中都有详细定义,如怎么取操 作数,怎么处理操作数,处理结果放在哪儿。
特点:
一次编译到处运行
自动内存管理
自动垃圾回收功能
现在的 JVM 不仅可以执行 java 字节码文件,还可以执行其他语言编译后的字节码文件,是一 个跨语言平台.
JVM的位置
JVM 是运行在操作系统之上的,它与硬件没有直接的交互。
构成部分:
JVM 整体组成可分为以下四个部分:
1.类加载器(ClassLoader)
2.运行时数据区(Runtime Data Area)
3.执行引擎(Execution Engine)
4.本地库接口(Native Interface)
简图:
详细图:
各个组成部分的用途
程序在执行之前先要把 java 代码转换成字节码(class 文件),jvm 首先需要 把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中 的运行时数据区(Runtime Data Area) ,而字节码文件是 jvm 的一套指 令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析 器执行引擎(ExecutionEngine) 将字节码翻译成底层系统指令再交由 CPU 去执行,而这个过程中需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能,这就是这 4 个主要组成部分的职责与 功能。
而我们通常所说的 JVM 组成指的是运行时数据区(Runtime Data Area),因为通常需要程序员调试分析的区域就是“运行时数据区”,或者 更具体的来说就是“运行时数据区”里面的 Heap(堆)模块。
Java 代码的执行流程
Java 编译器编译过程中,任何一个节点执行失败就会造成编译失败。虽然各个 平台的 java 虚拟机内部实现细节不尽相同,但是它们执行的字节码内容却是一 样的。 JVM 主要任务就是负责将字节码装载到其内部,解释/编译为对应平台上的机器 指令执行。JVM 使用类加载器(Class Loader)装载 class 文件。 类加载完成后,会进行字节码校验,字节码校验通过之后 JVM 解释器会把字节 码翻译成机器码交由操作系统执行。 但不是所有的代码都是解释执行,JVM 对此作了优化,比如 HotSpot 虚拟机, 它本身提供了 JIT(Just In Time)编译器.
JVM 架构模型
Java 编译器输入的指令流基本上是一种基于栈的指令集架构,另一种指令集架构 是基于寄存器的指令集架构.
基于栈式架构的特点
设计和实现更简单,适用于资源受限的系统. 使用零地址指令方式分配,其执行过程依赖于操作栈,指令集更小,编译器容易实 现.不需要硬件支持,可移植性好,更好实现跨平台.
基于寄存器式架构特点:
指令完全依赖于硬件,可移植性差.
性能优秀,执行更高效.
完成一项操作使用的指令更少.
使用 javap -v class 文件可以将 class 文件反编译为指令集.
所以由于跨平台的设计,Java 指令集都是根据栈来设计的,不同 CPU 架构不同,
所以不能设计为基于寄存器的.
优点是跨平台,指令集小,编译器容易实现.
缺点是性能下降,实现同样功能需要更多的指令.
2、JVM结构-类加载
类加载子系统
作用:
类加载器子系统负责从文件系统或者网络中加载 class 文件。
主负责加载类, 有执行引擎执行,存放在方法区(元空间)
扮演者的是一个快递员的角色
-
class file 存在于硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板 在执行的时候是要加载 JVM 当中来,根据这个模板实例化出 n 个一模一样的实 例.
-
class file 加载到 JVM 中,被称为 DNA 元数据模板,放在方法区中.
-
在.class–>JVM–>最终称为元数据模板,此过程就要有一个运输工具(类加 载器 Class Loader),扮演一个快递员的角色.
类加载过程
1.加载
根据类的地址,从硬盘上读取类的信息,
将信息读入到方法区,生成Class类的对象
2.链接
验证: 验证字节码文件格式是否是当前虚拟机所支持的文件格式,语法格式
准备: 为静态成员分配默认值(int 默认值0) 注意static final在编译期间赋值
解析: 将字节码中符号引用 替换 成 直接引用
例如: 编写代码 方法1 中调用 方法2 (符号引用)
类加载到内存后把符号的引用地址 换成 内存的地址引用
3.初始化
类什么时候初始化
1 )创建类的实例,也就是 new 一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName(“”))
5)初始化一个类的子类(会首先初始化子类的父类)
类的初始化顺序
先初始化静态的,多个静态的按照从上向下的顺序执行,
如果类有父类,则先初始化父类的静态,然后是子类.
如果是创建对象,先调用父类的构造方法,然后是子类自己的构造方法
类加载器
站在JVM的角度划分: 启动类加载器(不是java语言写的),
其他类加载(都是java语言写的)
站在开发者的角度:
启动类加载器(引导类加加载器)
这个类加载器使用 C/C++语言实现,嵌套在 JVM 内部.它用来加载 java 核心类
库.
负责加载扩展类加载器和应用类加载器
加载<JAVA_HOME>lib
扩展类加载类器
是由java语言实现的 继承自ClassLoader
负责加载 E:ProgramFilesJavajdk1.8.0_261jrelibext
应用程序类加载器(系统类加载器)
Java 语言编写的,由 sun.misc.Launcher$AppClassLoader 实现.
派生于 ClassLoader 类.
负责加载用户类
用户自定义类加载器(例如tomcat)
双亲委派机制
类的加载时按需加载,使用时才会加载.
类加载时,加载器都会将类交给父级类加载器加载.
如果所有的父级加载没有找到类,
则一级一级的向下委派查找.
如果都找不到,那么就会抛出异常.
目的: 为了安全考虑 避免了用户自己写的类覆盖了系统中的类.
类的主动使用和被动使用
主动使用会触发类的初始化
new
使用静态变量 静态方法
反射加载类
执行main方法
子类被初始化 父类也会触发初始化
被动使用不会触发了类的初始化
仅仅使用类的静态常量 而且是直接赋字面量的那种
将类作为数组的类型声明使用时不会触发初始化
3、JVM 运行时数据区
堆,方法区(元空间) 主要用来存放数据 是线程共享的.
程序计数器,本地方法栈,虚拟机栈 是运行程序的,是线程私有的.
程序计数器
jvm中的程序计数器不是cpu中的寄存器, 可以理解为计数器.
是一块非常小的内存空间,运行速度是最快的,不会出现内存溢出情况.
作用:记录当前线程中的方法执行的位置. 以便于cpu在切换执行时,记录程序执行的为位置.
在运行时数据区中唯一一个不会出现内存溢出的区域.
本地方法栈
当我们在程序中调用本地方法时,会将本地方法加载到 本地方法栈中执行.
也是线程私有的, 如果空间不够,也会出现栈溢出错误. hashCode();
虚拟机栈
背景: java为了移植性好(跨平台) 所以将运行程序的设计架构为栈结构运行, 而不是依赖于cpu的寄存器架构.
栈是运行时的单位(加载方法运行),
而堆是存储的单位(存储对象的).
作用运行方法 一个方法就是一个栈帧. 栈帧中包含( 局部变量(基本类型,引用地址) 方法地址,返回地址)
栈中的操作 入栈,出栈
栈中异常 StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。 递归调用方法次数过多
栈中存储方法运行时需要的数据
栈的运行原理 第一个方法被加载 入栈 在方法中调用了其他方法, 另一个方法入栈 方法运行结束后出栈.
栈帧的结构:
局部变量表: 方法参数,定义的局部变量, 基本值类型直接存值, 引用类型存地址.
操作数栈
动态链接
方法返回地址