JVM虚拟机
JVM虚拟机
内存区域
程序计数器
线程独有,存储正在执行的方法地址
Java虚拟机栈
线程独有,存储调用的方法
本地方法栈
线程独有,存储本地方法
堆
线程共享,存储对象实例
方法区
线程共享,存放元数据,类结构信息,常量
类加载
加载
查找字节流(class文件),创建类
使用双亲委派模型,每当一个类加载器接收到加载请求时,它会先将请求转发给父类加载器。在父类加载器
没有找到所请求的类的情况下,该类加载器才会尝试去加载,可以避免类重复加载,也保证核心api不被更改
类加载顺序:BootstrapClassLoader->ExtensionClassLoader->AppClassLoader->CustomClassLoader
链接
把创建的类合并到虚拟机中
验证
确保类满足虚拟机约束条件
准备
为静态字段分配内存
解析
把对类的符号引用(类的基本信息)转化为实际引用
初始化
执行类构造器的clinit方法(所有类只会被执行一次)
方法调用
识别方法方式:方法名、返回类型、参数
重载
方法名相同,参数不同,在编译阶段就会识别为不同方法
重写
子类继承父类方法,方法名相同,参数也相同,返回值也相同,逻辑不同
异常
Exception可被捕获,Error不可被捕获
构造异常时会生成异常的栈轨迹,每个方法附带一个异常表,每个异常对应一个出口,finally中的代码会被放在每一个出口,除非操作系统层面退出,不然一定会被执行
反射
在运行过程中可以知道类的属性和方法以及调用对象的属性和方法
对象内存布局
对象头包含标记字段和类型指针,标记字段包含哈希码、gc信息和锁信息,类型指针指向对应的类
压缩指针
通过内存对齐,由标记地址改为标记对象,把指针消耗的内存减半
垃圾回收
引用计数法
为每个对象添加一个引用计数器,统计指向该对象的引用个数,如果为0,就认为是垃圾
缺点:需要额外空间,更新操作繁琐,且无法处理循环引用问题,造成内存泄漏
可达性分析法
从GC Roots(初始存活对象)出发,探索能被引用到的对象,没探索到的就是垃圾
缺点:多线程可能造成误报和漏报
Stop-The-World
垃圾回收时,要等待所有线程到达安全点(堆栈不会发生变化)
垃圾回收方式
标记清除法
标记死亡对象的内存,放入空闲内存列表中,供需要时使用
缺点:内存碎片化,按照列表访问空闲内存效率低
标记压缩法
标记存活的对象,将其压缩到内存的起始位置,避免内存碎片化
缺点:时间开销大
标记复制法
把内存区域分为两部分,平时只用其中一块,标记存活对象,需要回收时将其复制到另一块
缺点:空间开销大
垃圾收集器
Serial GC
单线程,回收时会stop-the-world,数据结构简单,老年代采用标记整理法
ParNew GC
多线程的Serial GC
CMS GC(已被抛弃)
注重暂停时间短,使用标记清除法
Parrallel GC
注重吞吐量,老年代采用标记整理法
G1 GC(默认)
兼顾暂停时间时和吞吐量,把堆分区,类似标记整理法
堆划分
新生代
一个Eden和两个Survivor(一个空是空的),根据生成对象速率动态调整比例
Eden
new新对象的区域,因为堆是线程共享的,所以一般每个线程会预先分配一小块内存,然后在自己的内存里面新建对象
Survivor
如果Eden区内存耗尽,会触发Minor GC,把Eden里存活的和有内存的那个Survivor移动到空的那个Survivor中去(标记复制法),Minor GC前会检查老年代空间是否足够,不足够的话就改成Full GC(新生代老年代一起回收)
老年代
如果一个对象经历了一定数量的Minor GC(默认是15),或者单个Survivor区被占用一半,会把年代高的对象晋升到老年代
卡表
为了保证被老年代引用的新生代不被回收,把堆分为512字节的卡,每一个卡标记它有没有指向新生代的引用