JVM垃圾回收(GC)

  • 参考
  • 只发生在heap(堆)中
  • 分类
    • 次数上频繁收集Young区(年轻代),Minor GC
    • 次数上较少收集Old区(老年代),Full GC
    • 基本上不动Perm区(永久区没有GC)
  • 判断是不是垃圾
    • 引用计数法
      • 已经被淘汰了,没有办法处理循环引用问题,JVM已经不用这个算法了
    • 可达性分析算法
      • 使用一系列的GC Roots的对象(包括:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象)作为起点,从节点开始向下搜索,当没有被GCRoots链接到的对象就可以回收
      • 后来实现的垃圾判断算法中,都是从程序运行的根节点出发,遍历整个对象引用,查找存活的对象。
      • 通常起点是栈或者寄存器中的引用,如一些根对象(java栈, 静态变量, 寄存器...)
        • 栈是真正进行程序执行地方,所以要获取哪些对象正在被使用,则需要从Java栈开始。同时,一个栈是与一个线程对应的,因此,如果有多个线程的话,则必须对这些线程对应的所有的栈进行检查。
        • 除了栈外,还有系统运行时的寄存器等,也是存储程序运行数据的。
  • 四大收集算法
    • 复制算法
      • 特点:从一个内存区域复制到另一个内存区域
      • 时机和位置:发生在Young区(年轻代),Minor GC
      • 缺点
        • 需要双倍空间
      • 优点
        • 效率高,速度快
        • 没有内存碎片,是连续的。
    • 标记-清除
      • 特点:清除没有标记的,过程是标记+清除
      • 时机和位置:发生在Old区(老年代),Full GC
      • Old区(老年代)一般是由标记清除或者是标记整理的混合实现
      • 缺点
        • 清理过程中要扫描两次,标记一次,清除一次,耗时严重
        • 有内存碎片
      • 优点
        • 不需要双倍空间
    • 标记-整理/压缩
      • 特点:先进行标记,再进行压缩,过程是标记+压缩
      • 时机和位置:发生在Old区(老年代),Full GC
      • Old区(老年代)一般是由标记清除或者是标记整理的混合实现
      • 缺点
        • 清理过程中要扫描两次,标记一次,压缩一次,耗时严重
        • 需要移动对象的成本
      • 优点
        • 不需要双倍空间
        • 没有内存碎片,是连续的。
    • 标记清除压缩(前两者结合)
      • 标记、清除多次以后再进行压缩,减少了移动对象的成本
    • 分代收集算法(对象从新生代变成老年代的判定方法)
      • 每经历一次Minor GC(复制算法回收对象)就会让对象的年龄加一,当对象年龄为15时就会把新生代的对象放入老年代中。
      • 如果Survivor区中的存放不下的对象就会放入老年代中:对象会优先在Eden区中分配,而后通过一次Minor GC就让对象进入Survivor区中,当Survivor区中存放不下该对象时就会将该对象放入老年代。
      • 新生成的大对象也会直接放入老年代中(可以通过-XX:+PretenuerSizeThreshold设置)超过这个size的对象一生成就会放入老年代。
      • 老年代的都是比较重要的,好多次(一般15)清理都没清掉的。为了重复利用常用的、初始化麻烦的大对象,可以通过JVM参数设置让它一开始就放到老年代
  • 实际工作中的意义/用途
    • 尽可能减少Full GC的次数,导致Full GC的原因包括:老年代被写满、永久代(Perm)被写满和System.gc()被显式调用等。
    • 结合JVisualVM分析问题
      • 有了堆信息查看方面的功能,我们一般可以顺利解决以下问题:
        • 年老代年轻代大小划分是否合理
        • 内存泄漏(一般是代码有问题)
          • 年老代堆空间被占满 ####异常: java.lang.OutOfMemoryError: Java heap space
            • 这是最典型的内存泄漏方式,所有堆空间都被无法回收的垃圾对象占满,虚拟机无法再在分配新空间
            • 这种方式解决起来也比较容易,一般就是根据垃圾回收前后情况对比,同时根据对象引用情况(常见的集合对象引用)分析,基本都可以找到泄漏点。
          • 持久代被占满 ####异常:java.lang.OutOfMemoryError: PermGen space
            • 说明: Perm空间被占满。无法为新的class分配存储空间而引发的异常。这个异常以前是没有的,但是在Java反射大量使用的今天这个异常比较常见了。主要原因就是大量动态反射生成的类不断被加载,最终导致Perm区被占满。
          • 堆栈溢出 ####异常:java.lang.StackOverflowError
            • 说明:这个就不多说了,一般就是递归没返回,或者循环调用造成
          • 线程堆栈满 ####异常:Fatal: Stack size too small
            • 说明:java中一个线程的空间大小是有限制的。JDK5.0以后这个值是1M。与这个线程相关的数据将会保存在其中。但是当线程空间满了以后,将会出现上面异常。
            • 解决:增加线程栈大小。-Xss2m。但这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部分。
          • 系统内存被占满 ####异常:java.lang.OutOfMemoryError: unable to create new native thread
            • 说明: 这个异常是由于操作系统没有足够的资源来产生这个线程造成的。系统创建线程时,除了要在Java堆中分配内存外,操作系统本身也需要分配资源来创建线程。因此,当线程数量大到一定程度以后,堆中或许还有空间,但是操作系统分配不出资源来了,就出现这个异常了。
        • 垃圾回收算法设置是否合理
      • 热点分析
        • CPU热点
          • 结合CPU profiling+快照功能,其中可以看到调用堆栈即CPU占用
    • JVM调优。可以通过参数配置JVM参数进行调优
      • 年轻代大小选择
        • 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
        • 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用。
      • 年老代大小选择
        • 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
          • 并发垃圾收集信息
          • 并发垃圾收集信息
          • 传统GC信息
          • 花在年轻代和年老代回收上的时间比例 减少年轻代和年老代花费的时间,一般会提高应用的效率
      • 吞吐量优先的应用
        • 一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
      • 具体参数
        • -Xms:初始堆大小,JVM 启动的时候,给定堆空间大小。
        • -Xmx:最大堆大小,JVM 运行过程中,如果初始堆空间不足的时候,最大可以扩展到多 少。
        • -Xmn:设置年轻代大小。整个堆大小=年轻代大小+年老代大小+持久代大小。持久代一 般固定大小为 64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大, Sun 官方推荐配置为整个堆的 3/8。
        • -Xss:设置每个线程的 Java 栈大小。JDK5.0 以后每个线程 Java 栈大小为 1M,以前每 个线程堆栈大小为 256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减 小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成。
        • -XX:NewSize=n:设置年轻代大小
        • -XX:NewRatio=n:设置年轻代和年老代的比值。如:为 3,表示年轻代与年老代比值为 1: 3,年轻代占整个年轻代+年老代和的 1/4
        • -XX:SurvivorRatio=n:年轻代中 Eden 区与两个 Survivor 区的比值。注意 Survivor 区有两个。 如:3,表示 Eden:Survivor=3:2,一个 Survivor 区占整个年轻代的 1/5
        • -XX:MaxPermSize=n:设置持久代大小
        • -XX:MaxTenuringThreshold:设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象不经 过 Survivor 区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代 的存活时间,增加在年轻代即被回收的概率。
原文地址:https://www.cnblogs.com/wyp1988/p/12058877.html