JVM——垃圾回收机制与内存分配模型
JVM——垃圾回收机制与内存分配模型
什么内存区域的对象需要使用垃圾回收机制回收?
前言:
线程私有的内存区域的生命周期随线程而生,随线程而灭。分配与回收具有确定性,当方法或线程结束时就会被回收,因此,JVM的垃圾回收器及内存分配模型都是针对线程共享的内存区域
(Java堆+方法区)
回收对象前,如何判断对象已死?
一、对象是否已死?
垃圾回收器在对堆进行垃圾回收前,要先判断这些对象哪些还活着,哪些已死?
1、引用计数法:给每个对象创建时附加一个引用计数器并初始化为0,被引用+1,引用失效-1,在任意时刻,若计数器值为0,说明对象已死。
实现简单,判定效率高,但无法解决循环引用(只有a引用b,同时b引用a,这两个计数器都不为0,但JVM把两个当做垃圾回收了)
2、可达性分析算法
(JVM就使用该算法):通过一系列成为“GC Roots”的对象作为起始点,从这些结点开始向下搜索,搜索走过的路径称为“引用链”,当一个对象到GC Roots没有任何引用链相连时(从GC Roots到该对象不可达),证明此对象是不可用
的(是不可用,并没有直接判断死
)。
可以做GC Roots的有哪些对象?
能做GC Roots的对象:
虚拟机栈(栈帧中的局部变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(本地方法)引用的对象引用的具体分类(JDK1.2后)?
引用可分为强引用、软引用、弱引用、虚引用四种,强度依次降低。
①强引用:代码中直接明示(Object obj = new Object()),只要强引用还在,垃圾回收器永远不会回收此对象(即便内存不够)
②软引用:有用但不必须的对象,若内存不够时,垃圾回收器对软引用对象进行回收,如果内存足够,则会保留软引用对象
③弱引用:非必须的对象,垃圾回收机制开启,无论内存够不够用,都会将弱引用对象进行回收
④虚引用:一个对象的虚引用完全不会对该对象的生存周期有影响。
上面的可达性算法宣布对象不可用,并没有宣布对象死亡,对象到底是否死亡?
即使是不可达的对象,也并没有”非死不可”,要宣告一个对象真正的死亡,需要经历两次标记过程:第一次标记就是可达性算法,如果不可达,标记一次并判断该对象是否有必要执行finalize()
方法,若当前对象没有覆写finalize()方法
(继承Object默认的)或finalize()方法已被JVM调用过
,该对象被二次标记,则该对象被判定真正的死了。
如果覆写了finalize()方法且没被JVM调用过,该对象被放在F-Queue队列中,由JVM自动创建一个Finalizer线程执行,若对象覆写的finalize()方法中重新与任意一个GC Roots建立引用链
,则该对象死里逃生,但若在覆写的方法中没有建立引用链,则依旧被判死亡。
任何一个对象的finalize()都只会被系统自动调用一次
(最多死里逃生一次)
方法区上的内容如何被回收?
方法区主要回收两个部分:废弃常量与无用类。
废弃常量:没有任何引用指向该常量
无用类:满足3个条件
①该类所有实例已被回收
②该类的类加载器ClassLoader被回收
③该类的Class对象没被引用,无法通过反射访问该类的内容
JVM只是有可能
会对该类进行回收,要求太严格,几乎不会被回收(方法区叫做”永久代”),JVM会不定期的对方法区进行回收来防止永久代溢出。
Java堆分为新生代与老年代,分别使用什么垃圾回收算法及其原理?
二、垃圾回收算法(GC)
1、垃圾算法的基础:标记-清除算法
算法分为标记,清除两个阶段,首先标记出所有需要回收的对象(宣告对象彻底死亡的那两次标记),在标记完成后统一回收所有被标记的对象。
缺点:
①效率问题:标记和清除两个阶段的效率都不高
②会产生大量的不连续碎片,若程序运行中需要分配较大对象时,无法找到足够大的连续空间。
2、新生代垃圾回收算法(Minor GC
):复制算法
新生代对象具有“朝生夕灭”的特点(新生代对象存活率比较低)
复制算法具体如何实现?
将新生代内存分为一块Eden区和两块大小相等的Survivor区,Eden区与两块Survivor区(From区和To区)的比例为8
1,每次使用Eden区和其中一块Survivor(From),当需要回收时,将Eden区和Survivor中还存活的对象一次性复制到另一块Survivor空间(To)上,最后清理掉Eden区和刚才用过的那块Survivor区(From)。下次再回收,又使用Eden去和其中一块Survivor(To),重复上面步骤即可。
新生代为何如此设计?
新生代内存的利用率为90%(Eden+1/2*Survivor),剩下的10%用来存放回收后存活的对象。由于新生代朝生夕灭的特性,一个对象创建后不久就死了,还存活的对象不多(这才敢把Survivor区设置的那么小)。
新生代对象怎么进入老年代?
①经过反复复制,有的对象可能在Survivor的两块区间中复制来复制去,如此交换15次,该对象就不太符合新生代的特性了(还没死),就将该对象存入老年代。
②由于复制算法需要将Eden区+1/2Survivor区中还存活的对象放到1/2Survivor区中,如果这1/2*Survivor区放不下还存活的对象,那么这些对象直接通过分配担保机制
进入老年代。
复制算法的优点有哪些?
实现简单,运行高效,无空间碎片问题
3、老年代垃圾回收算法(Major GC/Full GC
):标记-整理算法
老年代的对象存活率都比较高,因此不能使用复制算法(大量对象复制操作)。
标记-整理算法的流程是什么?
标记阶段与标记-清除算法是相同的(判断对象已死的那两次标记)
整理阶段:把存活的对象向一端移动,而后一次性将存活对象边界以外的内存全部清理掉。
什么是分代算法?
4、分代收集算法-存活对象的划分
一般将Java堆分为新生代和老年代。
新生代(Minor GC)中,每次垃圾回收都会有大量的对象死去,只有少量存活(朝生夕灭),采用复制算法。
老年代(Full GC/Major GC)中,对象存活率高,没有额外空间对它进行分配担保,采用标记-整理算法。
三、垃圾收集器
垃圾收集器的分类?<没有最优的收集器,只有最适合的收集器>
新生代GC收集器:Serial,ParNew,Parallel Scavenge
老年代GC收集器:Serial Old,Parallel Old,CMS
全区域GC收集器:G1
前言:并行,并发,吞吐量到底什么
并行:多条垃圾收集线程并行工作,用户线程仍处于等待状态
并发:用户线程与垃圾收集线程同时执行(不一定并行,可能会交替执行),用户程序继续执行,而垃圾收集程序在另一个CPU上。
吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))
不同的垃圾收集器具体是怎样的?
1、Serial收集器:串行GC,单线程收集器,在Serial进行垃圾收集时,必须暂停其他所有工作线程,直到Serial收集结束(Stop The World)
应用于:JVM运行在Client模式下默认的新生代收集器
优点:简单而高效,对于单核CPU,没有线程交互开销,可获得最高的单线程收集效率
2、ParNew收集器:Serial收集器的多线程版本
应用于:JVM运行在Server模式下默认的新生代收集器
优点:随着CPU数量的增加,提高GC对系统资源的利用率
3、Parallel Scavenge收集器:吞吐量优先收集器,具有高吞吐量。
应用于:高吞吐量场景
优点:高吞吐量,GC自适应调节策略
4、Serial Old收集器:Serial收集器的老年代版本,单线程收集器,使用标记-整理算法
应用于:
Client模式:Serial Old主要用于Client模式下的虚拟机
Server模式:CMS收集器的备用
5、Parallel Old收集器:Parallel Scavenge的老年代版本
6、CMS收集器
:Concurrent Mark Sweep,以获取最短系统停顿时间为目标的垃圾收集器。使用标记-清除
算法。
流程分为4个步骤:
①初始标记:需要Stop the World标记一下GC Roots能够直接关联到的对象,速度很快。
②并发标记:耗时较长,并发
③重新标记:需要Stop the World,速度较快
④并发清除:耗时较长,并发
整个过程中耗时最长的并发标记和并发清除过程收集器线程和用户线程是并发执行的,所以总体上CMS收集器与用户线程是并发执行的。
应用于:B/S系统(浏览器-服务器)服务端(重视响应速度)
优缺点:并发收集,低停顿,但对CPU资源敏感,产生大量不连续空间碎片(标记-清除算法导致)
7、G1收集器:全区域垃圾回收器,同样追求低停顿。
GC日志的查看?
如何查看GC日志即各参数的意义
初始设置参数:
-XX:+PrintGCDetails:打印详细GC日志
-XX:+UseSerialGC:使用Serial回收器
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:SurvivorRatio:新生代中Eden区域与一块Survivor区域容量比值,默认为8(注意:是Eden区与1块Survivor的比值,但新生代一共有2块Survivor,要算Survivor总大小,需要再将该Survivor*2
)
GC日志:
出现Full GC:说明这次GC发生了Stop-The-World(在日志中
不是区分新/老生代的,但其他地方都指老年代)
方括号内:GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存总容量)
方括号外:GC前Java堆已使用容量->GC后Java堆已使用容量
四、内存分配模型
JVM到底是如何给一个对象分配内存的?
还没有评论,来说两句吧...