JVM垃圾回收
JVM (java虚拟机)
参考文献:
Java 虚拟机(JVM )- 内存与垃圾回收篇概述哔哩哔哩bilibili
Java开发必须掌握的线上问题排查命令-HollisChuang's Blog
JVM
JVM:java虚拟机,执行JAVA字节码的虚拟计算机。
是否遇到过这些问题?
- 运行着的线上系统突然卡死,系统无法访问,甚至直接OOM
- JVM GC问题
- JVM参数
所有java程序都运行在JVM上
JVM特点
-
代码一次编译,到处运行(字节码)
-
自动内存管理
-
自动垃圾回收功能
-
一个JAVA程序进程对应一个JVM
-
JVM允许一个应用有多个线程并行的执行
JVM的整体结构
堆 heap
对进程独立,对线程为共享
栈存放对象、指针,而堆空间存放实例
-
对象、指针 指向堆中存放的实例。
-
对象出栈时,堆中对应的内容不会回收;而是新的实例进入堆且堆放不下时,发生GC时,才进行回收。
-
GC的本质是:回收已经出栈了的对象或指针的实例。
基于分代收集理论设计,堆空间分为:
逻辑上分为:新生区+养老区+元空间(方法区)
实际上的堆(heap)分为:新生区+养老区
新生区具体细分为:
Eden + Survivor1 或 Survivor2 (Survivor1与Survivor2只有一个存放信息)
Eden + from 或 to
各区域的默认比例与设置参数
默认情况下:
新生区 : 养老区 = 1/3:2/3
Eden :S1 :S2 = 8/10:1/10:1/10
堆空间大小的设置
这里实际上指 新生区 + 养老区
-Xms:用于表示堆区的起始内存 (-X:是jvm的运行参数;ms:是memory start)
-Xmx:用于表示堆区的最大内存 (mx:是memory maximum)
-
堆空间,默认单位为kb,但后面可以接k、m、G作为单位,如:-Xms 6144k;-Xms 6m;
-
不设置堆空间大小时,默认初始内存大小为:主机内存/64,最大内存大小为:主机内存/4
-
开发中建议将-Xms与-Xmx设置为一样,原因为:-Xms与-Xmx不一致时,内存不足时会进行扩容,内存充裕时会进行缩容,造成系统资源的不必要损耗。
-
一旦堆区中的内存大小超过 '-Xmx',将会抛出OOM(outofmemory)
查看堆使用率
(一)方法一
# 获取pid
jps
jstat -gc pid
(二)方法二
# 添加参数
-XX:+PrintGCtails
实例分配的过程与回收过程
(1)新对象申请
(2)Eden判断是否放得下
Eden放得下,将新对象分配到Eden
Eden放不下,发生一次YGC,YGC后再进行判断Eden是否放得下
Eden放得下,将新对象分配到Eden
超大对象,Eden放不下,判断Old是否放得下
Old放得下,将新对象分配到Old
Old放不下,发生一次FGC,FGC后再进行判断Old是否放得下
Old放得下,将对象分配到Old
Old放不下,发生outofmemory(OOM)
YGC的动作:
(1)YGC时,回收Eden与Servivor中没有对象的实例
剩余的实例存放到空的S0/S1中,这种情况是S0/S1空间足够
这个时候会判断对象是否age>阈值(默认15),是则直接放到养老区
S0/S1空间不足时,将实例放到养老区
FGC的动作:
(1)YGC时,回收Old中没有对象的实例
## 特别需要注意的:
(1)发生GC时,会导致用户线程(作业线程)暂停。
(2)实例每在新生区移动一次,age+1
MinorGC、MajorGC、FullGC的区别
GC按照回收区域分为两大种类型:
(1)部分收集(Partial GC)
(2)整堆收集(Full GC)
-
MinorGC == Young GC,新生区收集,只针对Eden、S0、S1
-
MajorGC == Old GC,养生区收集,只针对Old
-
Full GC,整堆收集,收集整个java堆和方法区的垃圾收集
新生区GC触发机制与特性
-
触发机制:Eden空间不足,触发YGC
-
Minor GC触发很频繁,回收速度也较快
-
GC会引发STW,暂停用户线程(作业线程),等垃圾回收结束,用户线程才恢复运行
养生区触发机制与特性
特别的:目前,只有CMS收集器会有单独收集老年代的行为。其他收集器均无此行为。
- 触发机制:Old空间不足,触发MajorGC(Old GC)
Full GC触发机制与特性
对整堆(新生代,养生代)和方法区的垃圾收集。
触发机制:
-
调用system.gc(),系统建议执行Full GC,但是不一定会执行;
-
养生代空间不足;
-
方法区空间不足;
-
通过Minor GC后进入养生代的实例大于养生代的可用内存
-
S0/S1复制时,Survivor区空间不足,将实例转存到养生代,但实例大于养生代的可用内存
总结
-
Minor GC是清理新生代,但Survivor区满时不会触发;
-
Major GC是清理养生代;
-
Full GC是清理整个堆和方法区,包括新生代,养生代,方法区。
JAVA堆分代思想
-
分代的唯一理由就是优化GC性能。
-
若不进行分代,则每次GC都需要堆所有区域进行扫描。
内存分配策略
-
优先分配到Eden
-
大对象直接分配到养生区
-
长期存活的对象分配到养生区
TLAB (给每个线程单独分配的缓冲区,私有)
thread local allocation buffer
优势:提高对象的创建效率
为什么有TLAB
-
堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
-
由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间是线程不安全的
-
为避免多个线程操作同一地址,需要使用加锁等机制,进而影响分配速度。
-
所以进入TLAB机制!!!
什么是TLAB
-
对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域。
-
一旦对象在TLAB空间分配内存失败时(实例过大,或TLAB写满等),JVM就会尝试通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存
-
TLAB默认开启
-
默认下TLAB空间非常小,仅占整个Eden空间的1%
参数:
# TLAB的开与关
-XX:+UseTLAB
-XX:-UseTLAB
# 设置TLAB空间所占用Eden空间的百分比大小
-XX:TLABWasteTargetPercent=1
堆空间的参数设置
-Xms10m
设置堆最小值为10M
-Xmx10m
设置堆最大值为10M
-XX:SurvivorRatio=8
年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1,一个Survivor区占整个年轻代的1/10
-XX:NewRatio=3
设置年轻代(EC+S0C+S1C)和年老代(OC)的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4(jdk1.8,默认2)
-XX:+PrintGCDetails
打印GC的具体信息
-XX:loggc:D:/a.log
将jvm的日志存储到指定文件
#参考链接
https://www.jianshu.com/p/bcdae3c80c1d