JVM学习之路(九)——垃圾回收器

九、垃圾回收器

JVM的垃圾回收只收集那些由new关键字创建的对象。所以,如果不是用new创建的对象,可以使用finalize()函数来执行清理。

(一)GC什么时候回收?

  •  对象没有引用
  •  作用域发生未捕获异常
  •  程序在作用域正常执行完毕
  •  程序执行了System.exit()
  •  程序发生意外终止(被杀进程等)

(二)如何减少GC开销?

1、不要显式调用System.gc()。此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数,大大的影响系统性能。

2、尽量减少临时对象的使用。临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

3、对象不用时,最好显式置为Null。一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。

4、尽量使用StringBuffer,而不用String来累加字符串。由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象。如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为每次“+”操作时,都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它是在原有的基础上进行扩增,不会产生中间对象。

5、能用基本类型如Int、long,就不用Integer、Long对象。基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本类型变量。

6、尽量少用静态对象变量。静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

7、分散对象创建或删除的时间。集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的,它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

(三)串行回收器

原理:使用单线程进行垃圾回收。对新生代的回收使用复制算法,对老年代的回收使用标记压缩算法

特点:串行收集器是最古老最稳定的收集器,尽管它是串行回收,回收时间较长,但其稳定性是优于其他回收器的,综合来说是一个不错的选择。要使用串行收集器,可以在启动配置时加上以下参数:-XX:+UseSerialGC

流程:执行垃圾回收时,应用程序线程暂停,GC线程开始,执行垃圾回收,垃圾回收完成后,应用程序线程继续执行。注意:在GC线程运行过程中使用单线程进行串行回收

(四)并行回收器

并行回收器就是使用多线程并行回收。不过需要注意的是,根据新生代和老年代,是否都使用并行回收,分为以下不同的回收器:

1、 ParNew回收器

原理:这个回收器只针对新生代进行并行回收,老年代依然使用串行回收。回收算法依然和串行回收一样,新生代使用复制算法,老年代使用标记压缩算法

特点:在多核条件下,它的性能显然优于串行回收器,如果要使用这种回收器,可以在启动参数中配置:-XX:+UseParNewGC。如果要进一步指定并发的线程数,可以配置一下参数:-XX:ParallelGCThreads。

流程:在进行垃圾回收时应用程序线程依然被暂停,GC线程并行开始执行垃圾回收,垃圾回收完成后,应用程序线程继续执行。

2、Parallel回收器

原理:依然是并行回收器,但这种回收器有两种配置。

  • 一种类似于ParNEW:新生代使用并行回收、老年代使用串行回收。它与ParNew的不同在于它在设计目标上更重视吞吐量,可以认为在相同的条件下它比ParNew更优。要使用这种回收器可以在启动程序中配置:-XX:+UseParallelGC。
  • 另外一种配置则不同于ParNew,对于新生代和老年代均使用并行回收,要使用这种回收器可以在启动程序中配置:-XX:+UseParallelOldGC(多了一个Old)。

流程:Parallel回收器的流程和ParNew的流程是一样的。在进行回收时,应用程序暂停,GC使用多线程并行开始执行回收,回收完成后应用程序线程继续运行。

(五)CMS回收器

原理:Concurrent Mark Sweep,并发标记清除。注意这里注意两个词:并发、标记清除。

特点:

①并发表示它可以与应用程序并发执行、交替执行

②标记清除表示这种回收器使用的是标记清除算法,不是使用的标记压缩算法,这和前面介绍的串行回收器和并发回收器有所不同。

③需要注意的是CMS回收器是一种针对老年代的回收器,不对新生代产生作用

流程:CMS回收器运行机制非常复杂,可以简单的将他的运行流程分为以下几步:

初始标记(不可以和应用程序线程同时进行):标记从GC Roots可以直接可达的对象,标记速度快,时间短,所以对应用程序不会产生太大的影响。

并发标记(可以和应用程序线程同时进行):主要标记过程,标记全部对象;

重新标记(不可以和应用程序线程同时进行:由于并发标记时,用户线程依然运行,因此在正式清理前,再做依次重新标记,进行修正。标记速度快,时间短,所以对应用程序不会产生太大的影响。

并发清除(可以和应用程序线程同时进行):基于标记结果,直接清理对象。和应用程序同时进行的,避免了应用程序的停顿。

优点:减少了应用程序停顿的时间(减少了“stop the world”),因为它不需要应用程序暂停,等待垃圾回收,而是与垃圾回收并发执行。要执行这种垃圾回收器可以在启动参数中配置:-XX:+UseConcMarkSweepGC

缺点:从他的运行机制可以看出,因为它不像其他回收器一样集中一段时间对垃圾进行回收,而是在回收时应用程序还在运行,因此它的回收并不彻底。这也导致了CMS回收的频率相较其他回收器要高,而频繁的回收将影响应用程序的吞吐量。

(六)G1回收器

原理:G1回收器是jdk1.7以后推出的回收器,试图取代CMS回收器。不同于其他的回收器、G1将堆空间划分成了互相独立的区块。每块区域既有可能属于老年代、也有可能是新生代,并且每类区域空间可以是不连续的(对比CMS的老年代和新生代都必须是连续的)。这种将老年代区划分成多块的理念源于:当并发后台线程寻找可回收的对象时、有些区块包含可回收的对象要比其他区块多很多。虽然在清理这些区块时G1仍然需要暂停应用线程,但可以用相对较少的时间优先回收包含垃圾最多区块。这也是为什么G1命名为Garbage First的原因:第一时间处理垃圾最多的区块。要使用G1回收器需要在启动是配置以下参数:-XX:+UseG1GC

G1相对CMS回收器来说优点在于:

1、因为划分了很多区块,回收时减小了内存碎片的产生。

2、G1适用于新生代和老年代,而CMS只适用于老年代

(七)常见垃圾回收器的参数设置

-XX:+UseSerialGC:在新生代使用串行回收器(复制算法),在老年代使用串行回收器(标记压缩算法)

-XX:+UseParNewGC:在新生代使用并行回收器(复制算法),在老年代使用串行回收器(标记压缩算法)

-XX:+UseParallelGC :在新生代使用并行回收器(复制算法),在老年代使用串行回收器(标记压缩算法),但更加关注吞吐量

-XX:+UseParallelOldGC:在新生代使用并行回收器(复制算法),在老年代使用并行回收器(标记压缩算法)

-XX:ParallelGCThreads:设置用于垃圾回收的线程数

-XX:+UseConcMarkSweepGC:在新生代使用并行回收器(复制算法),老年代使用CMS回收器(标记清除算法)

-XX:ParallelCMSThreads:设置CMS的线程数

-XX:+UseG1GC:启用G1垃圾回收器

猜你喜欢

转载自blog.csdn.net/u012556994/article/details/81269592