JVM总结二

JVM垃圾回收机制

GC原理

GC (Garbage Collection:即垃圾回收)的基本原理:将堆内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器
由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、老年代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停

  • 对新生代的对象的收集称为minor GC
  • 对老年代的对象的收集称为Full GC
  • 程序中主动调用System.gc()强制执行的GC为Full GC
    WeakHashMap:Java 的4种引用:强引用、软引用、弱引用、虚引用
  • 强引用:创建一个对象,并把这个对象赋值给一个引用变量
 public void fun1() {
        Object object = new Object();
        Object[] objArr = new Object[1000];
 }

强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象,但是当运行完成之后,引用都不存在了,他们指向的对象都会被jvm回收。比如Vector类的clear方法中就是通过将引用赋值为null来实现清理工作的
**** 软引用***
如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它,用来描述一些有用的,但非必须的对象,在系统将要发生内存溢出前,会把对象列进回收范围之中进行二次回收,如果这次回收内存还不足够会抛出异常。
好处:软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。

oftReference的特点:是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。
也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。
另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。

MyObject aRef = new  MyObject();
SoftReference aSoftRef=new SoftReference(aRef);
  • 弱引用
    描述非必需对象,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。被弱引用关联的对象只能生存到下一次垃圾收集为止。
WeakReference<People>reference=new WeakReference<People>();
  • 虚引用
     虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。唯一目的:为了能在这个对象收集器回收时得到一个系统通知。

对象被标为垃圾的方法

JVM的内存结构中堆区和方法区内存的分配和回收是动态的,是垃圾收集器所需关注的区域

引用计数法

引用计数是垃圾收集器中的早期策略。堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。 当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。 当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。
缺点无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。
循环引用示例:在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可达性分析

可达性算法是目前主流的虚拟机都采用的算法, 程序把所有的引用关系看作一张图
GC思路:从一个节点GC Roots开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

在Java语言中,可作为GC Roots的对象包括下面几种:

  • 虚拟机栈中引用的对象(栈帧中的本地变量表);
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(Native方法)引用的对象。
    在这里插入图片描述
    可以得出对象实例1、2、4、6都具有对象可达性,也就是存活对象,不能被GC回收的对象。而随想实例3、5虽然直接相连,但并没有任何一个GC Roots与之相连,即GC Roots不可达对象,就会被GC回收的对象

垃圾回收算法

标记-清除算法

标记/清除算法的基本思想就跟它的名字一样,分为“标记”“清除” 两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象
标记阶段:

标记的过程其实就是前面介绍的可达性分析算法的过程,遍历所有的 GC Roots 对象,对从 GCRoots 对象可达的对象都打上一个标识,一般是在对象的 header 中,将其记录为可达对象;

清除阶段:

清除的过程是对堆内存进行遍历,如果发现某个对象没有被标记为可达对象(通过读取对象header 信息),则将其回收
在这里插入图片描述
图解过程
上图是标记/清除算法的示意图,在标记阶段,从对象 GC Root 1 可以访问到 B 对象,从 B 对象又可以访问到 E 对象,因此从 GC Root 1 到 B、E 都是可达的,同理,对象 F、G、J、K 都是可达对象;到了清除阶段,所有不可达对象都会被回收。
在垃圾收集器进行 GC 时,必须停止所有 Java 执行线程(也称"Stop The World"),原因是在标记阶段进行可达性分析时,不可以出现分析过程中对象引用关系还在不断变化的情况,否则的话可达性分析结果的准确性就无法得到保证。在等待标记清除结束后,应用线程才会恢复运行。
(标记时,线程会停止运行)
标记/清除算法缺点:

  • 效率问题
    标记和清除两个阶段的效率都不高,因为这两个阶段都需要遍历内存中的对象,很多时候内存中的对象实例数量是非常庞大的,这无疑很耗费时间,而且 GC 时需要停止应用程序,这会导致非常差的用户体验。
  • 空间问题
    标记清除之后会产生大量不连续的内存碎片(从上图可以看出),内存空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
标记-复制算法

**过程:**复制算法是将可用内存按容量划分为大小相等的两块,每次使用其中的一块。当这一块的内存用完了,就将还存活的对象复制到另一块内存上,然后把这一块内存所有的对象一次性清理掉
在这里插入图片描述复制算法每次都是对整个半区进行内存回收, 这样就减少了标记对象遍历的时间,在清除使用区域对象时,不用进行遍历,直接清空整个区域内存,而且在 将存活对象复制到保留区域时也是按地址顺序存储的,这样就解决了内存碎片的问题,在分配对象内存时不用考虑内存碎片等复杂问题,只需要按顺序分配内存即可。

复制算法缺点:
复制算法简单高效,优化了标记清除算法的效率低、内存碎片多问题,存在缺点:

  • 将内存缩小为原来的一半,浪费了一半的内存空间,代价太高;
  • 如果对象的存活率很高,极端一点的情况假设对象存活率为 100%,那么我们需要将所有存活的对象复制一遍,耗费的时间代价也是不可忽视的。
标记- 整理算法

标记-整理算法算法与标记/清除算法很像,事实上,标记/整理算法的标记过程任然与标记/清除算法一样,但后续步骤不是直接对可回收对象进行回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边线以外的内存。
在这里插入图片描述
可以看到,回收后可回收对象被清理掉了,存活的对象按规则排列存放在内存中。这样一来,当我们给新对象分配内存时,jvm 只需要持有内存的起始地址即可。标记/整理算法弥补了标记/清除算法存在内存碎片的问题消除了复制算法内存减半的高额代价,可谓一举两得。
标记/整理缺点:

  • 效率不高:不仅要标记存活对象,还要整理所有存活对象的引用地址,在效率上不如复制算法。
分代回收算法

分代收集算法的思想是按对象的存活周期不同将内存划分为几块一般是把 Java 堆分为新生代老年代(还有一个永久代,是 HotSpot 特有的实现,其他的虚拟机实现没有这一概念,永久代的收集效果很差,一般很少对永久代进行垃圾回收),这样就可以根据各个年代的特点采用最合适的收集算法。
特点:

新生代:朝生夕灭,存活时间很短。采用复制算法来收集

*老年代:*经过多次 Minor GC 而存活下来,存活周期长。采用标记/清除算法或者标记/整理算法收集老年代

新生代中每次垃圾回收都发现有大量的对象死去,只有少量存活,因此采用复制算法回收新生代,只需要付出少量对象的复制成本就可以完成收集;

老年代中对象的存活率高,不适合采用复制算法,而且如果老年代采用复制算法,它是没有额外的空间进行分配担保的,因此必须使用标记/清理算法或者标记/整理算法来进行回收。
在这里插入图片描述
新生代中的对象几乎都是“朝生夕死”的(达到 98%),现在的商业虚拟机都采用复制算法来回收新生代。由于新生代的对象存活率低,所以并不需要按照 1:1 的比例来划分内存空间,而是将内存分为一块较大的 Eden 空间和两块较小的 From Survivor 空间、To Survivor 空间,三者的比例为 8:1:1。 每次使用 Eden 和 From Survivor 区域,To Survivor 作为保留空间。GC 开始时,对象只会存在于Eden 区和 From Survivor 区,To Survivor 区是空的。GC 进行时,Eden 区中所有存活的对象都会被复制到 To Survivor 区,而在 From Survivor 区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象每熬过一轮垃圾回收,年龄值就加 1)的对象会被移到老年代中,没有达到阀值的对象会被复制到 To Survivor 区。接着清空 Eden 区和 From Survivor 区,新生代中存活的对象都在ToSurvivor 区。接着, From Survivor 区和 To Survivor区会交换它们的角色,也就是新的 To Survivor 区就是上次 GC 清空的 From Survivor 区,新的 From Survivor 区就是上次 GC 的 To Survivor 区,总之,不管怎样都会保证 To Survivor 区在一轮 GC 后是空的。GC 时当 To Survivor 区没有足够的空间存放上一次新生代收集下来的存活对象时,需要依赖老年代进行分配担保,将这些对象存放在老年代中。复制算法就不合适了
分代回收:
我们从一个object1来说明其在分代垃圾回收算法中的回收轨迹。
1、object1新建,出生于新生代的Eden区域。
在这里插入图片描述

2、minor GC,object1 还存活,移动到From suvivor空间,此时还在新生代。

3、minor GC,object1 仍然存活,此时会通过复制算法,将object1移动到ToSuv区域,此时object1的年龄age+1。
在这里插入图片描述

4、minor GC,object1 仍然存活,此时survivor中和object1同龄的对象并没有达到survivor的一半,所以此时通过复制算法,将fromSuv和Tosuv 区域进行互换,存活的对象被移动到了Tosuv。
在这里插入图片描述

5、minor GC,object1 仍然存活,此时survivor中和object1同龄的及以上的对象已经达到survivor的一半以上(toSuv的区域已经满了),object1被移动到了老年代区域。
在这里插入图片描述

6、object1存活一段时间后,发现此时object1不可达GcRoots,而且此时老年代空间比率已经超过了阈值,触发了majorGC(也可以认为是fullGC,但具体需要垃圾收集器来联系),此时object1被回收了。fullGC会触发 stop the world。
在这里插入图片描述

在以上的新生代中,我们有提到对象的age,对象存活于survivor状态下,**不会立即晋升为老年代对象,**以避免给老年代造成过大的影响,它们必须要满足以下 条件 才可以晋升:

  • 1、minor gc 之后,存活于survivor 区域的对象的age会+1,当超过(默认)15的时候,转移到老年代。
  • 2、动态对象,如果survivor空间中相同年龄所有的对象大小的综合和大于survivor空间的一半,年级大于或等于该年纪的对象就可以直接进入老年代。
内存的分配和回收

分配策略:

  • 对象优先分配在Eden区
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代
  • 动态对象年龄判定(年龄超过阈值或survivor空间相同年龄所有对象大小总和大于survivor区一半,年龄大于或等于该年龄的对象直接进入老年代)

堆分了:Eden、两个Survivor、Tenured共4个区,Eden与Survivor大小比是8:1,Eden和Survivor称为新生代,Tenured称为老年代(JDK8已经没有持久代了)
当新对象产生时,存放在Eden,当Eden放不下时触发Minor GC,将Eden中存活的对象复制到一Survivor中。继续存放对象到Eden,当Eden放不下时触发Minor GC,将Eden和非空闲Survivor中存活的对象复制到空闲Survivor中,往复操作。每经过一次Minor GC,对象的年龄加1,当对象年龄达到阀值(默认15)进入Tenured。如果在Minor GC期间发现存活对象无法放入空闲的Survivor区,则会通过空间分配担保机制使对象提前进入Tenured。如果在Survivor空间中的相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于和等于该年的对象就可以直接进入老年代,无需等到指定的阀值。
空间分配担保机制
在执行Minor GC前, JVM会首先检查Tenured是否有足够的空间存放新生代尚存活对象,由于新生代使用复制收集算法,为了提升内存利用率,只使用了其中一个Survivor作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况时,就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代,但前提是老年代需要有足够的空间容纳这些存活对象。但存活对象的大小在实际完成GC前是无法明确知道的,因此Minor GC前, **JVM会先首先检查老年代连续空间是否大于新生代对象总大小或历次晋升的平均大小,如果条件成立, 则进行Minor GC,否则进行Full GC(让老年代腾出更多空间)。**然而取历次晋升的对象的平均大小也是有一定风险的,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然可能导致担保失败(Handle Promotion Failure,老年代也无法存放这些对象了),此时就只好在失败后重新发起一次Full GC(让老年代腾出更多空间)。

垃圾回收器

收集器发展历程:
Serial收集器->Parallel收集器->CMS收集器(Concurrent Mark Sweep)->G1收集器(Garbage First)

在进行垃圾回收时,会暂停所有的工作线程,直到垃圾回收完成,垃圾回收器的不断迭代为了优化减少停顿时间
在这里插入图片描述
使用垃圾回收器,通过设置垃圾回收器参数:

-XX:+UseSerialGC,虚拟机运行在Client模式下的默认值,Serial+Serial Old-XX:+UseParNewGCParNew+Serial Old,在JDK1.8被废弃,在JDK1.7还可以使用。
-XX:+UseConcMarkSweepGCParNew+CMS+Serial Old-XX:+UseParallelGC,虚拟机运行在Server模式下的默认值,Parallel Scavenge+Serial Old(PS Mark Sweep)-XX:+UseParallelOldGCParallel Scavenge+Parallel Old-XX:+UseG1GC,G1+G1。
Serial收集器

Serial收集器是单一线程收集器,运行在Client端,在JDK 1.3.1之前唯一的垃圾回收器
优势
简单高效,对于单个CPU的环境,Serial收集器由于没有线程交互的开销,
专心做垃圾回收可以获得最高的单线程收集效率
在这里插入图片描述
缺点:在收集过程中,必须暂停其他所有工作的线程,知道收集结束。

ParNew收集器

ParNew收集器是Serial收集器的多线程版本,运行在Server端
特点:
多线程进行垃圾回收、其余行为(控制参数、收集算法、stop the world、对象分配规则、回收策略)与Serial收集器完全一致,随着CPU的数量增加、对于GC时系统资源的有效利用还是很有好处的,默认开启的收集线程数与CPU的数量相同

控制参数:
-XX:ParallelGCThreads 参数限制垃圾垃圾收集线程

优势:
除了Serial收集器,只有他能与CMS收集器配合使用
在这里插入图片描述

Parallel Scavenge收集器并行多线程收集器

并行多线程收集器
停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的。
特点:

达到一个可控制的吞吐量(其他线程的关注点尽可能的缩短用户线程的停顿时间),吞吐量就是CPU用于运行用户代码的时间与CPU的总消耗时间的比值,吞吐量=运行用户代码时间/( 运行用户代码时间+垃圾收集时间)

区别:

停顿时间越短,交互响应时间越快,用户体验越好
高吞吐量则是高效利用CPU时间,尽快完成程序运算任务,适合后台运算

控制参数

-XX:MaxGCPauseMillis 控制垃圾回收最大停顿时间
-XX:GCTimeRatio 设置吞吐量大小(大于0小于100)

Serial Old收集器

特点: Serial Old是Serial收集器的老年代版本,是一个单线程收集器、使用标记-整理算法,运行在Client端

另外还可以在Server模式下:JDK 1.5之前的版本中与Parallel Scavenge 收集器搭配使用,可以作为CMS的后备方案,在CMS发生Concurrent Mode Failure是使用
在这里插入图片描述

Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,是一个多线程收集器、使用标记-整理算法,JDK 1.6中开始提供

Parallel Old收集器的出现,使“吞吐量优先”收集器终于有了名副其实的组合。在吞吐量和CPU敏感的场合,都可以使用Parallel Scavenge/Parallel Old组合
在这里插入图片描述

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于标记-清除算法实现
特点:
基于标记-清除算法,并发收集、低停顿,运作过程复杂,分4步

  • 初始标记:
    仅仅标记GC Roots能直接关联到的对象,速度快,但是需要“Stop The World”
  • 并发标记:
    就是进行追踪引用链的过程,可以和用户线程并发执行。
  • 重新标记:
    修正并发标记阶段因用户线程继续运行而导致标记发生变化的
    那部分对象的标记记录,比初始标记时间长但远比并发标记时间短,需要“Stop The World”
  • 并发清除:
    清除标记为可以回收对象,可以和用户线程并发执行
    由于整个过程耗时最长的并发标记和并发清除都可以和用户线程一起工作,所以总体上来看,CMS收集器的内存回收过程和用户线程是并发执行的。

缺点:

  • 对CPU资源非常敏感
    并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。CMS的默认收集线程数量是=(CPU数量+3)/4;当CPU数量多于4个,收集线程占用的CPU资源多于25%,对用户程序影响可能较大;不足4个时,影响更大,可能无法接受。无法处理浮动垃圾(在并发清除时,用户线程新产生的垃圾叫浮动垃圾),可能出现"Concurrent Mode Failure"失败。
    并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满再进行收集;如果CMS预留内存空间无法满足程序需要,就会出现一次"Concurrent Mode Failure"失败;这时JVM启用后备预案:临时启用Serail Old收集器,而导致另一次Full GC的产生;产生大量内存碎片:
    CMS基于"标记-清除"算法,清除后不进行压缩操作产生大量不连续的内存碎片,这样会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作
    在这里插入图片描述
G1收集器

收集器发展最前沿成果之一,可以运行与服务端和客户端,基于region的内存布局形成,每一个region都可以扮演新生代的Eden空间,Survivor空间或者老年代空间。G1的储存结构本质上是一个哈希表,key是region的起始地址,value是一个集合,,里面储存的元素是卡表的索引号。

特征

  • 并行与并发:
    能充分利用多CPU、多核环境的硬件优势,缩短停顿时间;能和用户线程并发执行。
  • 分代收集:
    G1可以不需要其他GC收集器的配合就能独立管理整个堆,采用不同的方式处理新生对象和已经存活一段时间的对象。
  • 空间整合:
    整体上看采用标记整理算法,局部看采用复制算法(两个Region之间),不会有内存碎片,不会因为大对象找不到足够的连续空间而提前触发GC,这点优于CMS收集器。
  • 可预测的停顿:
    除了追求低停顿还能建立可以预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超N毫秒,这点优于CMS收集器。

为什么能做到可预测的停顿?
是因为可以有计划的避免在整个Java堆中进行全区域的垃圾收集。G1收集器将内存分大小相等的独立区域(Region),新生代和老年代概念保留,但是已经不再物理隔离。 G1跟踪各个Region获得其收集价值大小 [注:《深入理解JAVA虚拟机》这样解释:回收所获得得空间大小以及回收所需要的时间的经验值,在后台维护一个优先列表;每次根据允许的收集时间,优先回收价值最大的Region(名称Garbage-First的由来);这就保证了在有限的时间内可以获取尽可能高的收集效率。

回收过程步骤(与CMS较为相似):

  • 初始标记:
    仅仅标记GC Roots能直接关联到的对象,并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时能在正确可用的Region中创建新对象,需要“Stop The World”
  • 并发标记:
    从GC Roots开始进行可达性分析,找出存活对象,耗时长,可与用户线程并发执行
  • 重新标记:
    修正并发标记阶段因用户线程继续运行而导致标记发生变化的那部分对象的标记记录。并发标记时虚拟机将对象变化记录在线程Remember Set Logs里面,最终标记阶段将Remember Set Logs整合到Remember Set中,比初始标记时间长但远比并发标记时间短,需要“Stop The World”
  • 筛选回收:
    首先对各个Region的回收价值和成本进行排序,然后根据用户期望的GC停顿时间来定制回收计划,最后按计划回收一些价值高的Region中垃圾对象。回收时采用复制算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;可以并发进行,降低停顿时间,并增加吞吐量。
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值