javac 编译
将源码编译成字节码
字节码加载过程
加载
将字节码加载到方法区
由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部 并存储在运行时内存区的方法区 (整个代码当成模版放在方法代码、方法数据、静态变量中)
类加载器
双亲委派机制: 主要是沙箱安全机制 防止核心类库被修改 避免类的重复加载 保证被加载类的唯一性
类元信息包括
类型信息 类型的常量池 字段信息 方法信息 类加载器的引用 Class实例引用 方法表
常量池包括
字面量 文本字符串 final常量值 基本数据类型 其他符号引用 类和结构的全限定名 字段名称和描述符 方法名称和描述符
加载阶段实例化对象
将加载到的数据转换为一个与目标类型对应的java.lang.Class对象实例(对象存放在堆中,对象和方法区之间有一个指针引用) 这个Class对象在日后就会作为方法区中该类的各种数据访问入口
验证
运行final是否合规 类型是否正确 静态变量是否合法等校验操作
准备
为静态变量分配内存并初始化默认值
解析
解析类的方法确保类与类之间的相互引用正确性,完成内存结构布局
初始化
静态变量初始化
该方法的作用就是初始化一个类中的变量 使用用户指定的值覆盖之前在准备阶段设定的初始值 任何invoke之类的字节码都无法调用<clinit>方法 因为该方法只能在类加载的过程中由JVM调用
父类优先初始化
如果父类还没有被初始化,那么优先初始化父类,但在<clinit>方法内部不会显示调用父类的<clinit>方法,由JVM负责保证一个类的<clinit>方法执行之前,它的父类<clinit>方法已经被执行
JVM保证初始化时的数据安全
JVM必须确保一个类在初始化的过程中 如果多线程需要同时初始化它 仅仅只能允许其中一个线程对其执行初始化操作 其余线程必须等待 只有在活动线程执行完对类的初始化操作之后 才会通知正在等待的其他线程
使用
使用的过程就是在方法区和新生代本地方法栈中执行代码的过程
卸载
GC垃圾回收
本地方法栈
当前线程执行main方法,main方法中调用subfun方法 在当前线程所在的本地方法栈中 有2个栈桢 sunfunc的方法出口指向了main的栈桢 说明执行完subfunc方法 就会回到main方法继续执行其他 程序计数器中存放的是线程执行方法代码的位置
局部变量表
用于存boolean、char、short、int、float、long、double等类型的数据 以变量槽为最小单位 long和double需要两个slot 所以线程不安全 基本数据类型会直接存储值 引用数据类型会存放对象的引用
操作数栈
用于计算时的临时数据存储区、使用load执行将数据加载到此处
动态链接
动态链接(多态,编译器没有指明 运行时才会指明)指向常量池中的方法引用
方法出口
记录出栈地址即方法返回地址或异常地址
运行时数据区
堆区分为新生代和老年代 空间比例1:2 新生代分为 Eden和S0和S1 空间比例 8:1:1 Eden区存放的都是早生夕死的对象 经过一次垃圾回收如果该对象和跟对象是可达的 那么就不会被回收 就转移到S0或S1区 S0和S1同一时刻只有有一个是有数据的 另外一个是空的 对象的动态年龄如果达到15就会转移到老年代
垃圾回收