JVM-GC篇
GC:垃圾回收 垃圾指运行程序中没有任何指针指向的对象,还有内存中的碎片
STW:垃圾回收线程执行时,用户线程被占用,应用出现短暂停顿
并发:针对单核处理来说,cpu把一个时间段分为几个时间片段,然后来回切换,利用cpu的性能,给用户一种同时进行的感觉
并行:多核处理时,一个cpu处理一个进程,另一个cpu处理另一个进程,达到并行处理
System.gc():手动触发垃圾回收(会出现垃圾收集器不执行的情况)
安全点:在用户线程和GC线程之间的安全点,程序执行时并不是在所有地方都能停下来开始GC,只有到达安全点才能GC。 如果有线程一直没有进入到安全点,就会导致GC时JVM停顿时间延长。比如写了一个超大的循环导致线程一直没有进入到安全点,GC前停顿了8秒。就是说其他线程进入了安全点,但是有一个超大的循环一直没有进入,其他线程只能等待这个线程的到来,这样卡顿时间就久了。 所以安全点的选定以程序“是否具有让程序长时间执行的特征”为标准进行选定的。“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所以具有这些功能的指令才会产生Safepoint。这样就可以避免程序长时间无法进入safepoint 对于安全点,另一个需要考虑的问题就是如何在GC发生时让所有线程(这里不包括执行JNI调用的线程)都“跑”到最近的安全点上再停顿下来。 解决方法:主动式中断:主动式中断的思想是当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。
安全区域:线程处于sleep状态或者blocked状态的时候,这时线程无法响应jvm的中断请求,就需要安全区域。 安全区域是指在一段代码片段中,引用关系不会发生变化,在该区域的任何地方发生gc都是安全的。 当代码执行到安全区域时,首先标示自己已经进入了安全区域,那样如果在这段时间里jvm发起gc,就不用管标示自己在安全区域的那些线程了,在线程离开安全区域时,会检查系统是否正在执行gc,如果是那么就等到gc完成后再离开安全区域。
GC分类: Partial GC:并不收集整个GC堆的模式。其中分为: 新生代的回收:minorGC/Young GC 老年代的回收:MajorGC/Old GC Mixed GC :收集整个新生代和部分老年代的GC 只有G1支持这种回收方式 Full GC:收集整个堆 在通常,MajorGC 和Full GC 是等价的,但由于名词解读混乱,需要问情majorGC是全部回收,还是老年代回收
GC触发条件 年轻代触发:当Eden区满时,触发Minor GC 老年代触发:老年代区满了之后触发,如果MajorGC 之后还是空间不足,则会OOM Full GC : (1)System.gc()方法的调用 (2)老年代空间不足 (3)方法区空间不足 (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存(即新生代和老年代都满了之后) (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
检测垃圾,又称垃圾标记阶段 只有被标记死亡的对象,才会执行GC; 当对象不再被任何的存活对象继续引用时,就判断已经死亡了
判断对象存活的方式:引用计数算法(java不用)和可达性分析算法
缺点:引用计数算法因为对象的循环引用,放弃使用
可达性分析算法 GCRoot: 虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
堆里的对象实例不能作为root,如果一个指针保存了堆内存里的对象地址但该指针又不在堆内存里,那它就是root
对象的finalization体制 不要主动调用某个对象的finalize()方法 重写 finalize()方法可能会导致对象复活,会影响GC性能,造成死缓现象。 finalize()方法只能被调用一次 也就是说,该方法使得对象除了存在,死亡之外,出现了新的状态复活,通过该方法,对象可能复活重新使用,这时GC就不能回收该对象了,所以在可达性分析算法时,没有可达的连接后,不能直接回收,在判断使用该方法后(因为该方法只能使用一次),还是没有连接,此时该对象回收。
垃圾收集算法
常见三种算法:标记清除算法(Mark-Sweep)、复制算法(Copying)、标记压缩算法(Mark-Compact) 1.标记清除算法 分为标记和清除两个步骤,未被标记的则回收 标记过程和可达性分析算法类似,用GCRoot与后面对象的连接来看,前后有链接的header标记,没有链接的记为死亡对象,不进行标记,通过清除区清除 缺点:(1)效率不高,STW导致用户体验差 (**2)标记清除后会产生大量的不连续内存空间 会提前触发Full GC 2.复制算法 复制算法是在标记清除算法的改进,它把已经用了的内存(存活的对象)复制到另一块内存,类似于新生代的s0和s1
缺点:对象率存活高的时候,复制工作所花费的时间将不可忽视,对GC占时太长
3.标记整理(压缩)算法
在标记清除算法的基础上,标记整理算***将所有活得对象移动到一端,并对死亡对象进行处理,此时不会产生内存碎片 优点:消除内存分散的缺点,JVM只需要有一个内存的起始地址就可以了 缺点:效率低于复制算法,对象移动时,该对象被其它对象引用时,还需要调整引用的地址
对比来看,复制算法是效率最高的,但是浪费内存较大,标志压缩算法是常用的且平滑的
**年老代采用标志压缩算法和标记清除算法较多,年轻代使用复制算法多 4.分代收集算法
垃圾收集器 垃圾收集器分类 串行回收适合单CPU,并行和并发回收适合多CPU的 独占式回收会造成STW,而并发式回收不会造成STW 压缩式回收:在回收完成后,对存活的对象进行压缩整理,消除回收后的碎片 非压缩式回收:会产生碎片 年轻代垃圾回收器、老年代垃圾回收器
评估GC的性能指标 内存占用、暂停时间(STW)、吞吐量 吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间=程序的运行时间+内存回收的时间)
重点在于: 注重吞吐量时,意味着单位时间内,STW的时间最短;而注重低延迟时,暂停时间优先,意味着尽可能单次STW的时间最短
现在的标准下,在最大吞吐量优先的情况下,降低停顿时间。
垃圾回收器:
串行回收器(单CPU,适合Client模式下): Serial(复制算法,在JavaWeb中是不会使用的,影响交互)、 Serial old(标记整理算法 ,作为CMS收集器的后备预案,和ParallelScavenge收集器搭配)
并行回收器(缺点:CPU任务切换,有额外的开销): ParNew(即Parallel new,只处理新生代,并行多线程处理)、 Parallel Scavenge(复制算法,只处理新生代,并行多线程处理 ,同时被称为吞吐量优先的垃圾收集器,高效利用CPU)、 Parallel Old(吞吐量优先,标记压缩算法)
并发回收器(垃圾收集线程和用户线程同时运行):CMS(标记清除算法)、G1
CMS四阶段(标记清除算法): 初始标记:出现STW,标记GCRoot能直接关联的对象(直接关联对象小,速度快,STW时间短) 并发标记:从GCRoot直接关联的对象遍历对象图,标记全部垃圾对象,耗时长但与其它用户线程并发执行 重新标记:修正并发标记期间因用户线程继续运作而导致标记产生变动的部分对象的标记记录,出现STW,比初始标记STW长些,但是速度也很快 并发清除:清理死亡对象,与用户线程并发执行
垃圾-浮动:在垃圾清除的过程中,会伴有新的垃圾产生,只能等下次垃圾回收时再清除
重点:CMS回收过程中,应该确保应用程序用户线程有足够的内存空间,所以在堆内存使用率到达某一阈值时,就需要开始回收,若堆内预留内存不够,JVM会启动后备预案,临时启动SerialOld收集器重新进行老年代的垃圾收集
-XX:+PrintCommandLineFlags:查看命令相关参数(包含使用的垃圾收集器)
-XX:CMSFullGCsBeforeCompaction设置执行多少次Full GC后对内存空间进行验收整理
-XX:+Use CMSCompactAtFullCollection 指定在执行完Full GC后对内存空间压缩处理,无法并发执行,在-XX:CMSFullGCsBeforeCompaction设置Full GC执行次数且执行完毕之后 执行 该命令 多线程程序一定高于单线程程序嘛?(在单CPU情况下,并行多线程处理不一定比单线程处理效率高,因为单线程CPU不需要频繁的做任务切换,可以避免一些额外的开销)
总结: Client模式下(最小化使用内存和并行开销):Serial+SerialOld 最大化应用程序吞吐量:Parallel+ParallelOld 最小化GC中断或停顿时间:CMS+ParNew
G1(区域化分代式)并行与并发收集器,能建立可预测的停顿时间模型
回收范围:新生代+老年代 基于标记整理算法,将堆内内存分为各个Region区,里面包含了新生代和老年代
目标:在延迟可控的情况下,尽可能获得高吞吐量
使用命令-XX:USerG1GC启用G1
G1的四阶段: 初始化标记:出现STW,标记GCRoot能直接关联的对象(直接关联对象小,速度快,STW时间短) 并发标记:GCRoot对堆中的对象进行可达性分析,找出存活对象,并发执行 最终标记:修正并发标记期间因用户线程继续运作而导致标记产生变动的部分对象的标记记录,出现STW,比初始标记STW长些,但是速度也很快 筛选回收:对各个Region的回收价值和成本排序,根据用户期望GC停顿时间指定回收计划(用最少的时间回收包含垃圾最多的区域,采用单线程回收,STW)
使用场景:针对具有大内存,多处理的机器
最主要的应用是需要低GC延迟,并具有大堆的应用程序提供解决方案
区别:其他的垃圾收集器使用的内置的JVM线程执行GC,G1GC可以采用应用线程运行GC的工作,当JVM线程处理速度慢时,系统调用应用程序帮助加速垃圾回收过程。
Region:将整个java堆划分为约2048个大小相同独立的Region块,所有的Region大小相同,且在JVM生命周期中不会改变。 G1垃圾收集器增加了新的内存区域,叫Humongous内存区域,用于存储大对象,如果超出1.5个Region,就放入H区
跨Region引用对象:
Region不可能是孤立的,每个Region都有一个CardTable,每个CardTable都有若干大小为512字节的card用于存储其它Region的对象地址 如果对应到分代的情况,那就是在新生代中记录了老年代的一块块地址,在Minor GC的时候,直接从CardTable中获取到需要扫描的老年代对象,而不用去扫描整个老年代。(也就是说,把老年代的一个需要引用的对象放入新生代的一个Card中,然后进行MinorGC时,就不用扫描老年代了)
G1 回收器优化: 1)不要固定年轻代的大小 2)暂停时间目标不要太苛刻(太短),G1GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
ZGC:可伸缩的低延迟回收器
JVM性能监控及调优
1)架构调优和代码调优是优先的,之后是JVM调优 参数-Xms和-Xmx通常设置为相同值,避免运行时扩展JVM内存 2)年轻代设置:避免新生代设置过小,过小时会产生两种明显的现象,一MinorGC次数频繁,二可能导致MinorGC对象直接进入老年代,老年代不足会触发FullGC 避免新生代设置过大,过大会使年老代变小,导致FullGC频繁,过大还会导致MinorGC执行回收的时间大幅度增加 3)年老代设置: 注意低延迟的应用: 1 年老代使用并发收集器,所以大小需要小心设置,一般会考虑并发会话率和绘画持续时间等参数; 2 堆设置偏小,会造成内存碎片、高回收频率及应用暂停; 3 堆设置偏大,需要长时间的收集 吞吐量优先的应用: 吞吐量优先都有一个很大的年轻代和很小的年老代。原因是尽可能回收大部分短期对象,减少中期对象,年老代仅存放长期存活的对象。 4)方法区设置:空间大小的设置,最大空间和最小空间尽可能相同
