JVM学习笔记(三)

垃圾回收机制

(垃圾标记阶段)

引用计数算法(java不使用)

对每个对象保存一个整型的引用计数器属性,用于记录对象被应用的情况
对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,即表示对象A不可能在被使用,可进行回收
优点:实现简单,垃圾对象便于辨识,判定效率搞,回收没有延迟性
缺点:无法处理循环引用的情况、增加了存储空间的开销

可达性分析算法(搜索算法、追踪性垃圾收集)

该算法能有效地解决在引用计数算法中循环引用的问题,防止内存泄露的发生
 *基本思路:
1.可达性分析算法是以根对象集合(GC Roots就是一组必须活跃的引用)为起始点,按照从上至下的方式搜索被根对象集合所链接的目标对象是否可达
2.使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链
3.如果目标对象没有任何应用链相连,则是不可达的,就意味着该对象已经死亡,可标记为垃圾对象
4.在可达性分析算法中,只有能够被根对象集合直接或间接链接的对象才是存活对象

GC Roots包括以下几类元素:

虚拟机栈中的引用对象
本地方法栈内JNI(通常说的本地方法)引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
所有被同步锁synchronized持有的对象
java虚拟机内部的引用(基本数据类型对应的Class对象,一些常驻的异常对象,系统类加载器)
反映java虚拟机内部情况的JMXBean,JVMTI中注册的回调、本地代码缓存等

对象的finalization机制

java语言提供了对象终止机制来允许开发人员提供对象被销毁之前的自定义处理逻辑
当垃圾回收器发现没有引用指向一个对象,即:垃圾回收此对象前总会先调用这个对象的finalize()方法
finalize()方法允许在子类中被重写,用于在对象额比回收时进行资源释放

(垃圾清除阶段)

当成功分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间

标记-清除算法

执行过程:
当堆中的有效内存空间被耗尽的时候,就会停止整个程序,然后进行两项工作,第一项则是标记,第二项就是清除。
标记:Collector从引用根结点开始遍历,标记所有被引用的对象。一般是在对象的header中记录为可达对象
清除:Collector对堆内存从头到尾进行线性的遍历,如果发现某个对象在Header中没有标记为可达对象,则将其回收
缺点:
效率不高,在进行GC的时候,需要停止整个应用程序,影响用户体验
这种方式清理出来的空闲内存不是连续的,产生内存碎片
何为清除?
不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够就存放

复制算法

核心思想:
将活着的内存空间分为两块,每一次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收
优点:
没有标记和清除过程,实现简单,运行高效
复制过去以后保证空间的连续性,不会出现“碎片”问题
缺点:
此算法需要两倍的内存空间
对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小

标记-压缩(整理)算法

执行过程:
第一阶段和标记-清除算法一样,从根节点开始标记所有被引用对象
第二阶段将所有的存活对象压缩到内存的一端,按顺序排放
之后清理边界外所有的空间
优点:
消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可
消除了复制算法当中,内存减半的高额代价
缺点:
效率尚低于复制算法
移动过程中,需要全程暂停用户应用程序

分代收集算法

几乎所有的GC都是采用分代收集算法执行垃圾回收的
在HotSpot中,基于分代的概念,GC所使用的内存回收算法必须结合年轻代和老年代各自的特点
*年轻代:
相对于老年代较小,对象生命周期短,存活率低,回收频繁
这种情况下复制算法的回收整理,速度是最快的
*老年代:
区域较大,对象生命周期长,存活率高,回收不及年轻代频繁
这种情况存在大量存活率高的对象,复制算法明显变得不合适,一般是由标记-清除或者是标记-清除与标记-整理混合实现

Sysytem.gc()

默认情况下,通过system.gc()或者Runtime.getRuntime().gc()的调用,会显示触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存
附带一个免责声明,无法保证对垃圾收集器的调用
内存泄露
严格来说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄露
实际情况很多时候一些不太好的实践会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄露”
尽管内存泄露并不会立即引起程序奔溃,但是一旦发生内存泄露,程序中的可用内存就会被逐步蚕食,直到耗尽所有内存

引用

当内存空间还足够时,则保留在内存中,如果内存空间在进行垃圾收集后还是很紧张,则可可以抛弃这些对象
强引用:
最传统的"引用"的定义,是指在程序代码之中普遍存在的引用赋值,即类似"Object obj = new Object()"这种引用关系,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
软引用:
在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。当内存足够时,不会回收软引用的可达对象
弱引用:
被弱引用关联的对象只能生存到下一次垃圾回收之前。当垃圾回收器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。
虚引用(对象回收机制):
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

垃圾回收器

评估gc的性能指标

吞吐量:运行用户代码的时间占总运行时间的比例(程序的运行时间+内存回收的时间)
暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
内存占用:java堆区所占的内存大小
收集频率:相对于应用程序的执行,收集操作发生的频率
7款经典收集器与垃圾分代之间的关系
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS
整堆收集器:G1

如何查看默认的垃圾收集器

-XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)
使用命令行指令:jinfo -flag相关垃圾回收器参数 进程ID

Serial回收器:串行回收

serial收集器采用复制算法、串行回收和STW机制方式执行垃圾回收
除了年轻代之外,serial收集器还提供用于执行老年代垃圾收集的serial Old(MSC)收集器
serial Old的内存回收算法使用时标记-压缩算法
—serial Old在Server模式下主要有两个用途:1.与新生代的Parallel Scavenge配合使用 2.作为老年代CMS收集器的后备垃圾收集方案

ParNew回收器:并行回收

ParNew收集时Serial收集器的多线程版本,处理新生代
ParNew收集器除了采用并行回收的方式执行内存回收外,两款垃圾回收几乎没有任何区别。ParNew收集器在年轻代中同样也是采用复制算法,STW机制

Parallel回收器:吞吐量优先

HotSpot的年轻代中除了拥有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同样也采用了复制算法、并行回收和STW
和ParNew收集器不同,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量,也被称为吞吐量优先的垃圾收集器,并且存在自适应的调节系统
高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。因此,常见在服务器环境中使用。例如,那些执行批量处理、订单处理、工资支付、科学计算的应用程序
Parallel Old收集器采用了标记-压缩算法,基于并行回收和STW机制
-XX:+UseParallelGC 手动指定年轻代使用Parallel并行收集器执行内存回收任务
-XX:+UseParallelOldGC 手动指定老年代都是使用并行回收收集器
分别适用于新生代和老年代,默认jdk8开启。默认开启一个,另一个也会被开启
-XX:ParallelGCThreads 设置年轻代并行收集器的线程数。一般的,最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能
-XX:MaxGCPauseMills 设置垃圾收集器最大停顿时间
-XX:GCTimeRatio 垃圾收集时间占总时间的力b用于衡量吞吐量的大小
-XX:+UseAdaptiveSizePolicy 设置Parallel Scavenge收集器具有自适应调节策略

CMS收集器-低延迟

CMS(Concurrent-Mark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作
CMS收集器的关注点是尽可能缩短垃圾收集器时用户线程的停顿时间。停顿时间越短就越合适与用户交互的程序,良好的相应速度能提升用户体验
CMS的垃圾收集器采用标记-清除算法,并且也会STW
整个过程比之前的收集器要复杂:分为4个主要阶段
初始标记阶段:在这个阶段中,程序中所有的工作线程都将会因为STW机制出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Root能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快
并发标记阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,整个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
重新标记阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,整个阶段的停顿时间通常会比初始标记阶段稍长一些,但也远比并发标记阶段的时间短
并发清除阶段:此阶段清理删除标记阶段判断的已经死亡的对象,释放内存空间。由于不需要移动存活对象,所以整个阶段也是可以与用户线程同时并发的
弊端:
1)会产生内存碎片
2)CMS收集器对CPU资源非常敏感
3)CMS收集器无法处理浮动垃圾:在并发标记阶段如果产生新的对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收

G1回收器:区域化分代式

G1是一个并行回收器,它把堆内存分割为很多不相关的区域(物理上不连续的)。使用不同的区域表示Eden、S1、S2、老年代等
G1 GC有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
G1的特点(优势)
并行与并发:
并行性:在G1在回收期间,可以多个GC线程同时工作,有效利用多核计算能力。此时用户线程STW
并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生阻塞应用程序的情况
分代收集:
从分代来说,G1依然属于分代型垃圾回收器,它会区分年轻代和老年代,同时兼顾年轻代和老年代
空间整合:
CMS:“标记-清除”算法、内存碎片、若干次GC后进行一次碎片整理
G1将内存划分为一个个的region。内存的回收是以Region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩算法,两种都可以避免内存碎片
可预测的停顿时间模型:
G1除了追求低停顿外,还能建立可预测的停顿模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了G1收集器在有限时间内可以获取尽可能高的收集效率
-XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务
-XX:G1HeapRegionSize 设置每个Region的大小。默认1/2000
-XX:MaxGCPauseMills 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)
-XX:ParallelGCThread 设置STW工作线程数
G1回收器垃圾回收过程:
主要包括三个环节:
*年轻代GC
*老年代并发标记过程
*混护回收
*(如果需要,单线程、独占式、高强度的Full GC还是继续存在的。它针对GC的评估失败提供一种失败保护机制,即强力回收)
应用程序分配内存,当年轻代的Eden用尽时开始年轻代回收过程;G1的年轻代收集阶段是一个并行的独占式收集器。在年轻代回收期,G1 GC暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到S区间或者老年区,也有可能都会涉及
当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程
标记完成马上开始混合回收过程。对于一个混合回收期,G1 GC从老年区见移动存活对象到空闲区间,这些空闲区间也就成为了老年的一部分。和年轻代不同,老年代的G1回收器和其他GC不同,G1的老年代回收期不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以。同时这个老年代Region是年轻代一起被回收的。
Remember Set
一个对象被不同区域引用的问题
一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确?回收新生代也不得不同时扫描老年代?这样会降低Minor GC的效率
解决方案:
无论G1还是其他分代收集器JVM都是使用Remember Set来避免全局扫描
每个Region都有一个对应的Remember Set,每次Reference类型数据写操作时,都会产生一个Write Barrier暂时终端操作,然后检查将要写入的引用指向对象是否和该Reference类型在不同的Region,如果不同通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remember Set
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值