《深入java虚拟机》读后(2)

三、OutOfMemoryError

 

 

 

第三章 垃圾收集器与内存分配策略

一、 判断对象是否被实用

    1、 引用计数法

        给对象添加一个引用计数器,每当有一个地方引用他,计数器就加一,引用失效就减一,计数器为零的对象就是不再被实用的。(有弊端,不用)

    2、可达性分析算法

        基本思路:通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链,当一个对象到"GC Roots”没有任何引用链就说明对象不可用。

        可以作为“GC Roots”的对象:虚拟机栈中引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;Native方法引用的对象。

二、 引用

    1、 强引用:永远不会被回收被引用的对象。

    2、 软引用:描述有用但非必需的对象。在系统要内存溢出之前,才将这些对象列入回收范围进行二次回收。

    3、 弱引用:描述非必需对象。只能生存到下一次垃圾回收之前。

    4、 虚引用:唯一目的是能在这个对象被回收时受到一个系统通知。

三、 判断对象生死

    1、对象进行可达性分析后发现没有与GC Roots有引用链。

    2、第一次标记并进行筛选(筛选的条件是否当对象覆盖或已经调用过finalize方法 )

    3、执行finalize()方法。将对象放置在一个叫F-Queue的队列中,并由虚拟机自动建立的、低优先级的Finaizer线程去执行他。

    4、稍后GC堆F-Queue中的对象进行第二次标记,重新与引用链关联则移除回收范围,否则被回收

    注:尽量避免使用finalize()方法。

四、 垃圾收集算法

    1、 标记-清除算法

        缺点:效率低。清除后产生大量不连续内存碎片,无法找到足够连续内存给大对象,导致不得不再次GC。

    2、 复制算法(新生代)

        将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和一块Survivor,每当回收时将两块空间上存活着的对象复制到另一块Survivor上,同时清理掉这两块空间。当这块Survivor空间不够用时,需要依赖其他内存(老年代)进行分配担保。

        注: 默认Eden和Survivor的大小比例为8:1。

    3、 标记-整理算法(老年代)

        标记后让所有存活的对象向一端移动,然后直接清理掉端边界的内存。

    4、 分代收集算法

        把java堆分为新生代和老年代。新生代用复制算法,老年代用标记-清除或标记-整理算法进行回收。

五、 HotSpot中GC算法实现

    1、 枚举根节点:可达性分析必须在一个能确保一致性的快照进行(分析过程中对象引用关系不再变化)。

    2、 安全点:在特定的点让程序停止下来,虚拟机使用一组称为OopMap的数据结构记录栈和寄存器中哪些位置是引用。以便于虚拟机能快速而准确的完成GC Roots枚举。

            让所有线程中断的方式:抢险式中断(一半不用)、主动式中断。

    3、 安全区域:引用关系不会变化的一片代码片段。为防止没有分配CPU给jvm,以致太长时间不进入安全点。

六、 垃圾收集器

    1、 Serial收集器(client模式下新生代默认收集器)----单线程

        缺点:工作时必须暂停其他所有线程。

        优点:简单高效(不需要线程交互)。

    2、 ParNew收集器(Server模式下首选新生代收集器)----多线程

        缺点:单cpu时不如Serial收集器、工作时必须暂停用户线程

        优点:CPU越多越好用、可与CMS收集器配合工作

    3、 Parallel Scavenge 收集器(控制吞吐量的新生代收集器)----多线程

        吞吐量是CPU用于运行用户代码的时间与CPU总消耗时间的比值。

    4、 Serial Old 收集器(Client模式下老年代收集器)----单线程

        如果用于Server模式下,一用于jdk1.5以及之前Parallel Scavenge收集器搭配,二用于CMS收集器的备选。

    5、 Parallel Old 收集器(控制吞吐量的老年代收集器)----多线程

        与Parallel Scavenge收集器搭配。

    6、 CMS收集器(最短回收停顿的老年代收集器)

        1)初始标记:标记GC Roots能直接关联的对象。速度快,暂停所有用户线程。

        2)并发标记:进行根搜索算法。时间长,并发,可与用户进程一同进行。

        3)重新标记:修正并发标记期间因程序继续运作而导致标记变动的那部分对象的标记记录。速度快,暂停所有用户线程。

        4)并发清除:时间长,并发。

        缺点:(1) 对CPU资源非常敏感。

                  (2) 无法收集浮动垃圾(在并发清理阶段产生的垃圾),因此需要预留一部分空间。可以调节预留多少内存来影响

                    内存回收的次数和性能。当出现“Concurrent Mode Failure”,会临时启用Serial Old收集器重新收集老年代。

                  (3) 大量空间碎片产生,因此提供了一个在即将FullGC时可开启合并整合(需短暂停顿)的开关。

7、 G1收集器

        1)初始标记:标记GC Roots能直接关联到的对象,修改TAMS值(指示程序在正确可用的Resion中创建对象),需要停顿线程,耗时短。

        2)并发标记:进行可达性分析找出存活的对象,耗时较长,并发。

        3)最终标记:修正在并发标记期间因程序继续运作而导致标记产生变动的那部分标记记录。在变化期间,变化被记录在Remerbered Set Logs中,最终标记阶段将数据整合到Remembered Set中。

        4)筛选回收:在后台维护一个优先列表,对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时长制定回收计划。

        注:每个Region中都有对应的Remembered Set,当虚拟机发现程序在对Reference 类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region中(分代中就是检查老年代中是否引用了新生代的对象)。如果是,便通过CardTable把引用信息记录到被引用对象的Region的Remembered Set中。当进行内存回收时,GC Roots中加入Remembered Set。这样就可以避免全堆扫描了。

七、 GC日志(很简单)

八、 内存分配与回收策略

    1、 几种GC

        1)新生代GC(Minor GC)

        2)老年代GC(Major GC/Full GC):一般会伴随至少一次Minor GC

    2、Eden

        对象优先分配在Eden区(如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配)。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC。

    3、老年代

        1)大对象直接进入老年代。

        2)长期存活的对象进入老年代。经过一次Minor GC后,对象被移动到Survivor空间,熬过一定次数Minor GC(包括进入Survivor前那次)后对象进入老年代(默认15次)。

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

        3)空间分配担保:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。

九、 回收方法区(效率低)

    1、废弃常量

    2、无用的类(同时满足一下三个条件)

        1) 该类所有的实例都被回收了。

        2) 加载该类的ClassLoader已经被回收了。

        3) 该类对应的java.lang.Class对象没有在任何地方引用,无法在任何地方反射访问该类的方法。



posted @ 2019-06-13 22:22  lcnb  阅读(178)  评论(0编辑  收藏  举报