Java代码编译是由Java源码编译器来完成,流程图如下所示:
Java字节码的执行是由JVM执行引擎来完成,流程图如下所示
这部分比较复杂,涉及特别底层的执行过程,不在此做出解释,一个.java文件到.class文件,到jvm加载类的一个过程
类的加载过程(类加载器)
作用:加载class文件
类是抽象的 是一个模板
对象是具体的
加载完成之后 实例化
实例化的对象 引用是在栈里(通过内存地址引用) 对象是存在堆里
实例化对象返回 class getclass class 返回 classloader getclassloder
- 加载
类加载阶段就是由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例,简单理解就是查找class文件并加载方法区。
- 连接
加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中,连接分为三个状态验证,准备,解析
验证:验证类数据信息是否符合JVM规范,是否是一个有效的字节码文件,验证内容涵盖了类数据信息的格式验证、语义分析、字节码验证,符号引用验证
准备:为类中的所有静态变量分配内存空间,并为其设置一个初始值
解析:将符号引用转为直接引用
- 初始化
将一个类中所有被static关键字标识的代码统一执行一遍,
所有类变量初始化语句和静态代码块都会在编译时被前端编译器放在收集器里头,存放到一个特殊的方法中,这个方法就是方法,即类/接口初始化方法。该方法的作用就是初始化一个中的变量,使用用户指定的值覆盖之前在准备阶段里设定的初始值。任何invoke之类的字节码都无法调用方法,因为该方法只能在类加载的过程中由JVM调用。
如果父类还没有被初始化,那么优先对父类初始化,但在方法内部不会显示调用父类的方法,由JVM负责保证一个类的方法执行之前,它的父类方法已经被执行。
JVM必须确保一个类在初始化的过程中,如果是多线程需要同时初始化它,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。
- 使用
- 卸载
类加载完成之后就到了运行时数据区
运行时异常就在运行时数据区中
- 本地方法栈
本地方法栈与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java
方法(也就是java的字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务,调用c语言
object对象里很多都是native方法
public native int hashCode();
- 程序计数器
程序计数器是一个记录着当前线程所执行的字节码的行号指示器。通过程序计数器来记录某个线程的字节码执行位置。因此,程序计数器是具备线程隔离的特性,也就是说,每个线程工作时都有属于自己的独立计数器。
- 方法区
是一个被线程共享的内存区域,主要存储加载的类字节码、class/method/field等元数据对象、static-final常量、static变量、jit编译器编译后的代码等数据。另外,方法区包含了一个特殊的区域“运行时常量池”。
- 栈
栈是一种存储数据的结构,插入和删除仅在一端进行,即入栈和出栈,所以是后进先出。
java栈是线程私有的,生命周期与线程相同,每个线程会分配一个栈空间。栈是由栈帧组成的,每个栈帧又分为
局部变量表,操作数栈,动态链接,返回地址
局部变量表
局部变量表中存储了基本数据类型(boolean、byte、char、short、int、float、long、double)的局部变量(包括参数)、和对象的引用(String、数组、对象等),但是不存储对象的内容。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变局部变量表的大小。
局部变量的容量以变量槽(Variable
Slot)为最小单位,每个变量槽最大存储32位的数据类型。对于64位的数据类型(long、double),JVM
会为其分配两个连续的变量槽来存储
jvm通过索引定位的方式使用局部变量,索引的返回从0开始到局部变量所允许的最大的slot数量,普通方法0槽位存储的是所属对象的实例,静态方法不是。
slot是可以复用的,当前方法超出了变量的作用域,变量就失效了
操作数栈
栈决定了函数调用的深度。这也是慎用递归调用的原因。递归调用时,每次调用方法都会创建栈帧并压栈。当调用一定次数之后,所需栈的大小已经超过了虚拟机运行配置的最大栈参数,就会抛出
StackOverflowError 异常。
返回地址
方法开始执行后,只有 2 种方式可以退出 :方法返回指令,异常退出。
动态链接
符号引用* 符号引用是 对一个类的全局描述,存放在常量池中,可以查看class文件的字节码文件
直接引用 对于指向“类型”【Class对象】、类变量、类方法的直接引用可能是指向方法区的本地指针 Class文件中存放了大量的符号引用,字节码中的方法调用指令就是以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或第一次使用时转化为直接引用,这种转化称为静态解析。另一部分将在每一次运行期间转化为直接引用,这部分称为动态连接。
- 堆
堆是java所管理的最大一块内存区域,堆内存是被共享的,主要存放new出来的对象,
java堆又分为年轻代和老年代,年轻代又分为伊甸园区,幸存区,幸存区又分为,from区与to区
新生代和老年代的比例是1:2 伊甸园区和survivor区比例是8:1:1
执行引擎
执行引擎分为解释器,jit即时编译器,垃圾回收器
解释器( Interpreter):当Java虚拟机启动时,会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行, 解释器将.class字节码指令解释为本地机器指令
JIT( Just In Time Compiler)即时编译器:就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言JIT将.java代码直接编译为本地机器指令
java 是半编译半解释性语言
垃圾回收器:
垃圾回收器通过引用计数法和可达性分析法判断垃圾回收
引用计数法:给对象添加一引用计数器,被引用一次计数器值就加 1;当引用失效时,计数器值就减 1;计数器为 0
可达性分析法:以"GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的
垃圾回收算法
标记-清除算法
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
复制算法
复制算法主要用在from与to区,垃圾回收每次只发生在其中的一块区域里,然后将存活的对象复制到另一块为使用的区域,按顺序存放就可以了
标记整理算法
标记整理和标记清楚算法差不多,都是先标记 ,但是标记整理是将存活的对象移动到一定区域然后统一回收
分代收集算法
就是根据分代年龄进行垃圾回收
本地方法接口 本地方法库
本地方法接口
一个Native Method就是一个java调用非java代码的接口,与java虚拟机意外的系统交互。
本地方法库
就是提供 native接口的方法库,在jdk安装目录路下