java 垃圾回收总结.docx

上传人:b****7 文档编号:9543218 上传时间:2023-02-05 格式:DOCX 页数:14 大小:276.47KB
下载 相关 举报
java 垃圾回收总结.docx_第1页
第1页 / 共14页
java 垃圾回收总结.docx_第2页
第2页 / 共14页
java 垃圾回收总结.docx_第3页
第3页 / 共14页
java 垃圾回收总结.docx_第4页
第4页 / 共14页
java 垃圾回收总结.docx_第5页
第5页 / 共14页
点击查看更多>>
下载资源
资源描述

java 垃圾回收总结.docx

《java 垃圾回收总结.docx》由会员分享,可在线阅读,更多相关《java 垃圾回收总结.docx(14页珍藏版)》请在冰豆网上搜索。

java 垃圾回收总结.docx

java垃圾回收总结

java垃圾回收总结

以前看过很多次关于垃圾回收相关的文章,都只是看过就忘记了,没有好好的整理一下,发现写文章可以强化自己的记忆。

 

java与C,c++有很大的不同就是java语言开发者不需要关注内存信息,不会显式的直接操作内存,而是通过jvm虚拟机来实现。

java虚拟机运行的时候内存分配图如下图:

 

jvm虚拟机栈:

一个是线程独有的,每次启动一个线程,就创建一个jvm虚拟机栈,线程退出的时候就销毁。

这里面主要保存线程本地变量名和局部变量值。

本地方法栈:

调用本地jni方法的时候而创建的。

这里分配的jvm之外的内存空间。

方法调用结束之后销毁。

pc寄存器:

这个保存线程当前执行的字节码指令

堆:

主要保存创建的对象。

方法区:

保存class相关的信息。

主要是class的一个内存结构信息

常量池:

方法区的一部分,主要保存class内存结构中常量值例如String值,publicstaticfinal类型的值

 

我们这里说的垃圾回收,主要是java虚拟机对堆内存区域的回收。

 

1首先的问题是:

jvm如何知道那些对象需要回收?

目前有两种算法

∙引用计数法

每个对象上都有一个引用计数,对象每被引用一次,引用计数器就+1,对象引用被释放,引用计数器-1,直到对象的引用计数为0,对象就标识可以回收

这个可以用数据算法中的图形表示,对象A-对象B-对象C都有引用,所以不会被回收,对象B由于没有被引用,没有路径可以达到对象B,对象B的引用计数就就是0,对象B就会被回收。

 

 

但是这个算法有明显的缺陷,对于循环引用的情况下,循环引用的对象就不会被回收。

例如下图:

对象A,对象B循环引用,没有其他的对象引用A和B,则A和B都不会被回收。

 

∙root搜索算法

这种算法目前定义了几个root,也就是这几个对象是jvm虚拟机不会被回收的对象,所以这些对象引用的对象都是在使用中的对象,这些对象未使用的对象就是即将要被回收的对象。

简单就是说:

如果对象能够达到root,就不会被回收,如果对象不能够达到root,就会被回收。

如下图:

对象D访问不到根对象,所以就会被回收

以下对象会被认为是root对象:

∙被启动类(bootstrap加载器)加载的类和创建的对象

∙jvm运行时方法区类静态变量(static)引用的对象

∙jvm运行时方法去常量池引用的对象

∙jvm当前运行线程中的虚拟机栈变量表引用的对象

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

由于这种算法即使存在互相引用的对象,但如果这两个对象无法访问到根对象,还是会被回收。

如下图:

对象C和对象D互相引用,但是由于无法访问根,所以会被回收。

jvm在确定是否回收的对象的时候采用的是root搜索算法来实现。

在root搜索算法的里面,我们说的引用这里都指定的是强引用关系。

所谓强引用关系,就是通过用new方式创建的对象,并且显示关联的对象

?

1

Objectobj=new Object();

以上就是代表的是强引用关系,变量obj强引用了Object的一个对象。

java里面有四种应用关系,从强到弱分别为:

StrongReference(强引用)–>WeakReference(弱引用)->SoftReference(软引用)–>PhantomReference(引用)

 

StrongReference:

只有在引用对象root不可达的情况下才会标识为可回收,垃圾回收才可能进行回收

WeakReference:

即使在root算法中其引用的对象root可达到,但是如果jvm堆内存不够的时候,还是会被回收。

SoftReference:

无论其引用的对象是否root可达,在响应内存需要时,由垃圾回收判断是否需要回收。

PhantomReference:

在回收器确定其指示对象可另外回收之后,被加入垃圾回收队列.

 

下面可以看一个测试

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

public class ReferenceTest{

 

    public static final Mapmap=new HashMap();

 

    public static void main(String[]args){

        for (int i=0;i<1000;i++){

            map.put(i,new WeakReference(new ReferenceObject(i)));

        }

 

        int i=0;

        for (Referencer:

map.values()){

            if (r.get()==null){

                i++;

            }

        }

        System.out.println("被回收的对象数:

" +i);

    }

 

    static class ReferenceObject{

        private int    i;

 

        private byte[]b;

 

        public ReferenceObject(int i){

            this.i=i;

            b=new byte[1024 *10];

        }

    }

}

这里创建大约1000个10K的WeakReference对象,最后打印的结果是:

被回收的对象数:

767,这里ReferenceObject如果设置为1K的话,最后的打印结果是0

这个例子并不严谨,但是却说明了被WeakReference的对象在一定的时候会被jvm回收,但是强引用就不会出现这种状态。

java垃圾回收总结

(2)

上一篇文章 介绍了jvm虚拟机运行时内存结构以及如何标识需要回收的对象,这一节主要讲解垃圾回收的基本算法。

基本上jvm内存回收有三种基本算法

∙标记-清除

标记清除的算法最简单,主要是标记出来需要回收的对象,然后然后把这些对象在内存的信息清除。

如何标记需要回收的对象,在上一篇文章里面已经有说明。

 

∙标记-清除-压缩

这个算法是在标记-清除的算法之上进行一下压缩空间,重新移动对象的过程。

因为标记清除算法会导致很多的留下来的内存空间碎片,随着碎片的增多,严重影响内存读写的性能,所以在标记-清除之后,会对内存的碎片进行整理。

最简单的整理就是把对象压缩到一边,留出另一边的空间。

由于压缩空间需要一定的时间,会影响垃圾收集的时间。

 

∙标记-清除-复制

这个算法是吧内存分配为两个空间,一个空间(A)用来负责装载正常的对象信息,,另外一个内存空间(B)是垃圾回收用的。

每次把空间A中存活的对象全部复制到空间B里面,在一次性的把空间A删除。

这个算法在效率上比标记-清除-压缩高,但是需要两块空间,对内存要求比较大,内存的利用率比较低。

适用于短生存期的对象,持续复制长生存期的对象则导致效率降低

 

 

由于现在的处理器都是多核的,处理器的性能得到了极大的提升,所以在此基础上有产生了几种垃圾收集算法。

主要包括两种算法

∙并行标记清除

所谓并行,就是原来垃圾回收只是一个线程进行。

现在创建多个垃圾回收线程。

并行的进行标记和清除。

比如把需要标记的对象平均分配到多个线程之后,当标记完成之后,多个线程进行清除。

 

∙并发标记清除

所谓并发,就是应用程序和垃圾回收可以同时执行。

在标记清除算法中,在标记对象和清除对象,以及压缩对象的情况下是需要暂停应用的。

那么并行标记清除压缩算法则是在标记清除压缩算法的基础上,把标记清除压缩算法分为以下几个过程

初始标记->并发标记->重新标记->并发清除->重置

 

以上几种算法是垃圾回收的基本算法,jvm垃圾回收就是在以上几种算法为基础的,在以上几种算法的基础上,java垃圾回收器可以分为以下几种:

∙串行收集器

用单线程处理所有垃圾回收工作,因为无需多线程交互,所以效率比较高。

但是,也无法使用多处理器的优势,所以此收集器适合单处理器机器

单线程收集器。

在目前多核服务器端运行的情况下,效率比较低。

比较适合堆内存小的情况下使用。

∙并行收集器

用多线程处理所有垃圾回收工作,利用多核处理器的优势。

但是如果线程数量过多,导致线程之间频繁调度,也会影响性能。

一半并行收集的线程是处理器的个数。

“对吞吐量有高要求”,多CPU、对应用响应时间无要求的中、大型应用。

举例:

后台处理、科学计算。

∙并发收集器

并发收集器主要减少年老代的暂停时间,他在应用不停止的情况下使用独立的垃圾回收线程,跟踪可达对象。

在每个年老代垃圾回收周期中,在收集初期并发收集器会对整个应用进行简短的暂停(初始标记的过程),在收集中还会再暂停一次。

第二次暂停会比第一次稍长(重新标记的过程),在此过程中多个线程同时进行垃圾回收工作。

并发收集器使用处理器换来短暂的停顿时间。

在一个N个处理器的系统上,并发收集部分使用K/N个可用处理器进行回收,一般情况下1<=K<=N/4。

在只有一个处理器的主机上使用并发收集器,设置为incrementalmode模式也可获得较短的停顿时间。

浮动垃圾:

由于在应用运行的同时进行垃圾回收,所以有些垃圾可能在垃圾回收进行完成时产生,这样就造成了“FloatingGarbage”,这些垃圾需要在下次垃圾回收周期时才能回收掉。

所以,并发收集器一般需要20%的预留空间用于这些浮动垃圾。

ConcurrentModeFailure:

并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。

这种情况下将会发生“并发模式失败”,此时整个应用将会暂停,进行垃圾回收。

并发收集器,在垃圾回收的时候采用并发标记清除算法的收集器

对响应时间要求高的,多CPU,大型应用。

比如页面请求/web服务器。

前端业务系统用的比较多。

我们以sun公司的hotspot虚拟机为例,hotspot采用的是分代回收算法,因为根据经验,一般80%的对象就是朝生夕灭,对象的生命期都很短。

而根据对象的生命周期不同,可以划分出来几个区域,一部分区域的对象创建比较频繁,但是生命周期比较短,一部分区域对象生命周期比较长,还有一部分区域对象生命周期几乎和java虚拟机相同。

sunspot堆内存分为三个区域:

∙新生代(Young)

新生代包括两个区:

Eden区和Survivor区,其中Survivor区一般也分成两块,简称Survivor1Space和Survivor2Space(或者FromSpace和ToSpace)。

新生代通常存活时间较短,因此基于标记清除复制算法来进行回收,扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和From或To之间copy。

新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。

当连续分配对象时,对象会逐渐从eden到Survior,最后到旧生代。

可以采用串行处理和并行处理器

∙老年代(Old)

在垃圾回收多次,如果对象仍然存活,并且新生代的空间不够,则对象会存放在老年代。

在老年代采用的是标记清除压缩算法。

因为老年代的对象一般存活时间比较长,每次标记清除之后,会有很多的零碎空间,这个就是所谓的浮动垃圾。

当老年代的零碎空间不足以分配一个大的对象的时候,就会采用压缩算法。

在压缩的时候,应用需要暂停。

可以采用串行处理,并行处理器,以及并发处理器。

∙持久代(Permanent)

这部分空间主要存放java方法区的数据以及启动类加载器加载的对象。

这一部分对象通常不会被回收。

所以持久代空间在默认的情况下是不会被垃圾回收的。

 

由于把内存空间分为三块,一般把新生代的GC称为minorGC,把老年代的GC成为fullGC,所谓fullgc会先出发一次minorgc,然后在进行老年代的GC。

具体的过程如下:

首先想eden区申请分配空间,如果空间够,就直接进行分配,否则进行一次MinorGC。

minorGC首先会对Eden区的对象进行标记,标记出来存活的对象。

然后把存活的对象copy到From空间。

如果From空间足够,则回收eden区可回收的对象。

如果from内存空间不够,则把From空间存活的对象复制到To区,如果TO区的内存空间也不够的话,则把To区存活的对象复制到老年代。

如果老年代空间也不够(或者达到触发老年年垃圾回收条件的话)则触发一次fullGC。

简单方向就是Eden->From->To->Old,如下图所示。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 高等教育 > 文学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1