JVM--垃圾回收

2021-09-23  本文已影响0人  zhemehao819

三、垃圾回收

1、如何判断对象可以回收

引用计数法

弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放

可达性分析算法

分析方法:
jps查看进程id
抓取当前堆内存:(b表示二进制文件,live只抓取存活对象并进行一次垃圾回收)
jmap -dump:format=b,live,file=1.bin [进程id]
二进制文件可以用Eclipse Memory Analyer进行分析

五种引用

强引用

软引用

软引用的使用:

public class Demo1 {
    public static void main(String[] args) {
        final int _4M = 4*1024*1024;
        //使用软引用对象 list和SoftReference是强引用,而SoftReference和byte数组则是软引用
        List<SoftReference<byte[]>> list = new ArrayList<>();
        SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M]);
    }
}

如果在垃圾回收时发现内存不足,在回收软引用所指向的对象时,软引用本身不会被清理
如果想要清理软引用,需要使用引用队列

public class Demo1 {
    public static void main(String[] args) {
        final int _4M = 4*1024*1024;
        List<SoftReference<byte[]>> list = new ArrayList<>();

        //使用引用队列,用于移除引用为空的软引用对象
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        for(int i=0; i<5;i++) {
          //关联了引用队列,当软引用所关联的byte[]被回收时,软引用自己会加入到引用队列中去
          SoftReference<byte[]> ref= new SoftReference<>(new byte[_4M], queue);
          list.add(ref);
        }
        
        //遍历引用队列,如果有元素(说明软引用对象已回收),则移除
        Reference<? extends byte[]> poll = queue.poll();
        while(poll != null) {
            //引用队列不为空,则从集合中移除该元素
            list.remove(poll);
            //移动到引用队列中的下一个元素
            poll = queue.poll();
        }
    }
}

大概思路为:查看引用队列中有无软引用,如果有,则将该软引用从存放它的集合中移除(这里为一个list集合)

弱引用

虚引用

当虚引用对象所引用的对象被回收以后,虚引用对象就会被放入引用队列中,调用虚引用的方法

终结器引用

如上图,B对象不再引用A4对象。这是终结器对象就会被放入引用队列中,引用队列会根据它,找到它所引用的对象。然后调用被引用对象的finalize方法。调用以后,该对象就可以被垃圾回收了

引用队列

2、垃圾回收算法

标记-清除

定义:

标记清除算法顾名思义,是指在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后垃圾收集器根据标识清除相应的内容,给堆内存腾出相应的空间

标记-整理

标记-整理 会将不被GC Root引用的对象回收,清楚其占用的内存空间。然后整理剩余的对象,可以有效避免因内存碎片而导致的问题,但是因为整理需要消耗一定的时间,所以缺点是效率较低,优点是没有内存碎片

复制

将内存分为相等大小的两个区域,FROM和TO(TO中为空)。先将被GC Root引用的对象从FROM放入TO中,再回收不被GC Root引用的对象。然后交换FROM和TO。这样也可以避免内存碎片的问题,但是会占用双倍的内存空间

3、分代回收

回收流程

注:
1.新生代的垃圾回收称为Minor GC;
2.老年代的垃圾回收称为Full GC;
3.Minor GC:会触发 stop the world, 暂停其他用户线程,等垃圾回收结束后用户线程才恢复运行,时间较短;
4.Full GC:也会STW,但时间较长。

GC 分析

大对象处理策略

当遇到一个较大的对象时,就算新生代的伊甸园为空,也无法容纳该对象时,会将该对象直接晋升为老年代

线程内存溢出

某个线程的内存溢出了而抛异常(out of memory),不会让其他的线程结束运行。
这是因为当一个线程抛出OOM异常后它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行,进程依然正常

4、垃圾回收器

4.1 串行

安全点:让其他线程都在这个点停下来,以免垃圾回收时移动对象地址,使得其他线程找不到被移动的对象。
因为是串行的,所以只有一个垃圾回收线程。且在该线程执行回收工作时,其他线程进入阻塞状态。

4.2 吞吐量优先

4.3 响应时间优先

相关概念

并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上
吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值. 例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%

Serial 收集器

Serial收集器是最基本的、发展历史最悠久的收集器
特点:单线程、简单高效(与其他收集器的单线程相比),采用复制算法。对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程手机效率。收集器进行垃圾回收时,必须暂停其他所有的工作线程,直到它结束(Stop The World)

ParNew 收集器

ParNew收集器其实就是Serial收集器的多线程版本
特点:多线程、ParNew收集器默认开启的收集线程数与CPU的数量相同,在CPU非常多的环境中,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。和Serial收集器一样存在Stop The World问题

Serial Old 收集器

Serial Old是Serial收集器的老年代版本
特点:同样是单线程收集器,采用标记-整理算法

Parallel Scavenge 收集器

与吞吐量关系密切,故也称为吞吐量优先收集器
特点:属于新生代收集器也是采用复制算法的收集器(用到了新生代的幸存区),又是并行的多线程收集器(与ParNew收集器类似)。
该收集器的目标是达到一个可控制的吞吐量。还有一个值得关注的点是:GC自适应调节策略(与ParNew收集器最重要的一个区别)。
GC自适应调节策略:Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRation)、晋升老年代的对象年龄(-XX:PretenureSizeThreshold)等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。

Parallel Scavenge收集器使用两个参数控制吞吐量:

Parallel Old 收集器

是Parallel Scavenge收集器的老年代版本
特点:多线程,采用标记-整理算法(老年代没有幸存区)

CMS 收集器

Concurrent Mark Sweep,一种以获取最短回收停顿时间为目标的老年代收集器
特点:基于标记-清除算法实现。并发收集、低停顿,但是会产生内存碎片
应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。
CMS收集器的运行过程分为下列4步:
初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题
并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行
重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。
并发清除:对标记的对象进行清除回收
CMS收集器的内存回收过程是与用户线程一起并发执行


4.4 G1

定义:Garbage First

JDK 9以后默认使用,而且替代了CMS 收集器

适用场景

相关参数:JDK8 并不是默认开启的,所需要参数开启

G1垃圾回收阶段

新生代伊甸园垃圾回收—–>内存不足,新生代回收+并发标记—–>回收新生代伊甸园、幸存区、老年代内存——>新生代伊甸园垃圾回收(重新开始)

Young Collection

分代是按对象的生命周期划分,分区则是将堆空间划分连续几个不同小区间,每一个小区间独立回收,可以控制一次回收多少个小区间,方便控制 GC 产生的停顿时间

E:伊甸园 S:幸存区 O:老年代

Young Collection + CM

CM:并发标记

Mixed Collection

会对E、S、O 进行全面的回收

-XX:MaxGCPauseMills:xxx 用于指定最长的停顿时间

:为什么有的老年代被拷贝了,有的没拷贝?
因为指定了最大停顿时间,如果对所有老年代都进行回收,耗时可能过高。为了保证时间不超过设定的停顿时间,会回收最有价值的老年代(回收后,能够得到更多内存)--回收垃圾最多的老年代区。

Full GC

G1在老年代内存不足时

跨代引用

回收新生代时,会找新生代对象gc root对象,但有一部分gc root对象来自于老年代,老年代的对象比较多,这时候利用卡表技术的脏卡标记。

Remembered Set 存在于E中,用于保存新生代对象对应的脏卡,之后就会到这些脏卡处遍历gc root,减少遍历时间。

脏卡:O被划分为多个区域(一个区域512K),如果该区域引用了新生代对象,则该区域被称为脏卡

Remark

重新标记阶段
在垃圾回收时,收集器处理对象的过程中
黑色:已被处理,需要保留的 灰色:正在处理中的 白色:还未处理的

但是在并发标记过程中,有可能A被处理了以后未引用C,但该处理过程还未结束,在处理过程结束之前A引用了C,这时就会用到remark

过程如下:

JDK 8u20 字符串去重

过程:

优点与缺点

-XX:+UseStringDeduplication

JDK 8u40 并发标记类卸载

在并发标记阶段结束以后,就能知道哪些类不再被使用。如果一个类加载器的所有类都不在使用,则卸载它所加载的所有类

JDK 8u60 回收巨型对象

JDK 9 并发标记起始时间的调整

GC 调优

查看虚拟机参数命令

"F:\JAVA\JDK8.0\bin\java" -XX:+PrintFlagsFinal -version | findstr "GC"

可以根据参数去查询具体的信息

调优领域

确定目标

低延迟/高吞吐量? 选择合适的GC

最快的GC是不发生GC

首先排除减少因为自身编写的代码而引发的内存问题

新生代调优

幸存区调优

老年代调优

以CMS 为例

-XX:CMSInitiatingOccupancyFraction=percent

上一篇下一篇

猜你喜欢

热点阅读