JVM虚拟机优化

本文详细介绍了JVM的运行参数,包括标准参数、非标准参数和XX参数的使用,如-Xms、-Xmx、-XX:NewRatio等,并讲解了JVM的堆内存模式,包括JDK1.7和1.8的区别。此外,还探讨了线程状态、VisualVM工具的使用和垃圾回收算法,如引用计数法、标记清除法、标记压缩算法等,并提及了各种垃圾收集器的工作原理和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM虚拟机优化

1. JVM的运行参数

​ jvm中有很多参数可以设置,这样可以使jvm在各种环境中都能够高效的运行。绝大部分的参数保持默认即可。

  1. JVM的三种参数类型

    1. 标准参数:顾名思义,标准参数中包括功能和输出的参数都是很稳定的,很可能在将来的 JVM 版本中不会改变。你可以用 java 命令(或者是用 java -help)检索出所有标准参数。
      1. -help:
      2. -version:
    2. -X参数(非标准参数):非标准化的参数在将来的版本中可能会改变。所有的这类参数都以 - X 开始,并且可以用 java -X 来检索。注意,不能保证所有参数都可以被检索出来,其中就没有 - Xcomp。例如:
      1. -Xint
      2. -Xcomp
    3. -XX参数(使用率高, 非标准参数):它们同样不是标准的,甚至很长一段时间内不被列出来(最近,这种情况有改变 ,我们将在本系列的第三部分中讨论它们)。然而,在实际情况中 X 参数和 XX 参数并没有什么不同。X 参数的功能是十分稳定的,然而很多 XX 参数仍在实验当中(主要是 JVM 的开发者用于 debugging 和调优 JVM 自身的实现)。 例如:
      1. -XX:newSize
      2. -XX:+UserSerialGC
  2. 标准参数:

    1. java -help

    2. -server 和 -client参数

      可以通过-server或-client设置jvm的运行参数。

      1. 他们的区别是Server VM的初始堆内存会大一点,默认使用的是并行垃圾回收器启动慢运行快
      2. Client VM相对来讲会更加保守,初始堆内存会小一点,使用串行垃圾回收器启动快运行慢
      3. JVM启动的时候会根据硬件和操作系统自动选择Server还是Client的JVM
        1. 32位操作系统
          1. 如果是Windows系统,不论硬件配置如何,都默认使用Client类型的VM
          2. 如果是其他操作系统,机器配置有2GB以上的内存同时有2个以上的CPU默认使用Server模式,否则Client模式。
        2. 64位操作系统
          1. 只支持Server模式,使用-client会不生效。
      [root@node test]java -client -showversion TestJVM
      java version "1.8.0_141"
      
  3. -X参数:

    [root@node test] java -X
        -Xmixed           混合模式执行 (默认)
        -Xint             仅解释模式执行
        -Xbootclasspath:<; 分隔的目录和 zip/jar 文件>
                          设置搜索路径以引导类和资源
        -Xbootclasspath/a:<; 分隔的目录和 zip/jar 文件>
                          附加在引导类路径末尾
        -Xbootclasspath/p:<; 分隔的目录和 zip/jar 文件>
                          置于引导类路径之前
        -Xdiag            显示附加诊断消息
        -Xnoclassgc       禁用类垃圾收集
        -Xincgc           启用增量垃圾收集
        -Xloggc:<file>    将 GC 状态记录在文件中 (带时间戳)
        -Xbatch           禁用后台编译
        -Xms<size>        设置初始 Java 堆大小
        -Xmx<size>        设置最大 Java 堆大小
        -Xss<size>        设置 Java 线程堆栈大小
        -Xprof            输出 cpu 配置文件数据
        -Xfuture          启用最严格的检查, 预期将来的默认值
        -Xrs              减少 Java/VM 对操作系统信号的使用 (请参阅文档)
        -Xcheck:jni       对 JNI 函数执行其他检查
        -Xshare:off       不尝试使用共享类数据
        -Xshare:auto      在可能的情况下使用共享类数据 (默认)
        -Xshare:on        要求使用共享类数据, 否则将失败。
        -XshowSettings    显示所有设置并继续
        -XshowSettings:all
                          显示所有设置并继续
        -XshowSettings:vm 显示所有与 vm 相关的设置并继续
        -XshowSettings:properties
                          显示所有属性设置并继续
        -XshowSettings:locale
                          显示所有与区域设置相关的设置并继续
    
    -X 选项是非标准选项, 如有更改, 恕不另行通知。
    
    1. -Xint,-Xcomp,-Xmixed

      1. 在解释模式(interpreted mode)下,-Xint会强制JVM执行所有的字节码,这当然会降低运行速度,通常低10倍或更多。
      2. -Xcomp参数与-Xint刚好相反,JVM会在第一次使用时候把所有字节码编译成本地代码,从而带来最大程度的优化。
        1. 然而,很多应用在使用-Xcomp也会有一些性能损失,当然比使用-Xint损失的少,原因是-Xcomp没有让JVM启用IT编译器的全部功能。JIT编译器可以对是否需要编译做判断,如果所以在代码都进行编译的话,对于一些只执行一次的代码就没有意义了。
      3. -Xmixed是混合模式,将解释模式与编译模式进行混合使用,由JVM自己决定,这是jvm的默认模式,也是推荐使用的模式。
        1. 混合使用解释器+热点代码编译
        2. 起始阶段采用解释执行
        3. 热点代码检测
      # 强制设置为解释模式
      java -showversion -Xint TestJVM
      
      # 强制设置为编译模式
      java -showversion -Xcomp TestJVM
      
      #注意,编译模式下,第一次执行会比解释模式下执行慢一些,注意观察
      
  4. -XX参数

    1. -XX参数也是非标准参数,主要用于JVM的调优和debug操作。
    2. -XX参数的使用有两种方式,一种是boolean类型,一种是非boolean类型:
      1. boolean类型:
        1. 格式:-XX:[±]表示启用或禁用属性
        2. 如:-XX:+DisableExplicitGC表示禁止手动调用gc操作,也就是说调用System.gc()无效
      2. 非boolean类型
        1. 格式:-XX:=表示属性的值为
        2. 如:-XX:NewRation=1表示新生代和老年代的比值
  5. -Xms和-Xmx参数

    -Xms与-Xmx分别是设置jvm的堆内存的初始大小和最大大小。

    -Xmx2048m:等价于-XX:MaxHeapSize,设置JVM最大堆内存为2048M。

    -Xms512m:等价于-XX:InitialHeapSize,设置JVM初始堆内存为512M。

    java -Xms512m -Xmx2048m TestJVM
    
  6. 查看JVM的运行参数

    有时候我们需要查看jvm的运行参数,这个需求可能会存在2中情况:

    第一:运行java命令式打印出运行参数;

    第二:查看正在运行的java进程的参数;

    1. 运行java命令式打印参数:-XX:+PrintFlagFinal参数即可。打印结果中参数有boolean类型和数字类型,值的操作符是=或:=,分别代表默认值和被修改的值。
    2. 查看正在运行的jvm参数:jinfo命令查看。

2. JVM的堆内存模式

  1. JDK1.7的堆内存模型

在这里插入图片描述

  1. Yong年轻代

    Young区被划分成三部分,Eden区和两个大小严格相同的Survivor区,其中,Survivor区间中,某一时刻只有其中一个是被使用的,另外一个留作垃圾收集时复制对象用,在Eden区间变满的时候,GC就会将存活的对象移到空闲的Survivor区间中,根据JVM的策略,再进过几次垃圾收集后,仍然存活于Survivor的对象将被移动到Tenured区间。

  2. Tenured年老区(代)

    Tenured区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在Young复制转移一定的次数以后,对象就会被移到Tenured区,一般如果系统中用了application级别的缓存,缓存中的对象往往会被转移到这一区间。

  3. Perm永久区

    Perm区主要保存class,method,filed对象,这部分的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到java.lang.OutOfMemoryError:PermGen space 的错误,造成这个错误的很大原因就可能是每次都重新部署,但是重新部署后,类的class没有被卸载掉,这样就造成了大量的class对象保存在perm中,这种情况下,一般重新启动应用服务器可以解决问题。

  4. Virtual区

    最大内存和初始内存的差值,就是Virtual区。

  5. JDK1.8的堆内存模型

在这里插入图片描述

由上图可以看出,jdk1.8的内存模型是由两个部分组成,年轻代+老年代。

年轻代:Eden+2*Survivor

年老代:OldGen

在jdk1.8中最大的变化就是Perm区,被Metaspace(元数据空间)替代了

需要特别说明的是:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7永久代最大的差异

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nL6pX8JI-1623774592125)(D:\My_Life\Java\学习笔记\Snipaste_2021-06-13_15-34-58.png)]

为什么要废除永久区?

在现实使用中,由于永久代内存经常不够用或发生内存泄漏,爆出异常java.lang.OutOfMemoryError:PermGen。基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。这也是为了融合Hot Spot JVM与JRockit VM而做出的努力,因为JRockit没有永久代,因此不需要配置永久代。

  1. 通过jstat命令进行查看堆内存使用情况

    jstat命令可以查看堆内存各部分的使用量,以及加载类的数量,命令格式如下:

    jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]

    1. 查看class加载统计

      # jps
      7080 jps
      6219 Bootstrap
      # jstat -class 6219
      Loaded Bytes Unloaded Bytes  Time
      3273   7122.3  0       0.0    3.98
      

      说明:

      • Loaded:加载class的数量了
      • Bytes:所占用空间的大小
      • Unloaded:未加载数量
      • Bytes:未加载占用空间
      • Time:时间
    2. 查看编译统计:jstat -compiler

      # jstat -compiler 6219
      compiled	Failed	Invalid Time	FailedType FailedMethod
      2379		1			0	8.04	1			org/apache/tomcat/util/IntrospectionUtilssetProperty
      
      • Compiled:编译数量
      • Failed:失败数量
      • Invalid:不可用数量
      • Time:时间
      • FailedType:失败类型
      • FailedMethod:失败的方法
    3. 垃圾回收统计

      # jstat -gc 6219
      S0C S1C S1U S1U EC EU 0C 0U MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
      
      • SOC: 第一个Survivor区的大小
      • S1C : 第二个Surivior区的大小
      • S0U:第一个Surivior区的使用大小
      • S1U:第二个Suriviro区的使用大小
      • EC:Eden区的大小(KB)
      • EU:Eden区的使用大小(KB)
      • OC:Old区大小(KB)
      • OU:Old使用大小(KB)
      • MC:方法区大小(KB)
      • MU:方法区使用大小(KB)
    4. jmp的使用以及内存溢出分析

      前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,如:内存使用情况的汇总,对内存溢出的定位与分析

      C:\Users\jjs>jmap -heap 5932
      Attaching to process ID 5932, please wait...
      Debugger attached successfully.
      Server compiler detected.
      JVM version is 25.91-b15
      
      using thread-local object allocation.
      Parallel GC with 4 thread(s)
      
      Heap Configuration:
         MinHeapFreeRatio         = 0
         MaxHeapFreeRatio         = 100
         MaxHeapSize              = 1073741824 (1024.0MB)
         NewSize                  = 42991616 (41.0MB)
         MaxNewSize               = 357564416 (341.0MB)
         OldSize                  = 87031808 (83.0MB)
         NewRatio                 = 2
         SurvivorRatio            = 8
         MetaspaceSize            = 21807104 (20.796875MB)
         CompressedClassSpaceSize = 1073741824 (1024.0MB)
         MaxMetaspaceSize         = 17592186044415 MB
         G1HeapRegionSize         = 0 (0.0MB)
      
      Heap Usage:
      PS Young Generation
      Eden Space:
         capacity = 60293120 (57.5MB)
         used     = 44166744 (42.120689392089844MB)
         free     = 16126376 (15.379310607910156MB)
         73.25337285580842% used
      From Space:
         capacity = 5242880 (5.0MB)
         used     = 0 (0.0MB)
         free     = 5242880 (5.0MB)
         0.0% used
      To Space:
         capacity = 14680064 (14.0MB)
         used     = 0 (0.0MB)
         free     = 14680064 (14.0MB)
         0.0% used
      PS Old Generation
         capacity = 120061952 (114.5MB)
         used     = 19805592 (18.888084411621094MB)
         free     = 100256360 (95.6119155883789MB)
         16.496143590935453% used
      
      20342 interned Strings occupying 1863208 bytes.
      
    5. 查看内存中对象数量和大小:jmap -histo | more

      查看活跃对象:jmap -histo | more

      • B byte
      • C char
      • D double
      • F float
      • I int
      • J long
      • Z boolean
      • [ 数组,如[I表示int[] ] ]
      • [L+类名 其他对象]
    6. 将内存使用情况dump到文件中

      有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对他进行分析,jmap也是支持dump到文件中的

      jmap -dump:format=b,file=dumpFileName <pid>
      

      我们将jvm的内存dump到文件中,这个文件是一个二进制的文件,不方便查看,我们可以借助jhat工具查看

      jhat -port 9999 /tmp/dump.dat
      

      打开浏览器本地9999端口进行查看

      或者我们也可以使用MAT工具进行查看

3. 线程

  1. 线程状态

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rSyz6Umg-1623774592127)(D:\My_Life\Java\学习笔记\Snipaste_2021-06-13_16-17-22.png)]

    ​ 在java中线程的状态一般分为6种:

    • 初始态(NEW)
      • 创建一个Thread对象,但还未调用start()启动线程时,线程处于初始态
    • 运行态(RUNNABLE),在Java中,运行态包括就绪态和运行态
      • 就绪态:
        • 该状态下线程已经获得执行所需要的所有资源,只要CPU分配执行权就能运行
        • 所有就绪态的线程存放在就绪队列
      • 运行态
        • 获得CPU执行权,正在执行的线程
        • 由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程
    • 阻塞态(BLOCKED)
      • 当一条正在执行的线程请求某一资源失败的时候,就会进入阻塞状态
      • 在Java中,阻塞态专指请求锁失败时候进入的状态
      • 有一个阻塞队列存放所有阻塞态的线程
      • 处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入到就绪状态,等待执行
    • 等待态(WAITING)
      • 当前线程中调用wait,join函数时,当前线程就会进入等待态
      • 也有一等待队列存放所有的等待态线程
      • 线程处于等待态表示他需要等待其他线程的指示才能继续运行
      • 进入等待状态的线程会释放CPU执行权,并释放资源(如:锁)
    • 超时等待态(TIMED_WAITING)
      • 当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时候,就会进入该状态
      • 它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒
      • 进入该状态后释放CPU执行权和占有的资源
      • 与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁
    • 终止态(TERMINATED)
      • 线程执行结束后的状态

4. VisualVM工具的使用

Visual VM能够监控线程,内存情况,查看方法的CPU时间和内存中的对象,已被GC的对象,反向查看分配的堆栈(如100个string对象分别由哪几个对象分配出来的)

  1. 监控本地tomcat

  2. 监控远程tomcat:借住JMX技术实现

    1. JMX是一个为应用程序,设备,系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台,系统体系结构和网络传输协议,灵活的开发无缝集成的系统,网络,和服务管理应用。
  3. 想要监控远程tomcat,就需要在远程的tomcat中进行对JMX的配置:

    在 tomcat 的 catalina.bat 中添 加如下参数:
    set JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port="9004" -Dcom.sun.management.jmxremote.authenticate="false" -Dcom.sun.management.jmxremote.ssl="false"
    
    其中-Dcom.sun.management.jmxremote:允许启用JMX远程管理
    其中-Dcom.sun.management.jmxremote.port=9004 指定了 JMX 启动的代理端口;这个端口就是 Visual VM 要连接的端口
    其中-Dcom.sun.management.jmxremote.authenticate ="false" JMX 是否启用身份认证
    其中-Dcom.sun.management.jmxremote.ssl ="false" 指定了 JMX 是否启用ssl
    

    保存退出,使用VisualVM工具进行远程连接。

5. 垃圾回收

​ 在C/C++语言中,没有自动垃圾回收机制,是通过new关键字申请内存资源,通过delete关键字释放内存资源。如果,程序员在某些位置没有写delete进行释放,那么申请的对象一直占用内存资源,最终可能会导致内存溢出。

​ 为了让程序员更专注于代码的实现,而不用过多的考虑内存释放的问题,所以,在java语言中,有了自动的垃圾回收机制,也就是我们熟悉的GC。有了垃圾回收机制后,程席员只零要关心内存的由请即可,内存的释放电系统自动识别完成。换句话说,自动的垃圾回收的算法就会变得非常重要了,如果因为算法的不合理,导致内存资源一直没有释放,同样也可能会导致内存溢出的。
​ 当然,除了Java语言,C#、Python等语言也都有自动的垃圾回收机制。

  1. 垃圾回收的常见算法

    ​ 自动化的管理内存资源,垃圾回收机制必须要有一套算法来进行计算,哪些是有效的对象,哪些是无效的对象,对于无效的对象就要进行回收处理。
    ​ 常见的垃圾回收算法有:引用计数法、标记清除法、标记压缩法、复制算法、分代算法等。

5.1 引用计数法

  1. 原理:假设有一个对象A,任何一个对象对A的引用,那么对象A的引用计数器+1,当引用失败时,对象A的引用计数器就-1,如果对象A的计数器的值为0,就说明对象A没有引用了,可以被回收。
  2. 优缺点
    1. 优点:
      1. 实时性较高,无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。
      2. 在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember 错误。
      3. 区域性:更新对象的计数器时,只是影响到该对象,不会扫描全部对象。
    2. 缺点:
      1. 每次对象被引用时,都需要去更新计数器,有一点时间开销。
      2. 浪费CPU资源,及时内存够用,仍然在运行时进行计数器的统计。
      3. 无法解决循环引用的问题(两个对象相互引用彼此,则两个对象的计数器始终不为零,即使两者都为零也无法被清除)

5.2 标记清除法

标记清除算法,是将垃圾回收分为2个阶段,分别是标记和清除。

  • 标记:从根节点开始标记引用的对象。
  • 清除:未被标记引用的对象就是垃圾对象,可以被清除。

原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J0jnBzKW-1623774592128)(D:\My_Life\Java\学习笔记\Snipaste_2021-06-14_22-27-01.png)]

​ 这张图代表的是程序运行期间所有对象的状态,他们的标记位全是0(即未标记)。假设这会儿有效内存空间耗尽了,JVM会停止应用程序的运行,并开启GC线程,然后开始进行标记工作,根据跟随搜索算法,标记完后,对象状态如下图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tfffAkWB-1623774592128)(D:\My_Life\Java\学习笔记\Snipaste_2021-06-14_22-29-50.png)]

​ 可以看到,根据跟搜索算法,所有从root可达的对象就会被标记为存活的对象,此时已经完成了第一阶段标记。接下来,就要执行第二阶段清除了,清除结束后,剩下的对象以及对象的状态如下图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EPPqOvJI-1623774592129)(C:\Users\MitsuiYe\AppData\Roaming\Typora\typora-user-images\image-20210614223413262.png)]

​ 可以看到,没有被标记的对象将会被回收清除掉,而被标记的对象将会留下,而且会将标记位归零。唤醒停止的线程继续运行。

​ 优点:解决了循环引用的问题。

​ 缺点:

  1. 效率较低,标记和清除两个动作都需要遍历所有对象,并且在GC时,需要停止应用程序。
  2. 通过标记清除算法清理出来的内存,碎片化非常严重,因为被回收的对象可能存在于内存的每个角落,所以导致清理出来的内存不连贯。

5.3 标记压缩算法

​ 标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。

在这里插入图片描述

​ 优点:解决了标记清除算法的碎片化问题。

​ 缺点:增加了对象移动内存位置的步骤,其效率也有一定的影响。

5.34 复制算法

​ 复制算法的核心就是,将原有的内存空间一分为二,每次只用其中的一块,在垃圾回收时,将正在使用的对象复制到未被使用内存空间中,然后将在使用的内存空间清空,交换两个内存的角色,完成垃圾的回收。
在这里插入图片描述

​ 如果内存中的垃圾对象较多,需要复制的对象就较少,这种情况下适合使用该方法,否则不适用。

  1. JVM中年轻代内存空间

在这里插入图片描述

  1. 在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。

  2. 紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。

  3. 经过这次GC后,Eden区和From区已经被清空。这个时候,"From”和“To”会交换他们的角色,也就是新
    的“To”就是上次GC前的"From”新的“From”就是上次GC前的"To”。不管怎样,都会保证名为To的Survivor区域是空的。

  4. GC会一直重复这样的过程,直到“T”区被填满,"To”区被填满之后,会将所有对象移动到年老代中

  5. 优缺点

    1. 优点:
      1. 在垃圾对象多的时候,效率较高
      2. 清理后,不会存在内存碎片
    2. 缺点
      1. 垃圾对象少的时候,不适用,例如:老年代内存
      2. 分配两块内存空间,在同一时刻只能使用一个,内存使用率较低

5.5 分代算法

​ 根据回收对象的特别进行选择,例如:年轻代中使用复制算法,老年代中使用标记算法或标记压缩算法。

5.6 垃圾收集器和内存分配

  1. 分类

    1. 串行垃圾收集器
    2. 并行垃圾收集器
    3. CMS(并发)垃圾收集器
    4. G1垃圾收集器
  2. 串行垃圾收集器

    ​ 串行垃圾收集器,是指使用单线程进行垃圾回收,垃圾回收时,只有一个线程在工作,并且java应用中的所有线程都要暂停,等待垃圾回收的完成。这种现象称之为STW(Stop-The-World)。
    ​ 对交互性较强的应用而言这种垃圾收集器是不能够接受的,一般在avaweb应用中是不会采用该收集器的。

    1. 设置垃圾回收器为串行收集器,需要在程序运行参数中添加两个参数

      -XX:+UseSerialGC:指定年轻代和老年代都是用串行收集器
      -XX:+PrintGCDetails:打印垃圾回收的详细信息			
      
    2. GC日志信息解读
      年轻代的内存GC前后的大小:

    3. DefNew:表示使用的是串行垃圾收集器

    4. 4416K->512K(4928K):表示,年轻代GC前,占有4416K内存,GC后,占有512K内存,总大小4928K

    5. 0.0046102secs:表示,GC所用的时间,单位为毫秒

    6. 4416K->1973K(15872K):表示,GC前,堆内存占有4416K,GC后,占有1973K,总大小为15872K

    7. Full GC:表示,内存空间全部进行GC

  3. 并行垃圾收集器

    ​ 并行垃圾收集器在串行垃圾收集器的基础之上做了改进,将单线程改为了多线程进行垃圾回收,这样可以缩短垃圾回收的时间。(这里是指,并行能力较强的机器)
    ​ 当然了,并行垃圾收集器在收集的过程中也会暂停应用程序,这个和串行垃圾回收器是一样的,只是并行执行,速度更快些,暂停的时间更短一些。

    1. ParNew垃圾收集器

      ParNew垃圾收集器是工作在年轻代上的,只是将串行的垃圾收集器改为了并行。

      通过-xX:+UseParNewGC参数设置年轻代使用ParNew回收器,老年代使用的依然是串行收集器。

    2. ParallelGC垃圾收集器

      1. ParallelGC收集器工作机制和ParNewGC收集器样,只是在此基础之上,新增了两个和系统吞吐量相关的参数,使得其使用起来更加的灵活和高效。
      2. 相关参数如下:
        1. -XX:+UseParallelGC:年轻代使用ParallelGC垃圾回收器,老年代使用串行回收器。
        2. -XX:+UseParallelOldGC:年轻代使用ParallelGC垃圾回收器,老年代使用ParallelOldGC垃圾回收器。
        3. -XX:MaxGCPauseMillis:设置最大的垃圾收集时的停顿时间,单位为毫秒
          需要注意的时,ParallelGC为了达到设置的停顿时间,可能会调整堆大小或其他的参数,如果堆的大小设置的较小,就会导致GC工作变得很频繁,反而可能会影响到性能。该参数使用需谨慎。
  4. GMS垃圾收集器

    CMS是一款并发的,使用标记清除算法的垃圾回收器,该回收期是针对老年代垃圾回收的,通过参数-XX:+UseConcMarkSweepGC进行设置。它的执行过程如下图所示:

在这里插入图片描述

  • 初始化标记(CMS-initial-mark)标记root,会导致stw;
  • 并发标记(CMS-concurrent-mark),与用户线程同时运行:
  • 预清理(CMS-concurrent-preclean),与用户线程同时运行;重新标记(CMS-remark),会导致stw;
  • 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
  • 调整堆大小,设置设置CMS在清理之后进行内存压缩,目的是清理内存中的碎片;
  • 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;
#设置启动参数
 -xx:+UseConcMarkSweepGC-xx:+PrintGCDetails -Xms16m -Xmx16m3
4 #运行日志
5
[Gc(A1location Failure) [ParNew: 4926K->512K(4928K),0.0041843 secs] 9424K->6736K(15872K),0.0042168 secs] [Times: user=0.00 sys=0.00, rea1=0.00 secs]6
#第一步,初始标记
8
[Gc(CMs Initia1 Mark) [1CMs-initial-mark: 6224K(10944K)]6824K(15872K),0.0004209 secs] [Times: user=0.00 sys=0.00, rea1=0.00 secs]
9 #第一步,并发标记
10
[CMS-concurrent-mark-start]11
[CMS-concurrent-mark:0.002/0.002 secs! [Times: user=0.00 sys=0.00, rea1=0.00 secs]12 #第三步,预处理
13
[CMS-concurrent-preclean-start]14
[CMS-concurrent-preclean:0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
15 #第四步,重新标记
16
[GC(CMS Final Remark)[YG occupancy: 1657 K(4928 K)][Rescan (para1le1) ,0.0005811 secs][weak refs processing0.0000136 secs][c1ass unloading,0.0003671 secs][scrub symbo1 table,0.0006813 secs][scrub string table, 0.0001216 secs][1 CMS-remark:6224K(10944K)]7881K(15872K)0.0018324 secs] [Times: user=0.00 sys=0.00real=0.00
 secs]
17 #第五步,并发清理
18 [CMS-concurrent-sweep-start
19
[CMS-concurrent-sweep:0.004/0.004 secs] [Times: user=0.00 sys=0.00, rea1=0.00 secs]#第六步,重罟
20
21
[CMS-concurrent-reset-start]22
[CMS-concurrent-reset:0.000/0.000 secs] [Times: user=0.00 sys=0.00, rea1=0.00 secs]23

由以上日志信息,可以看出CMS执行的过程
  1. G1垃圾回收器

    ​ G1垃圾收集器是在jdk1.7中正式使用的全新的垃圾收集器,orace官方计划在jdk9中将G1变成默认的垃圾收集器,以替代CMS。
    G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:

    1. 开启G1垃圾收集器

    2. 设置堆的最大内存

    3. 设置最大的停顿时间

      G1中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC,在不同的条件下被触发。

    4. 原理

      ​ G1垃圾收集器相对比其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域。
      ​ 这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

    在这里插入图片描述

    		在G1划分的区域中,年轻代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者 Survivor空间,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。
     	这就意味着,在正常的处理过程中,G1完成了堆的压缩(至少是部分堆的压缩),这样也就不会有cms内存碎片问题的存在了。
    

    ​ 在G1中,有一种特殊的区域,叫Humongous区域。如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是一个巨型对象,这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。
    ​ 为了解决这个问题,G1划分了一个Humongous区,它用来专门存放巨型对象。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

    1. Young GC
      YoungGC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。

      • Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。
      • Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。
      • 最终Eden空间的数据为空GC停止工作,应用线程继续执行。
        在这里插入图片描述
      1. Remembered Set(已记忆集合)

        在GC年轻代的对象时,我们如何找到年轻代中对象的根对象呢?
        根对象可能是在年轻代中也可以在老年代中,那么老年代中的所有对象都是根么?
        如果全量扫描老年代,那么这样扫描下来会耗费大量的时间。
        于是,G1引进了RSet的概念。它的全称是RememberedSet,其作用是跟踪指向某个堆内的对象引用

在这里插入图片描述

     ​		每个Region初始化时,会初始化一个RSet,该集合用来记录并跟踪其它Region指向该Region中对象的引用,每个 Region默认按照512Kb划分成多个Card,所以RSet需要记录的东西应该是xx Region的 xx Card。
  1. Mixed GC

    ​ 当越来越多的对象晋升到老年代oldregion时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即 MixedGC,该算法并不是一个OldGC,除了回收整个YoungRegion,还会回收一部分的OldRegion,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些oldregion进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是MixedGC并不是Full GC。

    ​ MixedGC什么时候触发?由参数**-XX:InitiatingHeapOccupancyPercent=n**决定。默认:45%,该参数的意思是:当老年代大小占整个堆大小百分比达到该阀值时触发。
    它的GC步骤分2步

    1. 全局并发标记(global concurrent marking)

      全局并发标记,执行过程分为五个步骤:

      1. 初始标记( initial mark , STW )
        • 标记从根节点直接可达的对象,这个阶段会执行一次年轻代GC,会产生全局停顿。
      2. 根区域扫描(root region scan)
        • G1GC在初始标记的存活区扫描对老年代的引用,并标记被引用的对象。
        • 该阶段与应用程序(非STW)同时运行,并且只有完成该阶段后,才能开始下一次STW年轻代垃圾回收。
      3. 并发标记(Concurrent Marking)
        • G1GC在整个堆中查找可访问的(存活的)对象。该阶段与应用程序同时运行,可以被STW年轻代垃圾回收中断。
      4. 重新标记(Remark,STW)
        • 该阶段是STW回收,因为程序在运行,针对上一次的标记进行修正。
      5. 清除垃圾(Cleanup,STW)
        • 清点和重置标记状态,该阶段会STW,这个阶段并不会实际上去做垃圾的收集,等待evacuation阶段来回收
    2. 拷贝存活对象(evacuation)

      Evacuation阶段是全暂停的。该阶段把一部分Region里的活对象拷贝到另一部分Region中,从而实现垃圾的回收清理。

  2. G1收集器相关参数

    -XX:+UseG1GCI #使用 G1垃圾收集器
    
    -XX:MaxGCPauseMillis #设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到),默认值是200毫秒。
    
    -XX:G1HeapRegionSize=n #设置的G1区域的大小。值是2的幂,范围是1MB到32MB之间。目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000
    
    -XX:ParallelGCThreads=n #设置STW工作线程数的值。将n的值设置为逻辑处理器的数量。n的值与逻辑处理器的数量相同,最多为8.
    
    -XX:ConcGCThreads=n #设置并行标记的线程数。将n设置为并行垃圾回收线程数(ParalleGCThreads)的1/4左右。
    
    -XX:InitiatingHeapOccupancyPercent=n #设置触发标记周期的Java堆占用率阈值。默认占用率是整个Java堆的45%。
    
  3. 关于G1垃圾收集器的优化建议

    1. 年轻代大小
      • 避免使用-Xmn选项或XX:NewRatio等其他相关选项显式设置年轻代大小。
      • 固定年轻代的大小会覆盖暂停时间目标。
    2. 暂停时间目标不要太过严苛
      • G1GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
      • 评估G1GC的吞吐量时,暂停时间目标不要太严苛。目标太过严苛表示您愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量。
  4. 可视化GC日志分析工具

    1. 日志输出参数
      前面通过**-XX:+PrintGCDetails**可以对GC日志进行打印,我们就可以在控制台查看,这样虽然可以查看GC的信息,但是并不直观可以借助第三方方的GC日志分析工具GCEasy进行查看。

    在日志打印输出涉及到的参数如下

     -XX:+PrintGC #输出GC日志
     -Xx:+PrintGCDetai1s #输出GC的详细日志
     -xx:+PrintGCTimeStamps #输出GC的时间戳(以基准时间的形式)3
     -xx:+PrintGCDateStamps #输出GC的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800) 
     -Xx:+PrintHeapAtGC #在进行GC的前后打印出堆的信息
     -x1oggc:../logs/gc.1og #日志文件的输出路径
    

    测试:

    -Xx:+UseG1GC
    -XX:MaxGCPauseMi11is=100
    -Xmx256m
    -XX:+PrintGCDetails 
    -xx:+PrintGCTimeStamps 
    -xx:+PrintGCDateStamps 
    -XX:+PrintHeapAtGC 
    -xloggc:F://test//gc.1og
    

    运行后就可以在E盘下生成gc.log文件。将文件上传gceasy网站即可进行分析。

  5. 日志输出参数
    前面通过**-XX:+PrintGCDetails**可以对GC日志进行打印,我们就可以在控制台查看,这样虽然可以查看GC的信息,但是并不直观可以借助第三方方的GC日志分析工具GCEasy进行查看。

    在日志打印输出涉及到的参数如下

     -XX:+PrintGC #输出GC日志
     -Xx:+PrintGCDetai1s #输出GC的详细日志
     -xx:+PrintGCTimeStamps #输出GC的时间戳(以基准时间的形式)3
     -xx:+PrintGCDateStamps #输出GC的时间戳(以日期的形式,如2013-05-04T21:53:59.234+0800) 
     -Xx:+PrintHeapAtGC #在进行GC的前后打印出堆的信息
     -x1oggc:../logs/gc.1og #日志文件的输出路径
    

    测试:

    -Xx:+UseG1GC
    -XX:MaxGCPauseMi11is=100
    -Xmx256m
    -XX:+PrintGCDetails 
    -xx:+PrintGCTimeStamps 
    -xx:+PrintGCDateStamps 
    -XX:+PrintHeapAtGC 
    -xloggc:F://test//gc.1og
    

    运行后就可以在E盘下生成gc.log文件。将文件上传gceasy网站即可进行分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值