本文共 15894 字,大约阅读时间需要 52 分钟。
JVM垃圾回收
1、java的内存是如何划分的?2、垃圾回收回收的是哪些区域?3、如何确定哪些对象需要回收?4、对象生存还是死亡?5、什么时候回收?6、如何收回?回答第一个问题:java的内存是如何划分的?
java虚拟机在运行java程序的时候,会将内存划分为几个不同的数据区域,它们分别为:程序计数器、java栈、本地方法栈、方法区(有些也称为永久代)、java堆。共5个部分,组成java运行时数据区,如下图。
程序计数器是当前线程所执行字节码的行号指示器,反映当前线程在java虚拟机中的执行状态,java多线程轮流切换时的状态保证依靠程序计数器,因此,为了线程切换后能恢复到正确的执行位置,需要每个线程都有一个独立的程序计数器,也就是说,程序计数器的内存空间为“线程私有”的内存空间,其生命周期与线程的生命周期是一致的。
回答第二个问题:垃圾回收回收的那些区域?
回答第三个问题:如何确定哪些对象需要回收?
每个对象添加一个引用计数器,每被引用一次,计数器加1,失去引用,计数器减1,当计数器在一段时间内保持为0时,该对象就认为是可以被回收得了。(在JDK1.2之前,使用的是该算法)
缺点:
缺点:
回答第四个问题:对象生存还是死亡?
经过3中引用计数算法或者根搜索算法已被标记为需要垃圾回收的对象,真的就只能被回收吗,答案是否定的,要真正宣告一个对象死亡,至少要经历两次标记过程。
1、第一次标记:在可达性分析后发现到GC Roots没有任何引用链相连时,被第一次标记;并且进行一次筛选:此对象是否必要执行finalize()方法;
(A)、没有必要执行,没有必要执行的情况:
2、第二次标记:GC将对F-Queue队列中的对象进行第二次小规模标记;
finalize()方法是对象逃脱死亡的最后一次机会:finalize()方法主要有两种用途:
本地对等体:普通对象调用本地方法(JNI)委托的本地对象;
本地对等体不会被GC回收;
如果本地对等体不拥有关键资源,finalize()方法里可以回收它(如C/C++中malloc(),需要调用free());
如果有关键资源,必须显式的终止方法;
回答第五个问题:何时回收
当新生代(Eden区)空间不足时,发起一次 Minor GC
回答第6个问题:如何回收垃圾对象
如何高效地进行垃圾回收。由于Java虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器。
这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。
标记-清除算法分为两个阶段:标记阶段和清除阶段。
优点:标记-清除算法实现起来比较容易
缺点:标记-清除算法有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。
它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。
优点:这种算法实现简单,运行高效且不容易产生内存碎片
缺点:为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。
标记阶段:该算法标记阶段和Mark-Sweep一样,
整理阶段:在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存
它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
比如新生代的对象在每次垃圾时都会有大量的对象死去,只有很少一部分存活,那就可以选择标记-复制算法。另外,在新生代中每次死亡对象约占98%,那么在标记-复制算法中就不需要按照1:1的比例来划分内存区域,而是将新生代细分为了一块较大的Eden和两块较小的Survivor区域,HotSpot中默认这两块区域的大小比例为8:2。每次新生代可用区域为Eden加上其中一块Survivor区域,共90%的内存空间,这样就只有10%的内存空间处在被闲置状态。在进行垃圾回收时,存活的对象被转移到原本处在“空闲的”Eden区域。如果某次垃圾回收后,存活对象所占空间远大于这10%的内存空间时,也就是Survivor空间不够用时,需要额外的空间来担保,通常是将这些对象转移到老年代。
对于老年代来说,大部分对象都处在存活状态。同时,如果一个大对象要在该区域进行分配,而内存空间又不足,那么在没有外部内存空间担保的情况下,就必须选用标记-清除或者标记-整理算法来进行垃圾回收了。
堆区之外还有一个代就是永久代(Permanet Generation),也称方法区,它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
其主要就是将整个内存分为N个多小的独立空间,每个小空间都可以独立使用,这样细粒度的控制一次回收都少个小空间和那些个小空间,而不是对整个空间进行GC,从而提升性能,并减少GC的停顿时间。
2、暂停时间(pause times)越短算法越好
特点
应用场景
特点
应用场景
ParNew垃圾收集器是Serial收集器的多线程版本。
特点:除了多线程外,其余的行为、特点(如Serial收集器可用控制参数、收集算法、Stop The World、内存分配规则、回收策略等;)和Serial收集器一样;
+应用场景设置参数
Parallel Scavenge垃圾收集器因为与吞吐量关系密切,也称为吞吐量收集器(Throughput Collector)。
特点
应用场景
设置参数:Parallel Scavenge收集器提供两个参数用于精确控制吞吐量
特点
应用场景
并发标记清理(Concurrent Mark Sweep,CMS)收集器也称为并发低停顿收集器(Concurrent Low Pause Collector)或低延迟(low-latency)垃圾收集器;
是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;第一次实现了让垃圾收集线程与用户线程(基本上)同时工作;
特点
应用场景
CMS收集器运作过程:可以分为4个步骤
CMS收集器3个明显的缺点
(B)、无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败
(C)、产生大量内存碎片:由于CMS基于"标记-清除"算法,清除后不进行压缩操作;产生大量不连续的内存碎片会导致分配大内存对象时,无法找到足够的连续内存,从而需要提前触发另一次Full GC动作。解决方法:
G1(Garbage-First)是JDK7-u4才推出商用的收集器;
特点
也可以并发让垃圾收集与用户程序同时进行;
应用场景:面向服务端应用,针对具有大内存、多处理器的机器;最主要的应用是为需要低GC延迟,并具有大堆的应用程序提供解决方案;如:在堆大小约6GB或更大时,可预测的暂停时间可以低于0.5秒;用来替换掉JDK1.5中的CMS收集器;在下面的情况时,使用G1可能比CMS好:
设置参数
G1收集器运作过程:不计算维护Remembered Set的操作,可以分为4个步骤(与CMS较为相似)
(A)、初始标记(Initial Marking):
(B)、并发标记(Concurrent Marking):
(C)、最终标记(Final Marking):
(D)、筛选回收(Live Data Counting and Evacuation):
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。
阶段1:根扫描
静态和本地对象被扫描
阶段2:更新RS
处理dirty card队列更新RS
阶段3:处理RS
检测从年轻代指向年老代的对象
阶段4:对象拷贝
拷贝存活的对象到survivor/old区域
阶段5:处理引用队列
软引用,弱引用,虚引用处理
GC时我们需要考虑一个问题,如果仅仅GC 新生代对象,我们如何找到所有的根对象呢? 老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念。它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用。
在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代。但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了。于是G1中使用point-in来解决。point-in的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描。由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可。
需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址。默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。
-XX:+PrintGCDetails日志示例
2018-03-14T17:25:20.920+0800: 37343.682: [GC pause (young), 0.13128020 secs] [Parallel Time: 85.1 ms] [GC Worker Start (ms): 37343683.5 37343683.6 37343683.6 37343683.7 37343683.7 37343683.7 37343683.7 37343683.8 37343683.8 37343683.8 Avg: 37343683.7, Min: 37343683.5, Max: 37343683.8, Diff: 0.3] [Ext Root Scanning (ms): 31.4 31.1 26.3 28.5 32.1 34.6 30.8 31.5 30.1 32.1 Avg: 30.9, Min: 26.3, Max: 34.6, Diff: 8.3] [Update RS (ms): 10.7 10.7 16.0 11.9 8.9 8.9 10.7 10.3 12.2 11.9 Avg: 11.2, Min: 8.9, Max: 16.0, Diff: 7.1] [Processed Buffers : 2 19 14 16 5 6 2 24 14 3 Sum: 105, Avg: 10, Min: 2, Max: 24, Diff: 22] [Scan RS (ms): 4.1 4.3 4.3 4.3 4.3 3.9 4.4 4.4 4.1 2.2 Avg: 4.0, Min: 2.2, Max: 4.4, Diff: 2.1] [Object Copy (ms): 27.6 27.6 27.0 28.9 28.1 26.1 27.4 27.1 27.2 27.4 Avg: 27.4, Min: 26.1, Max: 28.9, Diff: 2.7] [Termination (ms): 0.0 0.0 0.1 0.1 0.0 0.0 0.0 0.0 0.1 0.0 Avg: 0.0, Min: 0.0, Max: 0.1, Diff: 0.0] [Termination Attempts : 8 3 5 6 6 5 11 1 4 7 Sum: 56, Avg: 5, Min: 1, Max: 11, Diff: 10] [GC Worker End (ms): 37343757.8 37343757.9 37343758.1 37343758.1 37343757.6 37343758.1 37343757.7 37343757.7 37343757.9 37343757.5 Avg: 37343757.8, Min: 37343757.5, Max: 37343758.1, Diff: 0.6] [GC Worker (ms): 74.3 74.4 74.4 74.5 73.9 74.4 74.0 73.9 74.1 73.7 Avg: 74.1, Min: 73.7, Max: 74.5, Diff: 0.8] [GC Worker Other (ms): 11.4 11.5 11.5 11.6 11.8 11.5 11.8 11.9 11.5 11.5 Avg: 11.6, Min: 11.4, Max: 11.9, Diff: 0.5] [Clear CT: 3.7 ms] [Other: 42.5 ms] [Choose CSet: 1.3 ms] [Ref Proc: 2.5 ms] [Ref Enq: 0.0 ms] [Free CSet: 37.2 ms] [Eden: 4573M(4573M)->0B(4578M) Survivors: 22M->16M Heap: 7190M(8192M)->2617M(8192M)] [Times: user=0.76 sys=0.00, real=0.13 secs]
转载地址:http://dzufm.baihongyu.com/