深入理解Java虚拟机——垃圾收集器与内存分配策略(读书笔记)

判断对象是否存活1、引用计数法给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1,当引用失效时,计数器值减1, 任何时刻计数器为0的对象就是不可能再被使用的。缺点:不能解决对象之间循环引用的...

判断对象是否存活

1、引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1,当引用失效时,计数器值减1, 任何时刻计数器为0的对象就是不可能再被使用的。

缺点:不能解决对象之间循环引用的问题

2、根搜索算法(GC Roots Tracing)

通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

可作为GC Roots对象:

(1)虚拟机栈中引用的对象

(2)本地方法栈中引用的对象

(3)方法区中类静态属性引用的对象

(4)方法区中常量引用的对象

 

引用的分类:强软弱虚(强度依次减弱)

1、强引用:最常见的,如Object obj = new Object();,只要强引用还在,垃圾回收器永远不会回收掉被引用的对象。

2、软引用(SoftReference):描述一些还有用,但并非必需的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围内并进行第二次回收,如果还是没有足够的内存,才会抛出异常。

3、弱引用(WeakReference):被弱引用关联的对象只能生存到下一次垃圾收集发生。

4、虚引用(PhantomReference):一个对象是否有虚引用关联,完全不会对其生存时间产生影响,也无法通过虚引用来取得一个对象实例。作用:对象被回收时收到一个系统通知。

 

finalize()方法

当对象要被删除时,并且对象实现了finalize()方法,还没有被调用过,那么,这个对象会被放在F-Queue队列,由虚拟机自动建立的、低优先级的Finalizer线程去执行,但是,虚拟机只负责触发,没有承诺会等待它结束,所以最终能否存活,不确定性很大。

 

回收方法区

永久代垃圾回收:废弃的常量、无用的类

无用的类(满足所有条件):

1、该类所有的实例都已经被回收

2、加载该类的ClassLoader已经被回收

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

ps:在大量使用反射、动态代理、CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久带不会溢出。

 

垃圾收集算法

1、标记——清除算法

缺点:

标记和清除的效率低

标记清除后会产生大量不连续的内存碎片,当需要给较大对象分配内存时,提前触发垃圾回收动作。

2、复制算法

将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另一块上面,然后再把已使用过的内存清理掉。

缺点:牺牲掉一半内存

3、标记——整理算法

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

4、分代收集算法

根据对象的存活周期的不同将内存划分为几块,一般是分为新生代和老年代,然后根据各个年代的特点采用合适的收集算法。新生代一般采用标记复制,老年代一般采用标记清理、标记整理算法。

 

垃圾收集器

 

1、Serial收集器:

单线程

STW(工作时必需暂停其他工作线程)

虚拟机在Client模式下默认的新生代收集器

优点:简单高效

2、PerNew收集器:

Serial的多线程版本

STW

虚拟机在Server模式下默认新生代收集器

默认开启的收集器的个数和CPU相同

3、Parallel Scavenge收集器

目标是达到一个可控制的吞吐量

吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

高吞吐量即最高效率的利用CPU时间,尽快的完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务

设置最大垃圾收集停顿时间:-XX:MaxGCPauseMillis

设置吞吐量大小:-XX:GCTimeRatio

-XX:+UseAdaptiveSizePolicy:开关参数,打开后就不需要手动设置新生代的大小、各分区比例、晋升老年代年龄了,JVM会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最适合的停顿时间或最大吞吐量。

4、Serial Old收集器

Serial的老年代版本

标记整理算法

CMS收集器的替补

5、Paralle Old收集器

Parallel Scavenge老年代版本

标记整理算法

6、CMS收集器

以获得最短停顿时间为目标

标记清除算法

步骤:

初始标记:

STW

标记GC Roots能直接关联到的对象

并发标记:

GC Roots Tracing

重新标记:

STW

修改并发标记期间,因用户进程继续运作而导致的产生变动的那一部分对象

并发清除

缺点:

(1)对CPU资源非常敏感,面向并发设计的程序都这样

(2)无法处理浮动垃圾,并发清理的过程中会有新的垃圾产生,只能下次处理掉,称为浮动垃圾,由于还需要留一部分空间给用户线程,所以不能等到快满了才收集,默认是68%,当预留空间不够,会出现Concurrent Mode Failure,导致Full GC,这时,会启用Serial Old收集器。

(3)产生大量空间碎片,参数-XX:+UseCMSCompactAtFullCollection,在Full GC之后进行碎片整理。

7、G1收集器

G1将整个Java堆划分为多个大小固定的独立区域(Region),并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域(Garbage First)。保证了G1在有限的时间内可以获得最高的收集效率。

标记整理算法

非常精准的控制停顿,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不超过N毫秒

 

内存分配

1、大多数情况,对象在新生代Eden区中分配,当空间不足,JVM发起Minor GC

2、-XX:+PrintGCDetails:JVM在发生垃圾收集行为时打印日志

3、GC

Minor GC:发生在新生代的垃圾收集动作,因为对象大多具有朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度比较快。

Major GC/Full GC:发生在老年代,出现Major GC,经常会伴随至少一次的Minor GC,速度比Minor GC慢10倍以上。

4、大对象

大对象需要大量连续的内存空间,例如:很长的字符串/数组,

-XX:PretenureSizeThreshold:大于这个参数值的对象直接在老年代中分配(只对Serial、ParNew有效)

5、长期存活的对象进入老年代

JVM给每个对象定义一个对象年龄的计数器,经历过一次Minor GC,加一,当年龄增加到一定程度(默认为15),就会被晋升到老年代。

-XX:MaxTenuringThreshold:设置晋升老年代的阈值

6、动态对象年龄判定

当Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于等于该年龄的对象可以直接进入老年代。

7、空间分配担保

发生Minor GC时,JVM会检测之前每次晋升到老年代的平均大小是否大于目前老年代剩余空间,如果大于,则直接改为Full GC,如果小于,则查看HandlePromotionFailure是否允许担保失败,如果允许,则只会进行Minor GC,否则,Full GC。

本文标题为:深入理解Java虚拟机——垃圾收集器与内存分配策略(读书笔记)