六、JVM之垃圾回收

六、JVM之垃圾回收

GC日志

-Xmx1024m -Xms1024m -XX:+PrintGCDetails

Heap
PSYoungGen total 305664K, used 26214K [0x00000000eab00000, 0x0000000100000000, 0x0000000100000000)
eden space 262144K, 10% used [0x00000000eab00000,0x00000000ec499be8,0x00000000fab00000)
from space 43520K, 0% used [0x00000000fd580000,0x00000000fd580000,0x0000000100000000)
to space 43520K, 0% used [0x00000000fab00000,0x00000000fab00000,0x00000000fd580000)
ParOldGen total 699392K, used 0K [0x00000000c0000000, 0x00000000eab00000, 0x00000000eab00000)
object space 699392K, 0% used [0x00000000c0000000,0x00000000c0000000,0x00000000eab00000)
Metaspace used 3224K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 351K, capacity 388K, committed 512K, reserved 1048576K

下面再编写一个代码,观察GC的触发操作

public class Demo {
    public static void main(String[] args){
        Random random = new Random();
        String  val = "test";
        while (true){
            val+=val+random.nextInt(999999999)+random.nextInt(999999999);
        }
    }
}

[GC (Allocation Failure) [PSYoungGen: 2031K->488K(2560K)] 2031K->676K(9728K), 0.0013870 secs] [Times: user=0.06 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 2441K->504K(2560K)] 2629K->1254K(9728K), 0.0010120 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1950K->488K(2560K)] 2700K->1951K(9728K), 0.0011297 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1940K->488K(2560K)] 4796K->4049K(9728K), 0.0012419 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1257K->488K(2560K)] 6212K->5443K(9728K), 0.0009412 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 488K->496K(1536K)] 5443K->5491K(8704K), 0.0005513 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 496K->0K(1536K)] [ParOldGen: 4995K->2727K(7168K)] 5491K->2727K(8704K), [Metaspace: 3281K->3281K(1056768K)], 0.0066911 secs] [Times: user=0.09 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 30K->32K(2048K)] 6938K->6940K(9216K), 0.0004666 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 32K->0K(2048K)] [ParOldGen: 6908K->2030K(7168K)] 6940K->2030K(9216K), [Metaspace: 3281K->3281K(1056768K)], 0.0082892 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 19K->0K(2048K)] 6231K->6211K(9216K), 0.0003457 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 6211K->4817K(7168K)] 6211K->4817K(9216K), [Metaspace: 3281K->3281K(1056768K)], 0.0027242 secs] [Times: user=0.08 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4817K->4817K(9216K), 0.0003852 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4817K->4798K(7168K)] 4817K->4798K(9216K), [Metaspace: 3281K->3281K(1056768K)], 0.0095410 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

GC日志分析

JVM垃圾回收

垃圾对象判定标准

jvm的GC工作主要针对的对象是堆内存,在做GC工作之前,首先要判定堆内存中的对象实例是否为垃圾,通常使用以下两种算法来定义

1.引用计数算法

java在运行时,当有一个地方引用该对象实例,会将这个对象实例加1,引用失效时就减1,jvm在扫描内存时,发现引用计数值为0的则是垃圾对象,计数值大于0的则为活跃对象。

目前垃圾回收算法,没有采用引用计数算法,原因是在对象互相引用的情况下,无法判定两者是否为垃圾对象。

2. 根搜索算法

根搜索算法是以“GC ROOTS”为起始点往下搜索,所有经过的对象合并起来称为引用链,在这引用链里,没有的对象称为垃圾对象,在引用链里的是活跃对象。那什么样的对象才能称为“GC ROOTS”呢?以下四种可以

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中 JNI(Native 方法)的引用对象。

垃圾回收算法

1. 标记-清除(Mark-Sweep)

jvm会扫描所有的对象实例,通过根搜索算法,将活跃对象进行标记,jvm再一次扫描所有对象,将未标记的对象进行清除,只有清除动作,不作任何的处理,这样导致的结果会存在很多的内存碎片。

2. 复制(copying)

jvm扫描所有对象,通过根搜索算法标记被引用的对象,之后会申请新的内存空间,将标记的对象复制到新的内存空间里,存活的对象复制完,会清空原来的内存空间,将新的内存最为jvm的对象存储空间。这样虽然解决了内存内存碎片问题,但是如果对象很多,重新申请新的内存空间会很大,在内存不足的场景下,会对jvm运行造成很大的影响

3. 标记-整理(Mark-compact)

标记整理实际上是在标记清除算法上的优化,执行完标记清除全过程之后,再一次对内存进行整理,将所有存活对象统一向一端移动,这样解决了内存碎片问题。

4. 分代回收

目前jvm常用回收算法就是分代回收,年轻代以复制算法为主,老年代以标记整理算法为主。原因是年轻代对象比较多,每次垃圾回收都有很多的垃圾对象回收,而且要尽可能快的减少生命周期短的对象,存活的对象较少,这时候复制算法比较适合,只要将有标记的对象复制到另一个内存区域,其余全部清除,并且复制的数量较少,效率较高;而老年代是年轻代筛选出来的对象,被标记比较多,需要删除的对象比较少,显然采用标记整理效率较高。