java 代码执行流程–
- java程序–> 编译成字节码文件 -->类加载器–> 字节码校验器 ->执行引擎
java编译器输入的指令流是一种基于栈的指令集,另一种是基于寄存器的指令集架构
基于栈式架构的特点:
- 设计和实现更简单,适用于资源受限的系统
- 避开了寄存器的分配难题,使用零地址指令方式分配
- 直流零的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小编译器容易实现
- 不需要硬件支持,可以执行更好,更好实现跨平台。
基于寄存器架构的特点
- 指令集架构 完全依赖硬件,可移植性差
- 性能优秀和执行更高效
- 花费更少的指令去完成一项操作
总结: 由于跨平台的设计,java的指令都是根据栈来设计的。不同的平台CPU架构不同,所有不能设计为基于寄存器的。有点是跨平台的
指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
JVM 生命周期
-
虚拟机的启动:
- java虚拟机的启动是通过引导类加载器(bootstrap calss loader) 创建一个初始类,来完成的。这个类是由虚拟机的的具体实现来指定的。
-
虚拟机的执行:
- 一个运行中的jvm有一个清晰的任务 执行java程序;
- 程序开始执行时他才执行,程序结束时他就停止
- 执行一个所谓的java程序的时候,真真正正在执行的是一个阿胶做java虚拟机的进程
-
虚拟机的退出:
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 由于操作系统出现错误而导致java虚拟机进程终止
- 某个线程调用runtime类 或者system类的exit方法 或者Runtime类的halt方法 并且java安全管理器也循序这次exit或halt操作
类转载子系统
-
类加载器子系统 负责从文件或者网络中加载Class文件,Class文件在文件中开头有特定的文件表示
ClassLoader 只负责class文件的加载 至于他是否可用运行 则有ExecutionEngine 决定的
加载的类的信息存放于一块成为方法区的内存空间。除了类的信息外,方法区还会有存放运行时的常量池信息,可能还包括字符串字面量和数字常量 -
Loading 加载过程
- 1、通过一个类的全限定名获取定义此类的二进制字节流
- 2、将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 3、在内存中生成一个代表这个类的java.lange.Class 对象 作为方法去这个类的各种数据的访问入口
-
linking 链接阶段
-
1、验证
目的在于确保class文件的字节流包含的信息符合当前虚拟机的要求,保证被加载类的正确性,不危害虚拟机自己的安全
包含 四种验证 文件格式验证,元数据验证,字节码验证,符号引用验证 -
2、准备
为类变量分配内存并且设置改类变量的默认初始值
这里不包括用final修饰的static 因为final 在编译的时候就会分配了准备阶段会显式的初始化
这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到java堆中 -
3、解析
将常量池的符号引用转换为直接引用的过程
事实上 解析操作往往伴随着jvm在执行完初始化之后在执行
-
-
初始化阶段
- 1、初始化阶段就是执行类构造器方法 方法
类加载器的分类
-
支持 引导类加载器 和自定义类记载器
将所有派生于抽象类ClassLoad 的加载器都属于自定义加载器 -
JAVA 核心类库 都是使用引用类加载器进行加载的
-
自定义类加载器情况
隔离加载类
修改类加载方式
扩展加载源
防止源码泄露
实现步骤 继承 ClassLoader 类 实现自己的类加载器 重写FindClass()
双亲委派机制
- 工作原理
-
1、 如果一个类加载器收到了类加载器的请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行
-
2、如果父类加载器还存在其父类加载器,则进一步向上委托,一词递归 请求最终将达到顶层的启动类加载器
-
3、如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载
-
- 优势
- 避免类的重复加载
- 保护程序安全,防止核心API 被随意篡改-
补充
JVM 表示两个class 对象是否为同一个类存在的两个必要条件
- 1、类的完整类名必须一致,包括包名
- 2、加载这个类的ClassLoader 必须相同
JVM 运行时数据区
方法区
堆
- 新生代
- 老年代
java栈
概述
-
栈是运行时的单位,堆是储存的单位
-
java虚拟机栈 也称java栈。 每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧。一个栈帧对应一个方法
-
生命周期
- 随着线程的创建而创建 消亡而消亡
-
作用
- 主管java程序的运行,保存方法的局部变量(8种基本数据类型,引用类型的引用),部分结果,并参与方法的调用和返回
- 局部变量 vs 成员变量(属性)
- 基本数据变量 vs 引用类型变量(类、数组、接口)
-
优点
-
栈是一种快速有效的分配存储方式,访问方式仅次于PC寄存器
-
JVM直接对java栈的操作只有两个每个方法执行,伴随着进栈 执行结束后的出栈工作
-
-
对于栈来说 不存在垃圾回收问题
-
面试题 开发中遇到的异常有哪些?
- StackOverflowError 栈溢出
- OutOfMemoryError 内存溢出(内存不足)
-
栈的存储
- 栈帧 在这个线程上正在执行的每个方法都对应一个栈帧
- 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
-
每个栈帧都存储
- 局部变量表
- 操作数栈(表达式栈 )
- 动态链接 ( 或指向运行时常量池的方法引用)
- 方法返回地址 (或方法正常退出 或者异常退出的定义
- 一些附加信息
-
局部变量表 :
- 定义为一个数字数组,主要用于村粗方法参数和定义在方法体内的局部变量 包括基本数据类型 对象引用 以及返回地址
- 由于局部变量表 在建立线程的栈上,是线程私有数据
面试题
-
举例栈溢出的情况 StackOverflowError
- 通过-Xss设置栈的大小; OOM 栈内存空间不足
-
调整栈的大小,就不能保证不出现溢出嘛? 不能
-
分配的栈内存越大越好嘛?
- 不好,理论上可以避免出现StackOverFlowError的概率,但是不会避免出现,也会让线程资源变少。
-
垃圾回收是否涉及到虚拟机栈?
- 不会,因为虚拟机栈 只有进栈和出栈操作 不存在GC 只存在ERROR
-
方法中定义的局部变量是否线程安全
- 如果只有一个线程才可以操作次数据,则是线程安全的。
- 如果 是内部产生并且消亡的 那就是线程安全的,如果是有作用于在外部 那就是线程不安全的
程序计数器
-
PC寄存器介绍
- 用来存储指令相关的现场信息。
- 作用:
- 用来村粗指向吓一跳指令的地址,也就是即将要执行的指令代码,由执行引擎读取吓一条指令。
-
每个线程都有自己的程序计数器,
-
不会抛出OOM 异常 内存溢出
面试问题
-
使用PC寄存器存储字节码指令地址有什么用呢?
为什么使用PC寄存器巨鹿当前线程的执行地址呢?- 因为CPU需要不停的切换各个线程,这时候切换回来以后
就得知道接下来从哪开始继续执行。 - JVM的字节码解释器就需要通过改变PC寄存器的值来明确吓一条
应该执行什么样的字节码指令
- 因为CPU需要不停的切换各个线程,这时候切换回来以后
-
PC寄存器为什么会被设置为线程私有的
- 为了能够准确的记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每个线程都分配一个PC寄存器