本文目录一览:
- 1、【JVM】对象分配与回收--垃圾回收机制
- 2、如何通知java虚拟机进行垃圾回收?以及垃圾回收机制的原理是什么
- 3、Java虚拟机垃圾回收机制是怎样的?
- 4、java中垃圾回收器让工作线程停顿下来是怎么做的?
- 5、深入理解GC垃圾回收机制
- 6、java垃圾回收是什么?
【JVM】对象分配与回收--垃圾回收机制
对象回收需要确认三件事,那些需要回收(对象存活判定,二次标记),何时回收(GC触发条件)以及如何回收(垃圾回收算法,垃圾回收器)
1)引用计数法
2)可达性分析:GCRoots作为起始点,沿着引用链搜索。
GCRoots可以为:虚拟机栈中引用的对象;本地方法栈中native引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;
1)强引用:即常见的那种引用,一个引用指向堆内存中对象。
2)软引用:还有用但非必须的,当空间不足了(即将OOM),才会对它进行回收。
3)弱引用:也是非必须的,不管空间剩余多少,只要有垃圾回收就进行回收。
4)虚引用:一个对象是否有虚引用,不会对它的生存事件有任何影响,也无法通过虚引用获得一个实例。唯一目的能在对象被回收时收到一个系统通知。
1)当对象被判定为不可达后,会进行一次标记,并筛选出覆盖了finalize方法且还没被执行过的对象进入下一步,那些没有覆盖的,或覆盖但已执行过的(finalize只能执行一次)将会被回收。
2)将筛选出的对象加入一个队列,并有一个优先级很低的Finalier线程去执行队列中对象的finalize方法,若finalize方法中该对象重新获得了引用,则复活,否则在第二次标记时他将被回收(第二次标记就是第一步中的标记,循环这两步)。
1)标记清除:将不可达的对象进行标记,GC时回收、----效率低、空间碎片
2)复制:将内存区域(堆)分为两块,每次只使用其中的一块。将可达对象进行标记,复制到另一边,然后将刚才那一边全部清空。
这中算法适用于存活对象很少的情况,经常被用于新生代的垃圾回收。
3)标记整理:将不可达对象标记后,清除对象后将存活对象向一端移动,减少空间碎片的产生。
该算法被广泛使用于老年代中。
4)分代收集:由于不同的对象有自己不同的特点。将区域划分为新生代与老年代。
新生代:对象大都朝生夕死,存活时间短。每次GC只有少量对象存活。一般用 复制算法 。(复制算法一般需要空间分配担保空间)
老年代:对象存活时间较长。也没有额外空间对他进行分配担保,更适合标记清除 、标记整理算法。
要注意垃圾回收器的设计目标是,有的是为了减少STW的时间,有的是为了吞吐量。这也应该作为一个选择回收器的标准。
1)Serial / Serial Old
Serial是一个单线程的垃圾回收器,进行垃圾回收时,必然要STW。Serial在新生代采用复制算法,其对应的老年代回收器在老年代采用标记整理法。
2)ParNew
ParNew实质上一个多线程的Serial,他能够实现多个线程进行垃圾回收,但仍是需要用户线程STW之后才能并发执行垃圾回收。一般开启CPU核数个线程,用-XX:ParallelGCThreads设置。也可以用-XX:SurvivorRatio设置年轻代Eden与Survivor的比例,-XX:HandlerPromotionFailure设置空间分配担保是否开启。
是一种年轻代的垃圾回收器,采用复制算法。
3)Parallel Scavenge收集器 /Parallel Old
这是一款新生代收集器,也是采用复制算法。特点是关注点不在于停顿时间,而在于吞吐量。可通过时设置-XX:UseAdapatorSizePolicy为true将该收集器设为一个自适应调节策略,会自动根据吞吐量与停顿时间等因素进行自适应调节。
相关参数:设置吞吐量大小-XX: GCTimeRatio,-XX:MaxGCPauseMillis:设置最大垃圾收集停顿时间,-XX:UseAdaptativeSizePolicy自适应的设置新生代中区域的比例
Parallel Old是它的老年代的回收器,采用多线程和标记整理算法实现,可以选择Parallel Scavenge与Parallel Old配合使用组合为一个注重吞吐量的垃圾回收机制。
4)CMS (Concurrent Mark Sweep)老年代回收器!
由名字就可以看出来,是一款基于并发的标记清除算法的收集器,CMS收集器是针对于老年带的收集器,以最短停顿时间为目标。
STEP 分为四个步骤,其中耗时最长的并发标记与并发清除是与用户线程并发完成的,而其它两个步骤会有短暂的停顿,以此种方式尽量减少停顿时间
a.初始标记:就是标记一些GCRoots的直接引用,速度非常快,这一过程会有短暂的SWT
b.并发标记:与用户线程并发的进行GCRoots tracing,沿引用链去标记不可达的对象。
c.重新标记:由于第二步是与用户线程一起发生的,有可能在回收过程中会有新的垃圾产生,这一步就是对于这些浮动垃圾进行标记(短暂停顿)
d.并发清除:与用户线程并发进行垃圾回收,采用标记清除算法。
优缺点
并发收集、低停顿
a.对CPU资源敏感,降低吞吐量(吞吐量指用户线程占用时间/总运行时间,这种算法占用了用户现成的一部分CPU时间)(并发涉及的程序都会争抢CPU资源)
b.标记清除算法导致的空间碎片:可能会造成大量空间浪费,若大对象(直接进入老年代)存储时,则会由于连续空间不足引发FullGC。可通过设置-XX:UseCMSCompactAtFullCollection开关,设置是否在FullGC前执行碎片整理合并。
c.浮动垃圾:在并发阶段,用户也可能会随时产生垃圾。在这里需要考虑另一个问题,由于是并发的,所以我们需要考虑用户线程需要占用一部分存储空间,就不可能等到老年代快满了再回收垃圾,需预留给用户线程一些空间。可以通过设置-XX:CMSInitialingOccupationFraction来设置触发FullGC时内存空间使用比例,若这个参数过低,则会增加发生GC的次数;若过高,留给用户空间的内存不够,又会引发Concurrent Mode Failure,而启用Serial Old收集器作为备份方案。
5)G1(Garbage first)
特点
a.将新生代与年老代同一划分为多个Region区域。
b.标记整理算法。
c.与用户线程并发执行。
d.可预测停顿时间。因为可以有计划的避免在整个java堆中进行全局的回收,将堆划分为多个Region,并为每个计算Region里垃圾堆积的价值大小(回收空间大小以及时间),根据价值维护一个Region的优先列表,每次都选取列表中第一个进行回收,即回收价值最大的那个Region。
需要考虑的问题是:化整为零后,多个region之间的一些引用如何确定呢(新生代老年代也有该问题),虚拟机采用Remember Set来避免全表扫描。每个region维护一个Remember Set,在对引用类型进行写操作时,会发生写中断,此时检查该引用的对象是否在别的Region中,若是,则将该信息写入他自己所属Region的RememberSet中。以便在对某一个Region进行可达性分析是不需要全表扫描。
STEP
a.初始标记:仅标记GCRoot是直接引用
b.并发标记:并发的标记所有不在引用链上的引用
c.最终标记:在并发标记阶段新产生的对象会写入Remember set log中,在该阶段将Remember Set log中的数据更新进Remember set中,进行标记。
d.筛选回收:!!!不是并发的,对Region的价值进行排序,并根据用户希望的GC停顿时间制定回收计划,由于只回收一个Region,速度也很快,就没有采取并发。
从前面已经知道了,我们根绝对象生存时间的长短不一这一特征,将堆划分为年轻代和年老代。这里进一步了解年轻代内部划分,以及分配对象时区域之间是如何协助的,从而进一步能够知道GC触发的时间。
1)年轻代的划分与基本工作机制
年轻代一般采用复制算法,将年轻代划分为Eden区与FromSurvivor、ToSurvivor。分配对象时,首先分配到Eden与FromSurvivor区中,然后将可达的对象复制到ToSurvivor区中,注意此时将这些对象的年龄+1,并清除Eden+FromS中的无用对象。然后再将FromS变为ToS,ToS变为FromS,(也省去了将To中的对象复制给From中这一步骤)以便下一次的对象回收。
2)对象分配的机制
a. 一般对象首先分配给Eden区,若空间不足则会触发MinorGC。
b. 大对象会直接分配到年老代,若空间不足触发FullGC。
c. 对象在几个情况下将从年轻代进入年老代:
c1:对象年龄到了(默认为15,在复制过程中年龄增加,因为可达而熬过了一次GC嘛)可通过-XX:MaxTenuringThreshole设置进入年老代的年龄。
c2:动态对象年龄判断:当年轻代中同一年龄的对象所占存储空间之和大于 Survivor 的一半时(占满了toSurvivor?),将大于等于该年龄的对象都放入年老代
c3:空间分配担保:年轻代采用复制算法,就需要有空间在survivor区装不下存活对象的时候帮忙存储多出来的对象(一般发生于一次MinorGC之前,存活对象过多导致survivor放不下了),年老代即为空间分配担保的空间。发生空间分配担保时,会进入老年代。
3)空间分配担保机制
年老代进行空间分配担保是有风险的,若担保过来的对象超过了剩余空间,那么年老代会发生FullGC。
所以在发生MinorGC之前,虚拟机会判断新生代所有对象总空间(!!一次保障性的对比,若成立则其子集肯定成立)是否小于年老代最大连续空间大小,若小于,则担保成功,认为这次MinorGC是安全的,执行GC。若大于,则要判断年老代是否开启HandlePromotionFailure(是否允许空间分配担保),若没开启,则直接执行FullGC。若开启了,则判断以往平均担保大小是否小于最大连续空间大小,若小于,则尝试MinorGC(有风险,失败了再FullGC,多一次MinorGC),若大于,则FullGC(包括MinorGC)。
4)总结一下GC触发条件与设置
MinGC:当Eden区不够时,可设置-XX:SurvivorRatio设置年轻代中Eden的比例,-XX:NewRatio设置堆中年轻代比例,-Xms -Xmx设置堆最小最大值。
FullGC:a.System.gc()
b.永久代,类、静态变量,常量太多,不够放 -XX:MaxPermSize
c.老年代:c1大对象直接进入时不够 c2空间分配担保,MinorGC前,不愿意担保直接FullGC,愿意担保且空间小于之前担保平均值FullGC,若大于但担保失败了(这次太多了)先MinorGC(发现不行)再FullGC。
d,CMS回收器:只针对老年代收集,浮动垃圾过多,当超过设置的比例大小时(因为用户线程也有空间),会FullGC。设置老年代空间使用比例
-XX:CMSInitialingOccupationFraction,到达这么多时FullGC
目前了解的调优方式:
1)频繁GC时,要使用JDK一些工具排查问题具体位置。
jstat:虚拟机各方面的运行数据 jmap:生成内存转储快照 一般通过这两个就能定位堆中的问题,判断是空间设置不合理还是程序问题。
跟着GC出发点走,调整各个区域大小-Xms -Xmx -XNewRatio -XX:SurvivorRatio -XX:permSize,以及年老代相关的 maxTenuringTreshold,handlePromotionFailure(一般设为true,以减少FullGC,给MinorGC机会嘛)
2)性能问题,要根据需求选择合适的垃圾回收器,以停顿时间为目标还是以吞吐量为目标
如何通知java虚拟机进行垃圾回收?以及垃圾回收机制的原理是什么
java的垃圾回收会由虚拟机自动进行。因为各版本虚拟机的实现不一样,具体回收时点会有一定的不同,但大体上在对内存不足时,是一定会尝试进行一次回收的。如果回收后,内存还是不够,则会报出经典的OutofMemory异常。
用户可以调用System.gc()进行强制的内存回收,但和上面一样,回收完后不一定就保证能有足够的内存。
具体原理你可以想象为虚拟机会保存一张森林结构的内存对象表,林中各树的根节点是各个线程,线程中引用的对象,以及这些对象引用的其他对象会按照引用关系依次排列分布到树中。这样当GC进行时,依次扫描所有对象,如果一个对象的父引用指向不到一个处于活动状态的线程,或者所有直接父引用已经标记为可回收,则将这个对象标记为可回收。最后再释放所有标记为可回收的对象内存,达到清理内存垃圾的目的。
Java虚拟机垃圾回收机制是怎样的?
System.gc() 可以建议jvm进行垃圾回收,不过这个可以通过配置禁止掉。
一个后台线程在资源不足或一定时间后回收废弃的对象占用的内存。
java中垃圾回收器让工作线程停顿下来是怎么做的?
1、jvm中,在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集收集器线程之外的线程都被挂起。此时,系统只能允许GC线程进行运行,其他线程则会全部暂停,等待GC线程执行完毕后才能再次运行。这些工作都是由虚拟机在后台自动发起和自动完成的,是在用户不可见的情况下把用户正常工作的线程全部停下来,这对于很多的应用程序,尤其是那些对于实时性要求很高的程序来说是难以接受的。 但不是说GC必须STW(Stop-The-World,全局暂停), 你也可以选择降低运行速度但是可以并发执行的收集算法,这取决于你的业务。
深入理解GC垃圾回收机制
在我们程序运行中会不断创建新的对象,这些对象会存储在内存中,如果没有一套机制来回收这些内存,那么被占用的内存会越来越多,可用内存会越来越少,直至内存被消耗完。于是就有了一套垃圾回收机制来做这件维持系统平衡的任务。
1.确保被引用对象的内存不被错误的回收
2.回收不再被引用的对象的内存空间
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时, 计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
优点:引用计数收集器可以很快地执行,交织在程序的运行之中。
缺点:很难处理循环引用,比如上图中相互引用的两个对象,计数器不为0,则无法释放,但是这样的对象存在是没有意义的,空占内存了。
引用计数法处理不了的相互引用的问题,那么就有了可达性分析来解决了这个问题。
从GC Roots作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之最终不能与GC Roots有引用关系的视为不可达,不可达对象即为垃圾回收对象。
我自己的理解是,皇室家族每过一段时间,会进行皇室成员排查,从皇室第一代开始往下找血缘关系的后代,如果你跟第一代皇室没有关系,那么你就会被剔除皇室家族。
1.虚拟机栈中引用的对象(正在运行的方法使用到的变量、参数等)
2.方法区中类静态属性引用的对象(static关键字声明的字段)
3.方法区中常量引用的对象,(也就是final关键字声明的字段)
4.本地方法栈中引用的对象(native方法)
1.显示地赋予某个对象为null,切断可达性
在main方法中创建objectA、objectB两个局部变量,而且相互引用。相互引用直接调System.gc()是回收不了的。而将两者都置为null,切断相互引用,切断了可达性,与GCRoots无引用,那么这两个对象就会被回收调。
2.将对象的引用指向另一个对象
这里将one的引用也指向了two引用指向的对象,那么one原本指向的对象就失去了GCRoots引用,这里就判断该对象可被回收。
3.局部对象的使用
当方法执行完,局部变量object对象会被判定为可回收对象。
4.只有软、弱、虚引用与之关联
new出来的对象被强引用了,就需要去掉强引用,改为弱引用。被弱引用之后,需要置空来干掉强引用,达到随时可回收的效果。
只被软引用的对象在内存不足的情况,可能会被GC回收掉。
只被弱引用持有的对象,随时都可能被GC回收,该对象就为可回收对象。
是不是被判定为了可回收对象,就一定会被回收了呢。其实Ojbect类中还有一个finalize方法。这个方法是对象在被GC回收之前会被触发的方法。
该方法翻译过来就是:当垃圾回收确定不再有对该对象的引用时,由垃圾回收器在对象上调用。子类重写finalize方法以处置系统资源或执行其他清除。说人话就是对象死前会给你一个回光返照,让你清醒一下,想干什么就干什么,甚至可以把自己救活。我们可以通过重写finalize方法,来让对象复活一下。
示例:
执行的结果:
这里我们重写FinalizeGC类的finalize方法, 使用FinalizeGC.instance = this语句,让对象又有了引用,不再被判定为可回收对象,这里就活了。然后再置空再回收一下,这个对象就死了,没有再被救活了。所以finalize方法只能被执行一次,没有再次被救活的机会。
在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
元空间有注意有两个参数:
MetaspaceSize :初始化元空间大小,控制发生GC阈值
MaxMetaspaceSize : 限制元空间大小上限,防止异常占用过多物理内存
为什么移除永久代?
移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
有了元空间就不再会出现永久代OOM问题了!
1.Generational Collection(分代收集)算法
分代收集算法是GC垃圾回收算法的总纲领。现在主流的Java虚拟机的垃圾收集器都采用分代收集算法。Java 堆区基于分代的概念,分为新生代(Young Generation)和老年代(Tenured Generation),其中新生代再细分为Eden空间、From Survivor空间和To Survivor空间。 (Survivor:幸存者)
分代收集算法会结合不同的收集算法来处理不同的空间,因此在学习分代收集算法之前我们首先要了解Java堆区的空间划分。Java堆区的空间划分在Java虚拟机中,各种对象的生命周期会有着较大的差别。因此,应该对不同生命周期的对象采取不同的收集策略,根据生命周期长短将它们分别放到不同的区域,并在不同的区域采用不同的收集算法,这就是分代的概念。
当执行一次GC Collection时,Eden空间的存活对象会被复制到To Survivor空间,并且之前经过一次GC Collection并在From Survivor空间存活的仍年轻的对象也会复制到To Survivor空间。
对象进入到From和To区之后,对象的GC分代年龄ege的属性,每经过GC回收存活下来,ege就会+1,当ege达到15了,对象就会晋级到老年代。
2.Mark-Sweep(标记-清除)算法
标记清除:标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。标记-清除算法主要是运用在Eden区,该区对象很容易被回收掉,回收率很高。
3.Copying(复制)算法
复制算法的使用在From区和To区,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
缺点:可使用内存缩减为一半大小。
那么复制算法使可使用内存大小会减半,设计上是怎么解决这个问题的呢。就是给From和To区划分尽可能小的区域。经过大数据统计之后,对象在第一次使用过后,绝大多数都会被回收,所以能进入第一次复制算法的对象只占10%。那么设计上,Eden、From、To区的比例是8:1:1,绝大多数对象会分配到Eden区,这样就解决了复制算法缩减可用内存带来的问题。
4.Mark-Compact (标记—整理)算法
在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记—清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记—整理算法,与标记—清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使它们紧凑地排列在一起,然后对边界以外的内存进行回收,回收后,已用和未用的内存都各自一边。
垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现:
Serial 收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,
优点是简单高效;
Serial Old 收集器 (标记-整理算法): 老年代单线程收集器,Serial 收集器
的老年代版本;
ParNew 收集器 (复制算法): 新生代收并行集器,实际上是 Serial 收集器
的多线程版本,在多核 CPU 环境下有着比 Serial 更好的表现;
CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行
收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿
的特点,追求最短 GC 回收停顿时间。
java垃圾回收是什么?
垃圾回收叫做GC:garbage collection,是java虚拟机JVM提供的一种内存回收方式。开发者在使用java开发的时候不需要像使用C/C++一样去手动释放内存,虚拟机会通过自动检测的方式,去释放内存。比如你用java new了一个对象,这个时候你得到了一个强引用,strong reference。当你不在需要这个对象的时候,你只需要把它设置成null。这个时候JVM并不会立刻回收掉这块内存,在之后的某个时间点,当JVM检测内存的时候发现这块内存已经没有任何强引用了,就会把它释放掉。