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字节的卡,每一个卡标记它有没有指向新生代的引用