深入理解JVM之二

149 阅读4分钟

javac 编译

将源码编译成字节码

字节码加载过程

加载

将字节码加载到方法区

由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部
并存储在运行时内存区的方法区
(整个代码当成模版放在方法代码、方法数据、静态变量中)

类加载器

双亲委派机制:
主要是沙箱安全机制
防止核心类库被修改
避免类的重复加载
保证被加载类的唯一性

加载阶段把数据存放在方法区

类元信息包括

类型信息
类型的常量池
字段信息
方法信息
类加载器的引用
Class实例引用
方法表

常量池包括

字面量
文本字符串
final常量值
基本数据类型
其他符号引用
类和结构的全限定名
字段名称和描述符
方法名称和描述符

加载阶段实例化对象

将加载到的数据转换为一个与目标类型对应的java.lang.Class对象实例(对象存放在堆中,对象和方法区之间有一个指针引用)

这个Class对象在日后就会作为方法区中该类的各种数据访问入口

验证

运行final是否合规
类型是否正确
静态变量是否合法等校验操作

准备

为静态变量分配内存并初始化默认值

解析

解析类的方法确保类与类之间的相互引用正确性,完成内存结构布局

初始化

静态变量初始化

编译之后的数据存放在类的clinit方法中

该方法的作用就是初始化一个类中的变量
使用用户指定的值覆盖之前在准备阶段设定的初始值 
任何invoke之类的字节码都无法调用<clinit>方法 
因为该方法只能在类加载的过程中由JVM调用

父类优先初始化

如果父类还没有被初始化,那么优先初始化父类,但在<clinit>方法内部不会显示调用父类的<clinit>方法,由JVM负责保证一个类的<clinit>方法执行之前,它的父类<clinit>方法已经被执行

JVM保证初始化时的数据安全

JVM必须确保一个类在初始化的过程中
如果多线程需要同时初始化它
仅仅只能允许其中一个线程对其执行初始化操作
其余线程必须等待
只有在活动线程执行完对类的初始化操作之后
才会通知正在等待的其他线程

使用

使用的过程就是在方法区和新生代本地方法栈中执行代码的过程

卸载

GC垃圾回收  

本地方法栈

当前线程执行main方法,main方法中调用subfun方法
在当前线程所在的本地方法栈中 有2个栈桢
sunfunc的方法出口指向了main的栈桢 说明执行完subfunc方法
就会回到main方法继续执行其他

程序计数器中存放的是线程执行方法代码的位置

局部变量表

用于存booleancharshortintfloatlongdouble等类型的数据 
以变量槽为最小单位 longdouble需要两个slot 所以线程不安全 
基本数据类型会直接存储值 引用数据类型会存放对象的引用

操作数栈

用于计算时的临时数据存储区、使用load执行将数据加载到此处

动态链接

动态链接(多态,编译器没有指明 运行时才会指明)指向常量池中的方法引用

方法出口

记录出栈地址即方法返回地址或异常地址

运行时数据区

堆区分为新生代和老年代 空间比例1:2
新生代分为 Eden和S0和S1 空间比例 8:1:1
Eden区存放的都是早生夕死的对象
经过一次垃圾回收如果该对象和跟对象是可达的 
那么就不会被回收 就转移到S0或S1区
S0和S1同一时刻只有有一个是有数据的 
另外一个是空的
对象的动态年龄如果达到15就会转移到老年代

垃圾回收

具体详见 葵花宝典上 最后一节