005-Java垃圾回收GC和JVM性能调优

Java知识点总结系列目录

1. 垃圾回收
005-Java垃圾回收GC和JVM性能调优_第1张图片

1.1 对象是否需要回收的两种算法

  • 引用计数法

    为每个对象添加一个引用计数器,用来统计指向该对象的引用个数。一旦某个对象的引用计数器为0,则说明该对象已经死亡,便可以被回收了

    如果有一个引用,被赋值为某一对象,那么将该对象的引用计数器+1。如果一个指向某一对象的引用,被赋值为其他值,那么将该对象的引用计数器 -1

    引用计数法是一种古老的方式,除了需要额外的空间来存储计数器,以及繁琐的更新计数器以外;引用计数法还有一个重大的漏洞:无法处理相互引用,如图中Object 6和Object 7的情况

  • 可达性分析法

    将一系列被称为GC Roots的变量作为初始的存活对象合集,然后从该合集出发,所有能够被该集合引用到的对象,并将其加入到该集合中,而不能被该合集所引用到的对象,并可对其宣告死亡。是现代虚拟机采用的方式。

    GC Roots是一些由堆外指向堆内的引用,如线程本地变量表中的引用变量,静态变量和本地方法栈中的变量等

    如图中Object 1-4位需要保留的对象,Object 5-7则没有GC Roots类的引用可达,告诉GC可以进行垃圾回收

1.2 分代回收
005-Java垃圾回收GC和JVM性能调优_第2张图片
上图为默认堆内存的逻辑构成。总体分为新生代和老年代两块区域,默认比例为 新:老=1:2。新生代又分为Eden,Survivor From和Survivor To三个区域,默认比例为 Eden:S0:S1=8:1:1

  • Minor/Young GC

    Minor/Young GC也就是普通最频繁的GC发生在Eden和Survivor From区。Survivor From和Survivor To两块区域的角色将会发生调换,谁空谁是To。采用的算法就是复制交换算法。

    详细过程:
    1)新new的对象分配在Eden区域
    2)当Eden占满时发生GC。根据可达性分析法发生GC后,将需要保留的对象复制到Survivor From区域,并将保留的对象的分代年龄加1(对象的分代年龄保存在对象头中的Mark Word中),然后将Eden区域的对象全部回收销毁。
    2) 再一次GC时将Eden和Survivor From作为回收区域,将需要保留的对象复制到Survivor To区域,每次幸存下来分代年龄都将加1,然后将Eden和From区域的对象全部回收
    3)From和To互换角色,To区域永远是Survivor中空的那一块区域。

  • Full/Major GC

    Full/Major GC发生在老年代中,垃圾回收时将伴随着STW(Stop the World)。过程中将会暂停其他线程的运行,时间相对比较长,应尽量避免发生此类型的GC。

    当分代年龄达到15(默认值,可以修改)时,对象将被放入老年代。除了分代年龄达到限制的对象被放入老年代,还有一些大对象(年轻代放不下,或者说超过了S0一半空间大小)将会直接放入老年代。当老年代空间被占满时将发生Full/Major GC。

    老年代回收算法采用标记清除

2. JVM性能调优

  • 目标

    JVM调优的目标是用较小的内存占用来获得较高的吞吐量或者较低的延迟。通过以下几个指标来进行查看分析

    1)内存占用:程序正常运行需要的内存大小。
    2)延迟:由于垃圾收集而引起的程序停顿时间。
    3)吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。

    结合实际场景,找到性能瓶颈进行分析来达到程序的目标。这里有篇博文可以参考一下,里面还有一些实例分析https://www.cnblogs.com/csniper/p/5592593.html

  • 常见关注点

    JVM参数设置上的常见关注点

    1)在设置程序堆内存的最大(-Xmx)最小(-Xms)值是尽量设置成一样,防止Full GC提前和JVM动态调整堆大小浪费系统资源(防止内存抖动)。

    2)新生代尽量设置大些,让对象尽量在新生代中存活时间长点,防止或延迟对象进入老年代的机会,以减少应用程序发生Full GC的频率(减少STW-Stop the World影响用户体验)。当然这个设置还是需要根据实际的代码进行分析,可以根据垃圾回收部分的内容进行分析设置

    3)如果对时延要求较高的服务,用户线程不允许长时间的停顿的业务场景可以设置老年代的垃圾回收采用CMS收集器,通过参数“-XX:+UseConcMarkSweepGC”进行设置,此时老年代区域的空间相对比较大,新生代空间比较小。缺点是服务长时间运行,造成严重的内存碎片化

    程序实现上常见关注点

    1)避免创建过大的对象及数组:过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代,有可能导致频繁的Full GC

    2)避免同时加载大量数据,如从数据库或文件中读取数据时,可以分批读取,用完尽快清空引用

    3)当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代

    4)尽量避免长时间等待外部资源(数据库、网络、设备资源等)的情况,缩小对象的生命周期,避免进入老年代,如果不能及时返回结果可以适当采用异步处理的方式等

    5)可以在合适的场景(如实现缓存)采用软引用、弱引用,比如用软引用来为obj分配实例:SoftReference obj=new SoftReference(); 在发生内存溢出前,会将obj列入回收范围进行二次回收,如果这次回收还没有足够内存,才会抛出内存溢出的异常。

    6)避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满

  • 常用分析工具

    通过参考的数据有系统运行日志、堆栈错误信息、gc日志、线程快照、堆转储快照等信息进行分析调优。可以借助以下常用工具进行分析

    1)jps(JVM process Status)可以查看虚拟机启动的所有进程、执行主类的全名(-l)、JVM启动参数(-v)

    jps -lv
    

    2)jstat(JVM Statistics Monitoring Tool)监视虚拟机信息

    jstat -gc <pid> 500 10
    

    每500毫秒打印一次Java堆状况(各个区的容量、使用容量、gc时间等信息),打印10次

    3)jmap(Memory Map for Java)查看堆内存信息

    jmap -histo <pid>
    

    打印出当前堆中所有每个类的实例数量和内存占用,如下,class name是每个类的类名([B是byte类型,[C是char类型,[I是int类型),bytes是这个类的所有示例占用内存大小,instances是这个类的实例数量

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

    转储堆内存快照到指定文件,文件格式一般是二进制的.hprof格式文件。快照文件可以使用jhat,jvisualvm,eclipse mat等工具进行加载查看

    4)jconsole,jvisualvm 可视化的界面展示JVM的参数,内存,线程和GC等等信息

    5)jhat(JVM Heap Analysis Tool) 命令来分析内存快照

    jhat -port <port> -J-Xmx4G <filepath>
    

    以指定端口port启动 jhat 内嵌的服务器来分析.hprof二进制文件

    内存快照可以使用jmap导出,也可以使用参数“-XX:+HeapDumpOnOutOfMemory”在程序发生内存溢出时dump出当前的内存快照

    6)jstack,查看JVM线程快照。可以用来检查死锁,统计线程数量和具体分析进程中的某些线程状态。

    jstack <pid>
    
  • 常用参数

    参数 说明 实例
    Xms 初始堆大小,默认物理内存的1/64 -Xms512M
    Xmx 最大堆大小,默认物理内存的1/4 -Xms2G
    Xmn 新生代内存大小,官方推荐为整个堆的3/8 -Xmn512M
    Xss 线程堆栈大小,jdk1.5及之后默认1M,之前默认256k -Xss512k
    XX:NewRatio=n 设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 -XX:NewRatio=3
    XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/8 -XX:SurvivorRatio=8
    XX:PermSize=n 永久代初始值,默认为物理内存的1/64(JDK1.7,1.8之后为元空间-XX:MetaspaceSize) -XX:PermSize=128M
    XX:MaxPermSize=n 永久代最大值,默认为物理内存的1/4(JDK1.7,1.8之后为元空间-XX:MaxMetaspaceSize) -XX:MaxPermSize=256M
    verbose:class 在控制台打印类加载信息
    verbose:gc 在控制台打印垃圾回收日志
    XX:+PrintGC 打印GC日志,内容简单
    XX:+PrintGCDetails 打印GC日志,内容详细
    XX:+PrintGCDateStamps 在GC日志中添加时间戳
    Xloggc:filename 指定gc日志路径 -Xloggc:/data/jvm/gc.log
    XX:+UseSerialGC 年轻代设置串行收集器Serial
    XX:+UseParallelGC 年轻代设置并行收集器Parallel Scavenge
    XX:ParallelGCThreads=n 设置Parallel Scavenge收集时使用的CPU数。并行收集线程数。 -XX:ParallelGCThreads=4
    XX:MaxGCPauseMillis=n 设置Parallel Scavenge回收的最大时间(毫秒) -XX:MaxGCPauseMillis=100
    XX:GCTimeRatio=n 设置Parallel Scavenge垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) -XX:GCTimeRatio=19
    XX:+UseParallelOldGC 设置老年代为并行收集器ParallelOld收集器
    XX:+UseConcMarkSweepGC 设置老年代并发收集器CMS
    XX:+CMSIncrementalMode 设置CMS收集器为增量模式,适用于单CPU情况。

你可能感兴趣的:(Java知识点总结系列)