Java基础之《JVM性能调优(7)—垃圾清除算法》

一、当前主流的垃圾清除算法有哪些

1、主流的垃圾收集算法
有3种:
标记-清除算法(Mark Sweep)
复制算法(Copying)
标记-整理算法(Mark Compact)

2、垃圾算法的作用
当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。

3、标记-清除算法(Mark Sweep GC)
世界上第一个GC算法,于1960年由Lisp之父John McCarthy在其论文中发布。
顾名思义,标记-清除(Mark-Sweep)算法分为两个阶段:
(1)标记阶段:先标记出所有需要回收的对象
(2)清除阶段:统一回收所有未被标记的对象

标记阶段的主要工作就是利用可达性分析将需要回收的对象标记出来,例如上图,就是从上到下,标记哪些是存活对象,哪些是可回收对象。
标记阶段伪代码:

//伪代码

mark_phase() {
    //挨个遍历所有GC Roots
    for (r: roots) {
        mark(r);
    }
}

mark(obj) {
    if (obj.mark == FALSE) {
        obj.mark = TRUE;
        //遍历子节点
        for (child: obj.children) {
            mark(child);
        }
    }
}

清除阶段,GC会遍历整个堆,回收未被标记的对象(即垃圾们)
清除阶段伪代码:

sweep_phase() {
    cursor = $head_start;  // $head_start表示堆首地址
    while(cursor < $head_end) {
        if (cursor.mark == TRUE) {
            cursor.mark = FALSE;  // 重置标记状态,准备下一次的GC
            // 将当前对象空间释放,连接到空闲链表头部
            cursor.next = $free_list;
            $free_list = cursor;
        }
        cursor += cursor.size;  // 跳过当前对象所占空间,指向下一个对象
    }

}

标记阶段会标记所有的活动对象,由此可知,标记时间与活动对象的总数成正比。

4、标记-清除算法小结
此算法缺点有两个:
(1)效率问题,标记和清除两个过程的效率都不高(标记时间与堆对象的总数成正比)。
(2)空间问题,标记清除之后会产生大量不连续的内存空间碎片,空间碎片太多可能会导致,以后在程序的运行过程中,需要分配较大的对象时,会因为无法找到足够大的连续内存空间而不得不提前触发另一次垃圾收集行为。
后续的很多垃圾收集算法都是基于此算法的思路进行改进而得到的。
垃圾收集器中的CMS是基于标记-清除算法实现的。

二、年轻代为什么要设计两个Survivor(Form,To)空间

1、什么是复制算法,它解决什么问题
为了解决,标记-清除算法的内存碎片缺陷,复制算法于Marvin L. Minsky在1963年被提了出来。
它将堆内存分成两个区域(例如:from area和to area)。这两个空间的大小一样。
GC复制算法利用from区做分配,当from区满了以后,会将存活的对象全部复制到to区。
当复制完成以后会将from区和to区互换,这样GC也就结束了。
这样一来就不容易出现内存碎片的问题。

2、例子

(1)使用标记清除
A是GCRoot,它引用了B、D。C没有引用源。
如果采用标记-清除算法的话:
标记阶段:A、B、D是存活对象
清除阶段:把C当做垃圾,清除掉
这种情况下,就会出现碎片问题,红色位置就是碎片,不能使用。

(2)为了解决碎片问题,设计2个空间
步骤一:

A是GCRoot,它引用了B、D。C没有引用源。

步骤二:

将from空间的A对象复制到to空间。由于A有引用B、D对象,所以to空间的A也必定引用到B、D对象。由于B、D还在from空间,故to空间的A仍然持有from空间的B、D引用。

步骤三:

把A引用的B、D都拷贝到to空间,同时把引用关系重新标记上,A为GCRoot,A引用B、D。

步骤四:

最后将from空间的对象全部清除,并将from和to空间交换,就此完成了一次GC的过程。

3、为什么这么改呢

因为Eden区对象,老了之后,存到From区。
Eden里的对象永远拷到From,不会拷到To,To仅仅用来做复制计算的。
所以年轻代内存溢出,是指Eden和From区。

4、复制算法有什么优缺点
优点:
(1)没有碎片化:每次进行复制时,都会将活动对象重新集中(压缩),避免了碎片化。
(2)吞吐量高:GC复制算法只搜索并复制活动对象,因此能在较短时间内完成GC。它消耗的时间与活动对象的数量成正比,而不受堆大小的影响。
缺点:
(1)浪费一半的内存,以空间换时间。

三、什么是标记-整理算法 

1、说明
此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段:
第一阶段:
和标记清除算法一样,从根节点开始标记所有被引用对象。
第二阶段:
将所有的存活对象压缩到内存的一端,按顺序排放;最后,清理边界外所有的空间。

2、标记-压缩算法的最终效果等同于标记-清除算法执行完成后,再进行一次内存碎片整理。因此,也可以把它称为标记-清除-压缩(Mark-Sweep-Compact)算法。

3、优缺点
优点:
标记-整理算法:不仅弥补了标记-清除算法内存区域分散导致内存碎片化的问题,也解决了复制算法中,内存浪费一半的问题。

缺点:
效率低,要标记所有存活对象,并且拷贝到一个新的地方,还得更新他们的引用地址。。从效率上来说,标记-整理算法要低于复制算法。

四、jvm为什么要采用分代收集算法

1、说明
一个运行的系统,系统里面的每一个对象,他们的生命周期是不一样的,大部分是短命,小部分是长命。
所以不同的年龄对象,分开处理,即分代处理,年轻的对象放在年轻代,年老的对象放在老年代。

2、回顾下,在基于jmap剖析堆的内部结构的时候
我们已经弄懂了,堆被划分为几块,用来存放不同年龄的对象。
年轻代和老年代是1:2的关系,年轻代再分为eden:survivor(from:to)=8:1:1的关系存在。

3、jvm如何做分代算法
第一步:
系统正常运行后,大部分对象直接进eden

第二步:
由于大部分对象都进入eden,必将导致eden区满,eden满后,就触发ygc,ygc的过程,通过可达性分析算法,把存活对象复制到survivor_from区,此时from区的对象年龄为1(例如对象a_age=1)。然后清空eden区。

第三步:
eden清空后,系统继续运行,过段时间eden继续被填满,还是继续gc
a)ygc后,eden存活对象继续拷贝到survivor_from区,这次拷贝过来的对象年龄为1(例如对象b_age=1)
同时,也对survivor_from区,进行可达性分析算法,判断老对象是否存活。
b)如果没有死亡垃圾对象,a_age=2,b_age=1(所有对象年龄加1)
c)如果发现死亡对象,采用复制-清除算法对survivor的from和to区进行互换

第四步:
survivor区达到一定年龄后,进入到老年代,默认是15岁(CMS里默认是6岁)。

第五步:
如果老年代也满了,就会触发full gc。
老年代采用的标记整理或标记清除(CMS)算法,因为老年代的对象一般都是存活期较长的对象,就没必要复制来复制去的。

5、小结
由于对象的生命周期不同,才采用了分代存储,短命存年轻代,长命存老年代。
年轻代采用复制算法。
老年代采用标记-清除 或 标记-整理算法。

五、什么对象绕过年轻代,直接进入老年代
1、例子

package deathAlgorithm;

public class GCTest01 {
    private static final int _1MB = 1024 * 1024;

    /**
     -Xms30M
     -Xmx30M
     -XX:+PrintGCDetails
     -XX:+PrintHeapAtGC
     */
    public static void main(String[] args) {
        byte[] allocation1 = new byte[2 * _1MB];
        System.out.println("----------创建2M后----------");
        byte[] allocation2 = new byte[2 * _1MB];
        System.out.println("----------创建2M后----------");
        byte[] allocation3 = new byte[4 * _1MB];
        System.out.println("----------创建4M后----------");
    }
}

2、执行结果

----------创建2M后----------
----------创建2M后----------
----------创建4M后----------
Heap
 PSYoungGen      total 9216K, used 6289K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 76% used [0x00000000ff600000,0x00000000ffc24798,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
  to   space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
 ParOldGen       total 20480K, used 4096K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000)
  object space 20480K, 20% used [0x00000000fe200000,0x00000000fe600010,0x00000000ff600000)
 Metaspace       used 3291K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 357K, capacity 388K, committed 512K, reserved 1048576K

3、参考这个图

(1)分配了30M堆内存,年轻代是10M,老年代是20M。eden区是8M,From区1M,To区1M。
(2)当创建了前2个对象后,占用eden区:4M + 2M(系统占用) = 6M
(3)当创建第3个对象,eden装不下了。只能去老年代了,可以看到老年代用了4M。
(4)而且不会触发ygc。

4、为什么不会触发young gc?
因为:大对象,不经过年轻代,直接进入老年代。
通过参数-XX:PretenureSizeThreshold来设置。
默认值为0,这个0特别特殊,0就是没限制,没限制就是当大于年轻代(eden区)的时候,直接进入老年代。
此参数如果自定义,例如设置1M,当对象大于1M时,就直接进老年代。

六、年轻代的survivor对象如何进入老年代

1、例子

package deathAlgorithm;

public class SurvivorTenuringTest {

    private static final int _1MB = 1024 * 1024;

    /**
     -verbose:gc
     -Xmx200M
     -Xmn50M
     -XX:+PrintTenuringDistribution
     -XX:+PrintGCDetails
     -XX:+PrintHeapAtGC
     -XX:+UseConcMarkSweepGC
     -XX:+UseParNewGC
     -XX:MaxTenuringThreshold=3

     说明:
     Xmn:新生代大小 50M,eden=40M,s_from=5M,s_to=5M
     UseParNewGC:年轻代采用parnew垃圾回收器
     UseConcMarkSweepGC:采用CMS垃圾回收器
     MaxTenuringThreshold:设置survivor的年龄为3,即对象年龄超过3次就进入老年代
     PrintTenuringDistribution:打印survivor区的年龄age信息
     */
    public static void main(String[] args) {

        //m,在s0和s1之间不断来回拷贝增加age,直到达到MaxTenuringThreshold晋升到old
        byte[] m = new byte[_1MB];

        //youngGC后,m的age为1
        youngGC(1, 40);

        //再次youngGC后,m的age为2
        youngGC(2, 40);

        //第三次youngGC后,m的age为3
        youngGC(3, 40);

        //这次再ygc时,由于m的年龄已经是3,且MaxTenuringThreshold=3,所以m对象会晋升到old区域
        //且ygc后,s0(from)和s1(to)空间中的对象被清除
        youngGC(4, 40);
    }

    public static void youngGC(int n, int m) {
        System.out.println("....................发生第"+n+"次,young gc....................");
        for (int i=0; i<m; i++) {
            byte[] m_1 = new byte[_1MB];
        }
    }
}

2、执行结果

....................发生第1次,young gc....................
{Heap before GC invocations=0 (full 0):
 par new generation   total 46080K, used 40144K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  98% used [0x00000000f3800000, 0x00000000f5f34100, 0x00000000f6000000)
  from space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 2621440 bytes, new threshold 3 (max 3)
- age   1:    1769632 bytes,    1769632 total
: 40144K->1760K(46080K), 0.0015886 secs] 40144K->1760K(199680K), 0.0020283 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=1 (full 0):
 par new generation   total 46080K, used 1760K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,  34% used [0x00000000f6500000, 0x00000000f66b8000, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
....................发生第2次,young gc....................
{Heap before GC invocations=1 (full 0):
 par new generation   total 46080K, used 42490K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  99% used [0x00000000f3800000, 0x00000000f5fc6ab0, 0x00000000f6000000)
  from space 5120K,  34% used [0x00000000f6500000, 0x00000000f66b8000, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 2621440 bytes, new threshold 3 (max 3)
- age   2:    1702728 bytes,    1702728 total
: 42490K->1909K(46080K), 0.0007002 secs] 42490K->1909K(199680K), 0.0007207 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=2 (full 0):
 par new generation   total 46080K, used 1909K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,  37% used [0x00000000f6000000, 0x00000000f61dd7b8, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
....................发生第3次,young gc....................
{Heap before GC invocations=2 (full 0):
 par new generation   total 46080K, used 42436K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  98% used [0x00000000f3800000, 0x00000000f5f938b0, 0x00000000f6000000)
  from space 5120K,  37% used [0x00000000f6000000, 0x00000000f61dd7b8, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 2621440 bytes, new threshold 3 (max 3)
- age   3:    1702464 bytes,    1702464 total
: 42436K->1814K(46080K), 0.0005715 secs] 42436K->1814K(199680K), 0.0005918 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=3 (full 0):
 par new generation   total 46080K, used 1814K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,  35% used [0x00000000f6500000, 0x00000000f66c5ad0, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
....................发生第4次,young gc....................
{Heap before GC invocations=3 (full 0):
 par new generation   total 46080K, used 42542K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  99% used [0x00000000f3800000, 0x00000000f5fc6100, 0x00000000f6000000)
  from space 5120K,  35% used [0x00000000f6500000, 0x00000000f66c5ad0, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 2621440 bytes, new threshold 3 (max 3)
: 42542K->0K(46080K), 0.0016670 secs] 42542K->1683K(199680K), 0.0016981 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=4 (full 0):
 par new generation   total 46080K, used 0K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 1683K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3297K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
Heap
 par new generation   total 46080K, used 10421K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  25% used [0x00000000f3800000, 0x00000000f422d748, 0x00000000f6000000)
  from space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 1683K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3304K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

3、说明
堆总大小200M,年轻代50M(eden区40M,s_from区5M,s_to区5M),老年代150M。
(1)第一次触发young gc:
eden区用了98%,40M(eden space 40960K,  98% used)
(2)young gc之后:
eden区被清空了(eden space 40960K,   0% used)
垃圾收集器找到1.7M的存活对象放到了from区(from space 5120K,  34% used),就是上面age=1的数据(age   1:    1769632 bytes)
(3)因为我自己创建了1M的数据和40M的局部变量
(4)第二次触发young gc:
40M局部变量被回收,from区还是1.7M(from space 5120K,  37% used)
年龄变成2
(5)第三次触发young gc:
40M局部变量被回收,from区还是1.7M(from space 5120K,  35% used)
年龄变成3
(6)第四次触发young gc:
年龄变成4
from区变成0了,数据进入了老年代(concurrent mark-sweep generation total 153600K, used 1683K)

4、小结
当survivor的对象年龄超过MaxTenuringThreshold阈值时,就进入old区。

七、survivor的动态年龄算法

1、survivor空间足

当survivor的空间充足时,参数MaxTenuringThreshold阈值能起到很好的作用

2、survivor空间不足

但是当survivor的空间不足时,还要等年龄超过15才进入old区,就太浪费时间了

3、如何提前进入old区呢
动态年龄判断算法:
survivor区的对象年龄从小到大进行累加,当累加到X年龄时的总和大于50%(-XX:TargetSurvivorRatio=? 默认是50),只要比X大的都会晋升到老年代。
例如:
survivor区的对象年龄从小到大进行累加,当累加到3年龄时的总和=70%大于50%,那么比3大的对象都会晋升到老年代。即4岁的15%、5岁的5%晋升到老年代。

4、例子

package deathAlgorithm;

public class SurvivorRatioTest {

    private static final int _1MB = 1024 * 1024;

    /**
     -verbose:gc
     -Xmx200M
     -Xmn50M
     -XX:TargetSurvivorRatio=60
     -XX:+PrintTenuringDistribution
     -XX:+PrintGCDetails
     -XX:+PrintHeapAtGC
     -XX:+UseConcMarkSweepGC
     -XX:+UseParNewGC

     说明:
     Xmn:新生代大小 50M,eden=40M,s_from=5M,s_to=5M
     TargetSurvivorRatio:动态年龄判断大小60
     UseParNewGC:年轻代采用parnew垃圾回收器
     UseConcMarkSweepGC:采用CMS垃圾回收器
     PrintTenuringDistribution:打印survivor区的年龄age信息
     */
    public static void main(String[] args) {

        //先计算理论值:survivor为5M,TargetSurvivorRatio=60%,即当5M*60%=3M时,survivor对象进入old区

        byte[] m_1 = new byte[1363148]; //1.3m
        youngGC(1, 40);

        byte[] m_2 = new byte[838860]; //0.8m
        youngGC(2, 40);

        byte[] m_3 = new byte[_1MB]; //1m
        youngGC(3, 40);

        youngGC(4, 40);

    }

    public static void youngGC(int n, int m) {
        System.out.println("....................发生第"+n+"次,young gc....................");
        for (int i=0; i<m; i++) {
            byte[] m_1 = new byte[_1MB];
        }
    }
}

5、执行结果

....................发生第1次,young gc....................
{Heap before GC invocations=0 (full 0):
 par new generation   total 46080K, used 40451K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  98% used [0x00000000f3800000, 0x00000000f5f80dd0, 0x00000000f6000000)
  from space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3299K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 3145728 bytes, new threshold 6 (max 6)
- age   1:    2026368 bytes,    2026368 total
: 40451K->2005K(46080K), 0.0013724 secs] 40451K->2005K(199680K), 0.0014066 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] 
Heap after GC invocations=1 (full 0):
 par new generation   total 46080K, used 2005K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,  39% used [0x00000000f6500000, 0x00000000f66f54f0, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3299K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
....................发生第2次,young gc....................
{Heap before GC invocations=1 (full 0):
 par new generation   total 46080K, used 42531K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  98% used [0x00000000f3800000, 0x00000000f5f93848, 0x00000000f6000000)
  from space 5120K,  39% used [0x00000000f6500000, 0x00000000f66f54f0, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 3145728 bytes, new threshold 6 (max 6)
- age   1:     838880 bytes,     838880 total
- age   2:    2017328 bytes,    2856208 total
: 42531K->2991K(46080K), 0.0010764 secs] 42531K->2991K(199680K), 0.0011007 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=2 (full 0):
 par new generation   total 46080K, used 2991K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,  58% used [0x00000000f6000000, 0x00000000f62ebcc0, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
....................发生第3次,young gc....................
{Heap before GC invocations=2 (full 0):
 par new generation   total 46080K, used 43517K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  98% used [0x00000000f3800000, 0x00000000f5f93900, 0x00000000f6000000)
  from space 5120K,  58% used [0x00000000f6000000, 0x00000000f62ebcc0, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 3145728 bytes, new threshold 3 (max 6)
- age   1:    1048592 bytes,    1048592 total
- age   2:     838880 bytes,    1887472 total
- age   3:    2017176 bytes,    3904648 total
: 43517K->4038K(46080K), 0.0008232 secs] 43517K->4038K(199680K), 0.0008419 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=3 (full 0):
 par new generation   total 46080K, used 4038K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,  78% used [0x00000000f6500000, 0x00000000f68f1800, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
....................发生第4次,young gc....................
{Heap before GC invocations=3 (full 0):
 par new generation   total 46080K, used 44766K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  99% used [0x00000000f3800000, 0x00000000f5fc6130, 0x00000000f6000000)
  from space 5120K,  78% used [0x00000000f6500000, 0x00000000f68f1800, 0x00000000f6a00000)
  to   space 5120K,   0% used [0x00000000f6000000, 0x00000000f6000000, 0x00000000f6500000)
 concurrent mark-sweep generation total 153600K, used 0K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [ParNew
Desired survivor size 3145728 bytes, new threshold 6 (max 6)
- age   2:    1048592 bytes,    1048592 total
- age   3:     838880 bytes,    1887472 total
: 44766K->1843K(46080K), 0.0018963 secs] 44766K->3833K(199680K), 0.0019157 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap after GC invocations=4 (full 0):
 par new generation   total 46080K, used 1843K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,   0% used [0x00000000f3800000, 0x00000000f3800000, 0x00000000f6000000)
  from space 5120K,  36% used [0x00000000f6000000, 0x00000000f61cccf0, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 1990K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3300K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 358K, capacity 388K, committed 512K, reserved 1048576K
}
Heap
 par new generation   total 46080K, used 14313K [0x00000000f3800000, 0x00000000f6a00000, 0x00000000f6a00000)
  eden space 40960K,  30% used [0x00000000f3800000, 0x00000000f442d788, 0x00000000f6000000)
  from space 5120K,  36% used [0x00000000f6000000, 0x00000000f61cccf0, 0x00000000f6500000)
  to   space 5120K,   0% used [0x00000000f6500000, 0x00000000f6500000, 0x00000000f6a00000)
 concurrent mark-sweep generation total 153600K, used 1990K [0x00000000f6a00000, 0x0000000100000000, 0x0000000100000000)
 Metaspace       used 3306K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 359K, capacity 388K, committed 512K, reserved 1048576K

6、说明
(1)第一次触发young gc:
垃圾收集器找到2M的存活对象(age   1:    2026368 bytes,    2026368 total)
from区是2M对象(from space 5120K,  39% used)
(2)第二次触发young gc:
存活对象
- age   1:     838880 bytes,     838880 total
- age   2:    2017328 bytes,    2856208 total
都在from区(from space 5120K,  58% used)
(3)第三次触发young gc:
存活对象
- age   1:    1048592 bytes,    1048592 total
- age   2:     838880 bytes,    1887472 total
- age   3:    2017176 bytes,    3904648 total
都在from区(from space 5120K,  78% used)
此时form区已经大于60%了,那下一次young gc绝对会进入老年代
(4)第四次触发young gc:
存活对象
- age   2:    1048592 bytes,    1048592 total
- age   3:     838880 bytes,    1887472 total
from区只有0.8M和1M的对象,1.9M的对象进入了老年代(concurrent mark-sweep generation total 153600K, used 1990K)

八、Java内存泄露的根本原因是什么

1、内存泄露
程序运行期间分配的对象,用完之后却没有被GC回收,始终占用着内存,即对象用完后又无法gc回收,就发生了内存泄露。

2、Java内存泄露的根本原因是什么
长生命周期的对象持有短生命周期对象的引用,就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。

九、有哪些常见的内存泄露场景

1、静态集合类引起内存泄露
静态的集合对象例如:List、HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有对象Object也不会被释放,因为他们也将一直被集合对象引用。
例子:

package deathAlgorithm;

import java.util.ArrayList;
import java.util.List;

public class RevealList {

    static List list = new ArrayList();

    public void addList() {
        for (int i=0; i<10; i++) {
            Object o = new Object();
            list.add(o);
        }
    }

    public void readList() {
        for (Object o : list) {
            System.out.println(o);
        }
    }

    public static void main(String[] args) {
        RevealList reveallist = new RevealList();
        reveallist.addList();
        reveallist.readList();
    }
}

在这个例子中,循环申请Object对象,并将所有申请的对象放入一个List中,List引用了Object的短命对象,这些短命对象对GC来说是不可回收的。因此如果对象加入到List后,还必须从List中删除,最简单的方法就是调用list.clear()。

2、单例模式引起的内存泄露
由于单例的静态特性使得其生命周期跟应用的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄露。

package deathAlgorithm;

import java.util.ArrayList;
import java.util.List;

public class Singleton {

    private static Singleton instance;

    private List list;

    public List getList() {
        return list;
    }

    public void setList(List list) {
        this.list = list;
    }

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        List list = new ArrayList();
        for (int i=0; i<10; i++) {
            Object o = new Object();
            list.add(o);
        }
        Singleton.getInstance().setList(list);
    }
}

3、常见的tcp连接
比如数据库连接(dataSource.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC回收的。
解决:在try里面去连接,在finally里面释放连接。

九、内存溢出和内存泄露的区别

1、内存溢出
内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。
内存泄露:一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光,最终演变成内存溢出。

十、常见的内存溢出有哪些

1、java.lang.OutOfMemoryError: Metaspace(方法区溢出)
我们知道jvm通过持久化实现了java虚拟机规范中的方法区,而运行时常量池就是保存在方法区中的,因此发生这种溢出可能是运行时常量池溢出,或是由于程序中使用了大量的jar或class,使得方法区中保存的class对象没有被及时回收或者class信息占用的内存超过了配置的大小。

2、java.lang.OutOfMemoryError: Java heap space(堆溢出)
(1)发生这种溢出的原因一般是创建的对象太多,在进行垃圾回收之前对象数量达到了最大堆的容量限制。
(2)解决这个区域异常的方法一般是通过内存映像分析工具对dump出来的堆转储快照进行分析,看到底是内存溢出还是内存泄露。
(3)如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链,定位出泄露代码的位置,修改程序或算法。
(4)如果不存在泄露,就是说内存中的对象确实都还必须存活,那就应该检查虚拟机的堆参数-Xmx(最大堆大小)和-Xms(初始堆大小),与机器物理内存对比看是否可以调大。

3、虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值