深入理解Java虚拟机第三章知识点记录

1.课本62页:判断对象是不是已死:

        引用计数法:每当有一个地方引用它时,计数器就加1,当引用失败时,计数器值就减1.任何时刻计数器为0的对象就不能再被引用。缺点:难以解决对象之间相互循环引用的问题。

        可达性分析算法:从GC Roots对象作为起始点,向下搜索,当一个对象到GCRoot不可达时,则此对象是不可用的。

2.课本64页:可作为GC Roots的对象有

栈中局部变量表中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中引用的对象。

 举例:

class Person{
	
}
class Test{
	static Person a = new Person();	//方法区中类的静态属性引用的对象
	static final String s = "abc";	//方法区中常量引用的对象
	Person b = new Person();        //栈中本地方法表引用的对象
}

3.课本65页:强引用、软引用、弱引用、虚引用:

强引用:普遍存在的,类似Object obj = new Object()类型的引用

软引用:有用但并非必须的对象,内存不够,将发生内存溢出异常前,将软引用关联着的对象进行第二次回收

弱引用:被弱引用关联的对象只能活到下一次垃圾收集器发生之前,垃圾收集器工作的时候,无论当前内存是否足够,都会回收掉弱引用关联的对象。

虚引用:无法通过虚引用取得一个对象实例,设置虚引用的目的在这个对象被回收时收到一个系统通知。

4.课本66页:finalize()方法:

可达性分析发现对象不可达,此时将继续判断对象是否覆盖finalize()方法,如果覆盖了finalize()并且是第一次被调用,如果在finalize()方法中重新与任何一个对象建立了连接,对象就仍然可以存活。

5.课本68页:方法区(永生代)回收

1)回收对象:废弃常量和无用的类

2)新生代的回收效率很高(一般是75%-95%),而永生代的垃圾收集效率较低。

6.课本69页 垃圾收集算法:

标记——清除算法、复制算法、标记——整理算法

标记——清除算法   缺点:效率和清除效率都很低、标记清除后产生大量不连续的内存碎片    注意CMS收集器使用的标记——清除算法

复制算法:将可用内存按容量大小划分为代大小相等的两块,每次只使用其中的一块,当一块使用完后,将存活的对象复制到另外一块上。使用使用中不是按照1:1的比例,而是将内存分为一块较大的Eden区和两块较小的Survivor区,每次只使用Eden和其中的一块Survivor,回收时将Eden区和Survivor存活着的对象一次性地复制到另外一块Survivor上。说明:因为一个Survivor中只占比例的百分之十,如果存活的对象超过了百分之十,另一块Survior的空间不够用的时候,就要依赖老年代进行空间担保简单说就是另一块Survivor中没有足够的空间,此时对象将直接通过担保机制进入老年代

标记——整理算法

标记整理算法不是直接对课回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉边界外的内存。

7.课本72页:分代收集算法:

将堆分为老年代和新生代,新生代中使用复制算法回收,老年代中使用标记——清理算法回收(主要是CMS收集器),或标记——整理算法回收。

8.课本75页:各种垃圾收集器

《深入理解Java虚拟机第三章知识点记录》

两个收集器之间存在连线,说明它们可以搭配使用,没有最好的收集器,我们选择对具体的应用选择合适的收集器。

首先理解几个参数:

吞吐量:CPU用于运行用户代码的时间与CPU总消耗时间的比值。高吞吐量适合在后台运算而不需要太多的交互任务。

停顿时间:停顿时间越短越合适需要与用户交互的程序。

两者之间相互关系:如果GC很频繁,那么最小停顿时间就会变小,但是吞吐量会下降,如果GC次数很少,那么最小停顿时间就会变长,那么吞吐量就会增加。

  • Serial/Serial Old收集器

1)Serical采用复制算法,适用于新生代,Serial Old使用标记-整理算法,适用与老年代。

2)适合运行在Client模式下

3)多CPU下不能以发挥其性能

4)收集垃圾时,用户线程会停止

  • ParNew收集器

1)ParNew收集器就是Serial收集器的多线程版本

2)运行在Server模式下的首选新生代收集器,因为只有它能与CMS收集器配合工作

3)单CPU的环境下,效果没有Serial好,在多CPU的情况下,才能发挥其优势

4)没有ParNew Old的说法

CMS收集器是以获取最短回收停顿时间为目标的收集器,它非常符合那些集中在互联网或者B/S系统的服务端上,这类应用非常注重服务器的响应速度。

  • CMS收集器

1)CMS收集器是以获取最短停顿时间为目标的收集器。

2)CMS的过程分为初始标记、并发标记、重新标记、并发清除

初始标记:仅仅标记GC Roots能直接关联的对象,速度很快。

并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活的对象

重新标记:重新标记是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录。

CMS的特征: 

其中初始标记和重新标记仍然要stop the world,并发标记和并发清除都是与用户线程并发执行的,因此CMS收集器尽可能缩短垃圾收集时用户线程的停顿时间,总体来说,CMS收集器的内存回收过程与用户线程一起并发执行的

《深入理解Java虚拟机第三章知识点记录》

CMS缺点:

CMS收集器对CPU资源非常敏感,在并发阶段,内存回收占用了一部分线程,会导致应用程序变慢。

CMS收集器无法处理浮动垃圾,由于CMS并发清理阶段用户线程还在运行,伴随着程序运行自然还会有新的垃圾不断产生,这部分垃圾是在标记过后,所以只能留到下次GC时再清理。

CMS收集器无法向其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时程序运作使用。JDK1.6时CMS收集器启动的阈值是92%。

CMS基于标记—清除算法,收集结束后会有大量空间碎片产生。空间碎片过多,会给大对象的分配带来麻烦,导致无法找到足够的连续空间来分配当前对象,不得不提前触发一次Full GC。

CMS收集器只能和Serial和ParNew配合工作,无法与Parallel Scavenge配合工作 

  • Parallel Scavenge收集器

1)适用于新生代,使用复制算法,与之相对应是Parallel Old收集器,适用于老年代,使用标记整理算法,注重吞吐量的场合,都可以优先考虑Parallel Scavenge加Parallel Old收集器。

2)Parallel Scavenge收集器目标是达到一个可控制的吞吐量

3)停顿时间越短就越适合需要用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

4)Parallel Scavenge收集器无法与CMS收集器配合工作

5)Parallel Scavenge收集器提供了两个参数用于控制吞吐量,分别是最大垃圾收集停顿时间的-XX:MaxGCPauseMills参数以及直接设置吞吐量大小的-XX:GCTimeRatio参数。

    MaxGCPauseMills参数设置一个值,收集器尽量保证内存回收花费的时间不超过设定值。但是GC停顿时间缩短是以牺牲吞吐量为代价的,比如:停顿时间设置较少,原先收集500M,现在只收集300M,但这也导致垃圾收集更频繁,原先10秒收集一次,每次停顿100毫秒,现在变为5秒收集一次,每次停顿70毫秒,停顿时间变少了,但是吞吐量也降低了。

    GCTimeRatio:垃圾收集时间占总时间的比率

4)还可以设置一个参数,UseAdaptiveSizePolicy,可以GC自适应调节。就不需要手动指定新生代的大小,Eden和Survivor的比例、晋升老年代对象大小等参数,收集器自动调整这些参数以提供最合适的停顿时间和最大的吞吐量。

  • 3.ParNew收集器

1)ParNew收集器其实就是Serial收集器的多线程版本

2)ParNew是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为除了Serial收集器外,目前只有它能与CMS收集器配合工作。

  • G1收集器

1)G1收集器简介

G1收集器将整个Java堆化为多个大小相等的独立区域,它可以根据回收回所获得空间大小以及回收所需时间的经验值,优先回收价值最大的区域。通过这种使用区域划分内存空间以及优先级的区域回收方式,保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。

2)G1收集过程

G1分为初始标记、并发标记、最终标记、筛选回收

初始标记:仅仅标记GC Roots能直接关联到的对象

并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活的对象

最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,这一阶段需要停顿线程,但是可并发执行。

筛选回收:对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间来指定回收计划。这一部分可以与用户程序并发执行,但是因为只回收一部分区域,所以停顿用户线程将大幅提高收集效率。

对收集器自己的几点理解

1)新生代一般使用复制算法,老年代一般使用标记-整理算法。例外:CMS使用的标记—清理算法,所以会产生内存碎片。

2)ParNew和CMS是单个出现,Serical和Serical Old,Parallel Scavenge和Parallel Old是成对出现。

3)CMS收集器使用标记—清除算法,老年代的收集器一般都是使用标记—整理算法,因为CMS收集器可以看成是并发的收集器(垃圾清扫工作和用户线程是同时的),而标记—整理算法要移动对象,此时用户线程在工作,显然不能移动对象,所以只能使用标记—清理算法来清理垃圾对象。

4)CMS强调的是最小停顿时间,而Parallel Scavenge收集器强调的是最大吞吐量。一般情况下,停顿时间越小,越适合实时交互的应用,而吞吐量越大越适合后运算而不需要太多交互的任务。

5)最小停顿时间和高吞吐量一对矛盾,清理停顿时间小了,清理就频繁了,那么吞吐量就降低了,应根据具体的场景有所偏重,是选择低停顿时间还是高吞吐量。

6)CMS是对整个老年代回收,所以不适合停止用户线程,因为那样即使并发回收老年代,也会花费较长时间,而G1回收只对一个区域回收,因为区域较小,所以可以停顿用户线程,并发回收区域的时间是很少的。

9.课本89页:理解GC日志

1)这里除以Full GC和GC指的是是不是stop the world,并不是指Full GC 和Minor GC。

2)DefNew :新生代

Tenured:老年代

Perm:永久代

3)3324K->152K(3712K)——前面的指的是GC前内存已经使用的,后面是GC后内存已经使用的,(该内存区域的总容量)。

10.课本91页 对象优先在Eden分配

1)在大多数情况下,对象在新生代的Eden区分配,当Eden没有足够空间进行分配时,虚拟机将发起一次Minor GC。

2)虚拟机提供PrintGCDetails参数打印日志

—Xmx20M:设置最大可用内存为20M

—Xms20M:设置内存为20M

—Xmn10M:设置新生代为10M

11.课本93页:大对象直接进入老年代

大对象:需要大量连续内存空间的Java对象。最典型的大对象是字符串和数组。经常出现大对象会导致eden还有内存就提前触发垃圾回收器以获取足够的连续的内存来安置它们。自己的理解:大对象放到eden区可能没有足够的连续内存来存放,所以直接将大对象放到老年代,目的避免在Eden区以及两个Survivor区之间发生大量的内存复制。

12.课本95页:长期生活的对象将进入老年代:

默认年龄到达15岁,就会今生到老年代中。晋升的阈值可以通过MaxTenuringThreshold设置

对象在Eden出身并经过第一次Minor GC后仍然存活,并能被Survivor容纳,将被移动到Surivivor空间中,此时对象年龄设置为1。对象在Survivor区中每熬过一次Minor GC,年龄就增加一岁,当年龄增加到默认15岁,就会被晋升到老年代。

13.课本97页:动态对象年龄判定:

如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。

14.课本98页 空间分配担保:

空间担保就是预防一个Survivor装不下eden和另一个Survivor里面的对象时,所以在发生GC前,虚拟机会先检查老年代最大可用的连续空间是否但与新生代所有对象总空间。如果不满足,且不允许担保,则发生一次Full GC,如果允许担保,发现老年代空用的连续空间小于历次晋升到老年代对象的平均空间,也发生一次Full GC,如果大于进行一次Minor GC。但是如果担保失败,只好重新一次Full GC。

说明:

1)老年代有一部分对象是大对象直接进来的,有一个部分是Survivor空间不够担保进来的,还有一部分是多次Minor GC后进来的。

2)复制算法需要老年代做担保

3)大对象直接进入老年代,如果大对象进入了Eden,那么很可能触发一次Minor GC ,并且后面的Survivor也不一定能装的下大对象,此时还是进入老年代,还有即使能装下大对象,那么小对象可能就装不下,小对象就会进入老年代,这不合理。

参考

1.https://crowhawk.github.io/2017/08/15/jvm_3/点击打开链接

2.周志明,深入理解Java虚拟机

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/chenkaibsw/article/details/80815846
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞