互联网技术学习27———垃圾回收算法+垃圾收集器

垃圾回收Garbage Collection,简称GC。GC中的垃圾指的是内存中不会再被使用的对象,而回收就是相当于把垃圾“倒掉”。垃圾回收有很多算法:引用计数法、标记压缩法、复制算法、分代、分区。

在java堆中,新生代/老年代 =1/2 或1/3比较合适

垃圾回收算法 

引用计算法:这是一个比较古老而经典的垃圾收集算法,其核心就是在对象被引用的时候计数器加1,而当引用失效时减1,但是这种方式存在严重的问题,无法处理循环引用的情况,还有就是每次进行加减操作是比较浪费系统性能。

标记清除法:分为标记和清除两个阶段,这种方式的弊端就是空间碎片问题。垃圾回收后的空间是不连续的。不连续的内存空间工作效率要低于连续的空间,且造成了内存的浪费。

标记压缩法:标记压缩法在标记清除法的基础上做了优化,把存活的对象压缩到内存的一端,而后进行垃圾清理

分代算法:根据对象的特点把内存分为N块,然后根据每个内存块的特点使用不同的算法。如Java中的新生代和老年代

分区算法:主要就是将整个内存分成N多个小空间,每个小空间都可以独立使用,这样细粒度的控制一次回收多少个小空间和哪些小空间,而不是对整内存进行GC,从而提高性能,并减少GC停顿时间。

对于新生代和老年代来说,新生代回收的频率很高,但是每次回收耗时短,而老年代gc频率较低,但是每次耗时长,所以应该尽量减少老年代的GC。

老年代中对象已经比较稳定,经过多次GC仍未被回收,在GC过程中,清理的内存比较小,存活的对象占比非常高,因此老年代使用标记压缩法。

垃圾回收时的停顿现象

为了让垃圾回收机高效的进行,大部分情况下,会要求系统进入一个停顿状态(stop the world)。停顿的目的是终止所有应用线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某个瞬间的一致性,也有益于更好的标记垃圾对象。

对象如何进入老年代
新创建的对象放置在新生代的eden区,如果没经历过gc,改新创建的对象会一直放在eden区。经过一次gc后,进入新生代的s0或s1区,在新生代中对象每经历过一次gc年龄就会增加1,在对象的年龄达到一定大小的时候,就会从新生代进入老年代,对象的年龄应该是该对象经历过的gc次数决定的。虚拟机提供了一个参数来控制信新生代对象的最大年龄,当超过这个年龄范围就会晋升为老年代。另外,大对象即新生代eden区无法装入时,也会直接进入老年代,JVM中存在相关参数可以设置对象大小超过指定对的大小之后,直接晋升为老年代。

-XX:MaxTenuringThresold 指定新生代对象经过多少次GC就进入老年代。默认情况下是15

-XX:PretenureSizeThreshold 指定不经过新生代直接进入老年代的对象大小

Test05.java

package com.jvmTest;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by BaiTianShi on 2018/9/24.
 */
public class Test05 {
    public static void main(String[] args) {
        Map<Integer,byte[]> map = new HashMap<>();
        for (int i = 0; i < 5 ; i++) {
            byte[] b = new byte[1024*1024];
            map.put(i,b);
        }
    }

}

参数设置:
-Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000

控制台打印:

上述代码中,设置了-XX:PretenureSizeThreshold=1000,即指定不经过新生代而直接晋升到老年代的对象大小为1000字节,而代码中new byte[1024*1024] 的大小为1M>1000k,属于大独享直接放在老年代,因此老年代userd 5136k。

Test06.java

package com.jvmTest;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by BaiTianShi on 2018/9/24.
 */
public class Test06 {
    public static void main(String[] args) {

        Map<Integer,byte[]> map =new HashMap<>();
        for (int i = 0; i < 5*1024 ; i++) {
            byte[] b =new byte[1000];
            map.put(i,b);
        }
    }
}

JVM设置参数: -Xmx30M -Xms30M -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000

控制台打印:

老年代仅仅使用了56k,这是因为虚拟机对于体积不大的对象,会优先把数据分配到TLAB区域中,因此就失去了再老年代分配的机会。JVM中每个线程,默认是使用TLAB的。

TLAB

TLAB即Thread Local Allocation Buffer 即线程本地分配缓存,每一个线程都会产生一个TLAB,是线程独享的工作区域,JVM使用TLAB避免多线程冲突问题,提高对象的分配效率。TLAB空间一般不会太大,当大对象无法再TLAB分配至,会直接分配到堆上。

-XX:+UseTLAB 使用TLAB

-XX:-UseTLAB 禁用TLAB

-XX:+TLABSize 设置TLAB大小

-XX:TLABRefillWasteFraction 设置TLAB空间的单个对象的大小吗,它是一个比例,默认为64,即对象大于整个空间的1/64时,则在堆中创建此对象。

-XX:+PrintTLAB 打印TLAB信息

-XX:ResizeTLAB 自动调整TLABRefillWasteFraction阈值。

一个对象的创建流程

一个对象创建在什么位置,JVM会有一个比价细节的流程,根据数据的大小,参数的设置,决定如何创建分配,以及其位置

垃圾收集器

在tomcat的catalina.sh中使用JAVA_OPT配置初始堆大小、最大堆大小

在JVM中,垃圾回收器有多种:串行垃圾回收器、并行垃圾回收器、CMS回收器(主流)、G1回收器

串行回收器:使用单线程进行垃圾回收的垃圾回收器,对于并行能力较弱的计算机来说,串行回收器的专注性和独占性往往有更好的性能表现。

+XX:UseSerialGC  设置新生代串行回收器和老年代串行回收器

并行回收器(新生代ParNew回收器):在串行回收器上做了改进,可以使用多个线程同时进行垃圾回收,对于计算能力强的计算机而言,可以有效的缩短垃圾回收所需要的时间,ParNew回收器是一个工作在新生代的垃圾回收器,它只是简单的将串行回收器多线程话,它的回收策略和算法与串行回收器一样。

-XX:+UseParNewGC 使用新生代ParNew垃圾回收器

-XX:ParallelGCThreads 指定ParNew回收器工作时的线程数量,一般最好和计算机cpu核数相当,避免过多线程影响性能。

并行回收器(新生代ParallelGC):新生代ParalleGC回收器,它是使用了复制算法的回收器,也就是多线程独占形式的收集器,它有个非常重要的特点,即非常关注系统的吞吐量。提供了2个非常关键的参数控制系统的吞吐量。

-XX:MaxGCPauseMillis 设置最大垃圾收集停顿时间,可将虚拟机在GC的停顿时间控制在MaxGCPauseMillis范围内。如果希望减少GC停顿时间,可将MaxGCPauseMillis设置的很小,但是会导致GC频繁,从而增加GC总时间,降低了吞吐量,所以需要根据实际情况设置该值。

-XX:GCTimeRatio 设置吞吐量大小,它是一个0-100之间的数,默认情况下是99,那么系统将花费不超过1/(1+n)的时间用于垃圾回收。

-XX:UseAdaptiveSizePolicy 打开自适应模式,在这种模式下,新生代大小、eden和from或to的比例,以及晋升老年代的对象年龄参数都会被自动调整,以达到堆大小、吞吐量、停顿时间的平衡点

并行回收器(老年代ParallelOldGC):老年代ParallelOldGC回收器也是一种多线程回收器,和新生代的ParallelGC回收器一样,也是一种关注吞吐量的回收器,使用了标记压缩算法进行实现。

-XX:+ParallelOldGC 设置使用老年代ParallelOldGC收集器

-XX:ParalleGCThreads 设置垃圾收集时的线程数量

CMS回收器(主流):全称Concurrent Mark Sweep 意思为并发标记清除,使用的是标记清除算法,主要关注系统停顿时。且适用于老年代。

-XX:+UseConcMarkSweepGC 使用cms垃圾回收器

-XX:ConcGCThreads 设置并发线程数

CMS并不是独占的垃圾回收器,在其进行垃圾回收的过程,程序仍然在工作(其实remark标记阶段仍然需要stop the world),又会有新的垃圾不断产生,所以在使用CMS的过程中要保证应用程序的内存足够可用,CMS不会等到应用程序饱和的时候才去回收垃圾,而是在某一个阈值的时候就开始回收,回收阈值可以使用指定的参数进行设置-XX:CMSInitiatingOccupancyFraction来指定,默认为68,即当老年代中的使用空间达到68的时候,会执行CMS回收。如果内存使用增长过快,在CMS执行过程中,已经出现了内存不足的情况,此时CMS回收就会失败,虚拟机将启动老年代串行回收器进行垃圾回收,这会导致应用程序中断,直到垃圾回收完成后才会正常工作,这个过程GC停顿时间可能较长,所以-XX:CMSInitiatingOccupancyFraction的设置要根据实际的情况来决定。

标记清除法有个缺点就是存在碎片的问题,在CMS中存在相关的参数如下:

-XX:+UseCMSCompactAtFullCollection  设置在CMS回收完成之后进行一次碎片整理

-XX:CMSFullGCBeforeCompaction 设置进行多少次CMS回收之后,对内存进行一次压缩

G1回收器:全称Garbage-First,它是在jdk1.7中提出的垃圾回收器,长期目标是为了取代CMS回收器,G1回收器拥有独特的垃圾回收策略,G1属于分代垃圾回收器,区分新生代和老年代,依然有eden和from/to区,它并不要求整个eden区或者新生代、老年代空间都连续,它使用了分区算法。

并行性:G1回收期间可以多线程同时工作

并发性:G1拥有与应用程序交替执行能力,部分工作可以与应用程序同时执行,在整个GC期间不会完全阻塞应用程序

分代GC:G1是一个分代收集器,但是它兼顾新生代和老年代一起工作,之前的垃圾收集器他们或者在新生代工作,或者在老年代工作,这是一个很大的不同。

空间整理:G1在回收过程中,不会像CMS那样在若干次GC之后需要进行碎片整理,G1采用了有效复制对象的方式,减少空间碎片。

可预见性:由于分区的原因,G1可以只选择部分区域进行回收,缩小了回收的范围,提高了性能。

-XX:+UseG1GC  使用G1收集器

-XX:MaxGCPauseMillis  指定最大停顿时间

-XX:ParallelGCThreads  设置并行回收的线程数量

猜你喜欢

转载自blog.csdn.net/qq_28240551/article/details/82832649