JVM | 垃圾回收

JVM | 垃圾回收

1. Java垃圾回收的概念

1-1. 如何判断哪些是垃圾对象,引用计数法,根搜索算法

1-2. 哪些是GC Roots

  1. JVM栈中的引用
  2. 方法区静态变量的引用
  3. JNI(即native方法)中的引用

1-3. 方法区(永久代)会发生GC吗,会回收哪些对象?

  1. 方法区的垃圾回收主要回收两部分内容:废弃常量无用类
  2. 废弃常量:常量池中没有被其他地方引用的对象,在GC时,有必要的情况下会被回收
  3. 无用类:无用的类必须满足三个条件
    • 该类的所有实例对象都被回收了,jvm中不存在该类的任何实例
    • 加载该类的ClassLoader已经被回收
    • 该类的Class对象没有任何引用,没有任何地方通过反射引用该类
  4. 虚拟机可以对满足上述条件的无用类进行回收,可以通过-Xnoclassgc关闭对类的回收(拓展阅读:JVM参数详解)
  5. 在大量使用反射,动态代理,自定义ClassLoader的场景,会有大量的类被创建,需要JVM具备类的卸载功能.

1-4. 什么时候会发生minor GC和full GC

  • minorGC时机:
    1. 当JVM无法为一个新创建的对象分配空间时,会触发minor GC,比如新创建的对象大于Eden区剩余空间
    2. full GC之前会调用一次minor GC
    3. 如果设置了-XX:+CMSScavengeBeforeRemark参数,CMS就会在重新标记之前执行一次minor GC
  • fullGC时机:
    1. minor GC每次从新生代晋升到老年代的对象的平均大小 > 老年代的剩余空间
    2. minor GC后新生代存活的对象总大小 > 老年代的剩余空间
    3. -XX:CMSInitiatingOccupancyFactioncms中可以配置老年代已使用空间超过了这个参数指定的比例,也会触发Full GC
    4. 大对象直接进入老年代,这时对象的大小超过了老年代剩余空间
      • 通过-XX:PretenureSizeThreshold=xxx可以设置大对象的标准,超过这个值,会被直接放入到老年代,注意这个时候是不会触发minor GC的
      • PretenureSizeThreshold参数只对SerialParNew两款收集器有效,Parallel Scavenge收集器不认识这个参数,Parallel Scavenge收集器一般并不需要设置。如果遇到必须使用此参数的场合,可以考虑ParNew加CMS的收集器组合
    5. 永久带(PermGen jdk7)或元空间(metadata jdk8)不足会触发full GC
      • 永久带和元空间都是Hotspot对方法区的实现方式.
      • JDK8中元空间默认最大值为21M,可以通过-XX:MetaspaceSize=xxM来设置元空间大小
      • 查看JVM初始化参数默认值:java -XX:+PrintFlagsInitial

2. 垃圾回收器

3. 安全点和安全区域

我对于安全点的理解,把GC的过程看成街道消毒的过程,用户线程就是在街上跑步前进的人,安全点就是街道上每隔一段距离设置的一个防空洞,街上的人会有三种状态:跑步(running),在防空洞等待消毒完成(block),在街外面等人(in native),每个人状态修改时,必须将自己的状态写在一个本子上(serialization page内存).消毒的时候,必须确保所有人都已经躲进地下的防空洞中了或者在街外等人.这要怎么做呢,首先我准备消毒的时候,会设置一个信号灯(sync_state),在街上跑步的人每路过一个防空洞,都要看一下这个预备消毒的信号灯有没有亮,如果亮了,就躲进防空洞里面,停止跑步,并修改自己的状态为阻塞(block),等消毒完成后在出来继续跑.在消毒的过程中,街道办事处就不允许人修改自己的状态了(设置serialization page只读).
安全区域就是一开始我在防空洞里面睡觉(线程调用sleep),或者我到街外面等人去了(等待IO),在睡觉或者等待的时候,我会把我的状态修改好(block或者in native),而且在我睡醒或者等到人了之后,在回到街道之前,我要去修改我的状态,如果当前街道还在消毒,办事处不会让我改状态的(serialization page只读),只有等到消毒完成,我才可以重新回到街道.

  • 安全点的位置

    1. 方法返回前
    2. 调用方法之后
    3. 抛出异常的位置
    4. 循环的末尾
      • 如果for循环中有明确的循环计数器变量,而且该变量有明确的起始值、终止值、步进长度的循环,它有可能被优化为循环末尾没有safepoint,这种循环被称为'counted loop'于是如果这个循环的循环次数很多、循环体里又不调用别的方法或者是调用了方法但被内联进来了,就有可能会导致进入safepoint非常耗时。
      • 解决办法: 把单层循环拆成等价的双重嵌套循环,这样其中一层循环末尾的safepoint就可能会留下来,减少进入safepoint的等待时间。
  • 为啥要在这四个地方设置安全点
    JVM要等所有应用线程进入到安全点后才会分派GC任务,如果有线程一直没有进入到安全点,就会导致GC的停顿时间延长.

  • 安全点应用实战

    • 生产环境推荐使用-XX:+PrintGCApplicationStoppedTime打印JVM停顿时间,如果GC日志前面有较大的停顿,要就要考虑是不是代码里有较大的循环操作.
    • 在测试环境开启安全点统计日志
      -XX:+UnlockDiagnosticVMOptions  //diagnostic [ˌdaɪəɡˈnɑstɪk] 诊断的,判断的
      -XX:+LogVMOutput
      -XX:LogFile=/dev/shm/vm.log
      -XX:+PrintSafepointStatistics   //打印安全点分析
      -XX:PrintSafepointStatisticsCount=1  
  • 看看大神是怎么解决问题的 无法进入安全点导致GC停顿时间过长问题

  • 推荐 JVM安全点介绍及实战

  • 推荐 安全点图解

  • 推荐 CMS垃圾收集器详细介绍

原文地址:https://www.cnblogs.com/Serenity1994/p/12466925.html