关于内存布局
经典的JVM内存布局.png
- 图中常量池,指的是运行时常量池
- 字符串常量池在堆中
OOM
设置JVM参数 -XX:+HeapDumpOnOutOfMemoryError,当遇到 OOM 异常时能输出堆内信息,
JDK使用元空间替换永久代,是为什么?
- 永久代在启动时固定大小,较难调优
- 动态加载类过多,容易导致永久代的OOM ,可通过-XX:MaxPermSize=1280m解决
- 永久代在垃圾回收过程中还存在诸多问题
永久代 ,元空间在本地内存中的分配差
永久代的所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移动至元空间内。
JVM Stack ( 虚拟机栈)
虚拟机栈.png
Native Method Stacks (本地方法栈)
- 线程对象私有
- 为 Native 方法服务
Program Counter Register (程序计数寄存器)
- 程序计数器用来存放执行指令的偏移量和行号指示器等,
- 线程执行或恢复都要依赖程序计数器。
- 程序计数器在各个线程之间互不影响,此区域也不会发生内存溢出异常
java线程与内存
java线程和内存.png
关于GC
简要GC
简要GC图.png
什么对象可以作为GC Roots
- 类静态属性中引用的对象
- 常量引用的对象
- 虚拟机栈中引用的对象
- 本地方法栈中引用的对象
垃圾回收算法
- 标记-清除,空间碎片易FGC
- 标记-整理,费内存空间
- 标记-复制,标记-整理的改进,内存空间分为Eden和两个Survivor区,每次只清理一个Eden和一个Survivor区,减少了内存空间的浪费,作为主流的YGC算法
垃圾回收器
Serial回收器
- 串行单线程
- STW
- YGC:标记-复制,FGC:标记-整理
CMS回收器
- 以最短停顿时间为目标
- 初始标记、并发标记、重新标记、并发清除
- 1、3步会导致STW
- 标记-清除算法
G1垃圾回收器
- 可预测停顿时间
- JDK7版本推出,JDK11版本默认的垃圾回收器
- 添加参数 -XX:+UseGIGC
- 采用标记-复制算法,避免碎片问题
- 可预测的停顿时间,能够在指定时间内完成垃圾回收
- 将堆空间分割成若干相同的区域,即region,包括Eden,Survivor,Old,Humongous
- SO/SI 的功能由 GI 中的 Survivor region 来承载
- 初始标记时存在STW
CMS和G1的区别
- 使用范围:CMS作用于老年代配合新生代的Serial和ParNew收集器,G1作用于新老年代
- STW的时间:CMS以最小停顿时间为目标,G1可预测垃圾回收的停顿时间
- 垃圾碎片:CMS采用标记-清除算法,G1采用标记-整理算法
- 清理过程不同。
如何解决CMS回收器的空间碎片
由于CMS回收器采用的是标记-清除算法,因此会产生大量的空间碎片,可添加以下JVM参数强制JVM 在 FGC执行n次后对老年代进行空间碎片整理,每次整理会引发 STW
-XX:+UseCMSCompactAtFullCollection
-XX:+CMSFul!GCsBeforeCompaction=n
其他
类加载过程
类加载过程.png
- 加载,将.class文件加载到jvm中并生成Class对象
- 验证,详细验证类
- 准备,为静态属性分配内存空间,赋默认值
- 解析,符号引用转直接引用
- 初始化,为所有静态变量赋正确的值。
双亲委派模型
双亲委派模型.png
双亲委派模型的作用
- 重复类的加载
- 保证同一个类只会被加载一次
对象实例化过程
- 确认类元信息是否存在
- 分配对象内存
- 设定默认值
- 设置对象头
- 执行构造方法
- 将内存空间赋值给变量
monitorenter
jvm规定,当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。拿hotspot举例:
- ObjectMonitor对象就是monitor。
- 当线程执行synchronized的修饰的代码块时,得先获取到修饰对象的ObjectMonitor对象
- 每个线程都存在两个ObjectMonitor对象列表,分别为free和used列表,先向自身的free列表请求若存在则使用,若不存在则从global list中申请。
ObjectMonitor对象的关键属性
- 线程的重入次数
- 等待线程数
- 该monitor关联的对象
- 当前持有锁的线程
对象的引用类型
对象的引用类型.png
逃逸分析
- 如何分析是否逃逸,可通过判断新对象是否对外部线程可见,对外部线程可见,无逃逸
- 影响1:将堆分配转化为栈分配,减少垃圾回收的频率
- 影响2: 同步消除,如果发现某个对象只能从一个线程可访问,那么在这个对象上的操作可以不需要同步。
- 影响3: 分离对象或标量替换。把对象分解成一个个基本类型,并且内存分配不再是分配在堆上,而是分配在栈上
不发生逃逸时,HotSpot可以在栈上分配对象?
不能,HotSpot并没有实现在栈上分配对象,而是使用标量替换的方式。
内存屏障
- 写内存屏障(Store Memory Barrier):在指令后插入Store Barrier,能让写入缓存中最新数据更新写入主内存中,让其他线程可见。强制写入主内存,这种显示调用,不会让CPU去进行指令重排序
- 读内存屏障(Load Memory Barrier):在指令后插入Load Barrier,可以让高速缓存中的数据失效,强制重新从主内存中加载数据。也是不会让CPU去进行指令重排。
jvm参数调优
- Java整个堆大小设置,Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍