性能监控与调优

*性能监控与调优

概述
  为什么要调优?
    1.防止出现OOM
    2.解决OOM
    3.减少Full GC出现的频率
  监控的依据:
    1.运行日志
    2.异常堆栈
    3.GC日志 
    4.线程快照
    5.堆转储快照
 性能优化的步骤:
    1.(发现问题)性能监控
        GC频繁
        cpu load过高
        OOM
        内存泄露
        死锁
        程序响应时间较长
    2.(排查问题)性能分析:开发环境下进行
        打印GC日志,通过GCviewer或者http://gceasy.io分析日志信息
        灵活运用命令行工具,jstack,jmap,jinfo等
        dump出堆文件,使用内存分析工具分析文件
        使用阿里Arthas,或jconsole,JVisualVM来实时查看JVM状态
        jstack查看堆栈信息
    3.(解决问题)性能调优
        适当增加内存,根据业务背景选择垃圾回收器
        优化代码,控制内存使用
        增加机器,分散节点压力
        合理设置线程池数量
        使用中间件提高程序效率,比如缓存,消息队列等
        其他....
 性能评价/测试指标
        1.停顿时间(或响应时间):提交请求和返回该请求响应之间使用的时间,一般比较关注平均响应时间【主要关注】
            垃圾回收环节中暂停时间(STW):执行垃圾收集器时,程序的工作线程被暂停的时间
        2.吞吐量【主要关注】
           单位时间内完成的工作量
           在GC中:运行用户代码的时间占总运行时间的比例(总运行时间=程序运行时间+内存回收时间)-XX:GCTImeRatio=n  1-1/(1+n)                    
        3.并发数
           同一时刻,对服务器有实际交互的请求数
        4.内存占用
           java堆区所占内存大小
        5.相互间关系
           高速公路为例
*命令行工具
   1.jps(Java Process Status)显示指定系统内所有的Hotspot虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程
      jps[options][hostid]
      options:
        -q:只看pid
        -l:多了完整的路径包名
        -m:输出虚拟机进程启动时传递给主类main()的参数
        -v:列出虚拟机进程启动时的jvm参数 例如Xms100m
        以上参数可以综合使用,如果某java进程关闭了默认开启的UsePerfData参数(-XX:-UsePerfData),那么jps就无法使用
     hostid:
        如果想要远程监控主机上的java程序,需要安装jstatd
     
   2.jstat(JVM Statistics Monitoring):查看jvm统计信息,用于监视虚拟机各种运行状态信息的命令行工具,他可以显示本地或者远程虚	   拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据 
         jstat -<options> [-t] [-h<lines>] <wind> [<interval>[<count>]]
         options:
            类装载相关的:
               -class:显示ClassLoader的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
            垃圾回收相关的:
               -gc:显示与GC相关堆信息
               -gccapacity:与-gc基本相同堆各个区域使用到的最大、最小空间
               -gcutil:与-gc基本相同,已使用空间占总空间的百分比
               -gccause:与-gc基本相同,重点导致gc原因
               -gcnew:显示新生代GC状况
               -gcnewcapacity:显示内容与gcnew基本相同,主要关注最大最小空间
               -gcold:显示老年代GC状况
               -gcoldcapacity:显示内容与-gcold基本相同,输出主要关注使用到的最大、最小空间
               -gcpermcapacity:显示永久代使用到的最大、最小空间 
               
            JIT相关的:
               -compiler:显示JIT编译器编译过的方法、耗时等信息
               -printcompilation:输出已经被JIT编译的方法
            
         interval参数:用于指定输出统计数据的周期,单位为毫秒。即:查询间隔(每隔多少打印一次)
         count参数:用于指定查询的总次数
         -t参数:可以在输出信息前加上一个Timestamp列,显示程序的运行时间。
         -hn参数:可以在周期性数据输出时,输出多少行数据后输出一个表头信息 n为每隔几个输出表头
  3.jinfo(Configuration Info for java):查看虚拟机配置参数信息,也用于调整虚拟机的配置参数
       查看:
           jinfo -sysprops PID   一些系统的设置
           jinfo -flags PID      查看曾经赋过值的一些参数
           jinfo -flag 具体参数PID 查看某个java进程的具体参数的值(垃圾收集器等UseParallelGC)
       修改:(并非所有都可以修改,只有被标记为manageable的flag可以被实时修改)
           针对boolean类型  :jinfo -flag [+|-]具体参数 PID
           针对非boolean类型:jinfo -flag 具体参数=具体参数 PID

       拓展:
            java -XX:+PrintFlagsInital 查看所有JVM参数启动的初始值
            java -XX:+PrintFlagsFinal  查看所有JVM参数的最终值
            java -XX:+PrintCommandLineFlags 查看那些已经被用户或者JVM设置过的详细的XX参数的名称和值

  4.jmap(JVM memory Map):导出内存映像文件&内存使用情况,一方面是获取dump文件(堆转储快照文件,二进制文件),它还可以获取目标						   Java进程的内存相关信息,包括java堆各区域的使用情况、堆中对象的统计信息、类加载信息等
       基本语法:
          jmap [options] <pid>
          jmap [options] <executable <core>>
          -dump :生成Java堆转储快照:dump文件  -dump:live 只保存堆中的存活对象【关注什么数据导致内存溢出等等】
                  手动方式:
                      jmap -dump:format=b,file=<filename.hprof> <pid>
                      jmap -dump:live,format=b,file=<filename.hprof> <pid>
                  自动方式(Dump之前会触发一次Full GC,在报OOM的时候自动生成一个dump文件)
                      -XX:+HeapDumpOnOutOfMemoryError
                      -XX:HeapDumpPath=<filename.hprof	>
          -heap :输出堆空间的详细信息,包括GC的使用、堆配置信息,以及内存的使用信息等
                  jmap -heap pid
                  jmap -histo pid
          -histo:输出堆中对象的统计信息,包括类、实例数量和合计容量; -histo:live只统计堆中的存活对象
          -permstat:永久代信息
          -finalizerinfo:显示在F-Queue 中等待Finalizer线程执行finalize方法的对象
          -F :-dump没有任何响应,可以使用这个强制生成dump文件

  5.jhat(JVM Heap Analysis Tool):jdk自带堆分析工具,与jamp搭配使用,jhat内置了一个http/HTML服务器,生成dump文件分析结果
 								jhat在jdk9、jdk10中删除了,官方建议使用VisualVM

 
  6.jstack(JVM Stack Trace):打印JVM中线程快照,用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪),可用于定位线程出现							  长时间停顿的原因。
  
  7.jcmd:多功能命令行,可以用来除了jstat之外所有命令的功能。比如:用它来导出堆、内存使用、好看java进程、导出线程信息、执行GC、		   JVM运行时间等。
          jcmd -l 列出所有JVM进程 替换jps
          jcmd pid help 针对指定进程,列出支持的所有指令
          jcmd pid 具体命令  显示指定进程的指令命令的数据 
  8.jstatd:远程主机信息收集,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd服务器将本机的java应用程序信			息传递到远程计算机。       
*JVM监控及诊断GUI工具
  一、概述
     JDK自带的工具:
        jconsole
        VisualVM
        JMC
     第三方:
        MAT
        JProfiler
        Arthas
        Btrace
        
  二、jConsole
      用于对JVM中内存、线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监控工具,在cmd输入jconsole
      三种连接方式:
         Local:连接本地运行的JVM
         Remote:使用URL通过RMI连接器连接到一个JMX代理
         Advanced:   
  三、Visual VM
       多合一的故障诊断和性能监控的可视化工具。可以显示虚拟机的进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC
       堆、方法区及线程的信息(jstat、jstack)等,可以代替JConsole。(https://visualvm.github.io/index.html下载)
       也可以支持插件(https://visualvm.github.io/pluginscenters.html)
  
  浅堆与深堆
     浅堆:是指一个对象所消耗的内存。引用类型跟具体值无关
     深堆:是指对象的保留集中所有的对象的浅堆大小之和。
          保留集:对象A的保留集指当对象A被垃圾回收后,可以释放的所有的对象的集合(包括A本身),即对象A的保留集可以被认为是只能通					 过对象A被直接访问到的所有对象的集合。
          对象实际大小:一个对象所能触及的所有对象的浅堆大小之和
          【对象的大小=对象头(要看是多少位计算机)+对象数据+对齐偏移】
  支配树(Dominator tree):源自图论
     体现了对象实例间的支配关系,所有指向B的路径都经过对象A,则认为对象A支配对象B
  内存泄漏:
     理解与分类:
        可达性分析算法判断对象是否是不再使用的对象,JVM误以为对象还在使用无法回收而造成内存泄漏,宽泛意义上在实际情况很多时候一		 些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致OOM,也可以叫做宽泛意义上的“内存泄漏”。
            是否还被使用? 是  是  否
            是否还被需要? 是  否  否
             内存泄露     否  是  否
      
      内存泄漏与内存溢出的关系?
         1.内存泄露
             申请了1024M内存,分配了512内存一直不回收,那么可以用的内存就只有512,仿佛泄露了一部分。【占着茅坑不拉屎】
         2.内存溢出:
             申请内存时没有足够内存可以用。一个厕所三个坑,有两个占着茅坑不走的(内存泄漏),剩下一个坑来了两个人,坑位不够,内存       		 泄漏变成内存溢出了。 
      
      泄露的分类
         经常发生:发生内存泄漏的代码会被执行多次,每次执行,泄露一块内存
         偶然发生:在某些特定情况下才会发生
         一次性:发生内存泄露的方法只会执行一次
         隐式泄露:一直占着不释放,直到执行结束;严格的说这个不算内存泄露,以为最终释放掉了,但是如果执行时间特别长,也可能会导				 致内存耗尽。
         
     java中内存泄漏的8种情况
         1.静态集合类:HashMap、LinkedList等等,如果这些容器为静态的,那么他们的生命周期与jVM程序一致,则容器中的对象在程序			结束之前将不能被释放。就是长生命周期的对象持有短生命周期对象的引用导致不能回收
            代码:
               public class MemoryLeak{
                  static List list = new ArrayList();
                  public void oomTests(){
                     Object obj = new Object();
                     lsit.add(obj);
                  }
               }
         2.单例模式:和静态导致内存泄露的原因类似,因为单例的静态特性,他的生命周期和JVM的生命周期一样长,所以如果单例对象如果			 		  持有外部对象的引用,那么这个外部对象也不会被回收,那么就会造成内存泄漏。
         3.内部类持有外部类:如果一个外部类的实例对象的方法返回了一个内部类的实例对象。这个内部类对象被长期引用了,即使那个外部						   类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成					      内存泄露。
         4.各种连接,如数据库、网络连接和IO连接等:数据库不再使用时,需要调用close方法来释放与数据库的连接。只有连接被关闭后垃											 圾回收器才会回收对应的对象,否则如果在访问数据库的过程中,对Connection、												 Statement或ResultSet不显性地关闭,将会造成大量的对象无法被回收,导致泄漏             代码:
                 public static void main(String[] args){
                    try{
                         Connection conn = null;
                         Class.forName("com.mysql.jdbc.Driver");
                         conn = DriverManager.getConnection("url","root","123");
                         Statement stmt = conn.createStatement();
                         ResultSet rs = stmt.executeQuery("select * from student");
                    }catch(Exception e){
                    
                    }finally{
                         //1.关闭结果集 Statement
                         //2.关闭声明的对象 ResultSet
                         //关闭连接  Conn.close
                    }
                 }
         5.变量不合理的作用域:一般而言,一个变量的定义的作用范围大于其适用范围,很有可能会造成内存泄露,另一方面,如果没有及时							地把对象设置为null,很有可能会导致内存泄露
             代码:
                public class UsingRandom{
                   private String msg;
                   public void receiveMsg(){
                       readFromNet();//从网络中接收数据保存到msg
                       saveDB();//把msg	保存到数据库中
                   }
                }
            如上代码,通过readFromNet方法把接收的消息存到变量msg中,然后调用saveDB方法把msg的内容保存到数据库中,此时msg已			经没用了,由于msg的生命周期与对象的生命周期相同,此时msg不能被收回,因此造成了内存泄露。 实际上可以把msg放到					receiveMsg方法内,方法使用完就可以回收了,还有一种方法就是在使用完msg之后,把msg设置为null这样垃圾回收器也会回收
            
         6.改变哈希值:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算的哈希值字段了,否则对象修改后的					 哈希值与最初存储进HashSet集合中的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用						 作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也导致无法从HashSet集合中单独删除当前对						象,造成内存泄露【总结:如果属性参与了哈希值的计算,在存入集合之后就不能对属性进行修改,如果没有则可以】
         7.缓存泄露:一旦你把对象放入缓存中,他就很容易遗忘。对于这个问题可以改成弱引用(WeakHashMap)代表缓存,此种Map的特点是				   当除了自身有对Key的引用外,此key没有其他引用那么此map会自动丢弃此值
         8.监听器和回调:如果客户端在你实现的API中注册回调,却没有显示的取消,那么就会积聚,需要确保回调立即被当作垃圾回收的最						 佳方法是只保存它的弱引用,例如将他们保存成为WeakHashMap中的键。
     内存泄漏案例分析   
          栈中弹栈时只移动了指针没有把具体值回收,事实上是还存在栈中的,所以我们需要把要弹的对象置空。
     OQL语言查询对象信息:
          SELECT * FROM java.util.Vector v

四、JProfiler:测试运行时内存占用情况(https://www.ej-technologies.com/products/jprofiler/overview.html)
      数据采样方式:
           Instrumentation重构模式:
                 这是JProfiler全功能模式。在class加载之前,JProfiler把相关功能代码写入到需要分析的class的bytecode中,对				   行的jvm有一定影响
               优点:功能强大。在此设置中,调用堆栈信息是准确的
               缺点:若要分析的class较多,则对应用的性能影响较大,CPU开销可能很高(取决于Filter的控制)。此模式一般配合Filter					使用,只对特定的类或包进行分析。
           Samping抽样模式:类似于样本统计,每隔一定时间将每个线程栈中方法栈中的信息统计出来
              优点:对CPU的开销非常低,对应用影响小(即使你不配置任何Filter)
              缺点:一些数据/特性不能提供(例如:方法的调用次数、执行时间)
      遥感监测Telemetries
      内存视图Live Memory
            内存中对象的情况:
                1.频繁创建的Java对象:死循环、循环次数过多
                2.存在大的对象:读取文件时,byte[]应该边读边写。-->如果长时间不写出的话,导致byte[]过大
                3.存在内存泄露
      堆遍历heap walker
      cpu视图cpu views
      线程视图 threads
            线程分析关注三个方面:
               1.web容器的线程最大数 比如:Tomcat的线程容量应该略大于最大并发数
               2.线程阻塞
               3.线程死锁
      监视器&锁 Monitors&locks
      
 五、Arthas:在线排查问题,无需重启;动态跟踪java代码,实时监控jvm状态(https://arthas.aliyun.com/zh-cn)
     wget https://alibaba.github.io/arthas/arthas-boot.jar
      【具体命令看官方文档】
 六、Java Mission Control:java应用程序进行管理、监视、概要分析和故障排除的工具套件
         java Flight Recorder java飞行记录器:是jmc的其中一个组件能够以极低的性能开销收集java虚拟机性能数据、
     
 七、其他
    1.火焰图(Flame Graphs)
      在追求极致性能的场景下,了解你的程序运行过程中cpu在干什么很重要,火焰图就是一种非常直观的展示cpu在程序整个生命周期过程中时		间分配的工具。
    2.Tprofiler:可以将TPS(Transactions Per Second 每秒事务数量)由2.5提升到20,并准确定位系统瓶颈 
    3.Btrace:为java提供安全可靠的动态跟踪分析工具。
*JVM运行时参数  
     一、JVM参数选项类型
        标准参数选项:比较稳定,以-开头,java -help可以看到所有的标准参数选项
                  java -version
                   -server
                   -client
        -X参数选项:非标准化,功能稳定,以-X开头,java -X可以看到所有参数选项
            	 -Xms<size>  等价  -XX:InitialHeapSize
            	 -Xmx<size>  等价  -XX:MaxHeapSize
            	 -Xss<size>  等价  -XX:ThreadStackSize  java线程堆栈大小
            	 -Xmixed  
            	 -Xint
                 
        -XX参数选项:非标准化参数,使用的最多的类型,以-XX开头,用于开发和调试jvm
             分类:
                Boolean类型:-XX:+/-<option>  表示启用/禁用
                      -XX:-UseParallelGC
                      -XX:+UseG1GC    表示启用G1收集器
                      -XX:+UseAdaptiveSizePolicy 自动选择年轻代区大小和相应的Survivor区比例
                非Boolean:
                      数值型 -XX:<option>=<number>
                            -XX:NewSize=1024m
                            -XX:GCTimeTRatio=19
                      非数值型:-XX:<name>=<string>
                            -XX:HeapDumpPath=/usr/local/heapdump.hprof
             特别地: -XX:+PrintFlagsFinal:输出所有参数的名称和默认值,默认不包括Diagnostic和Experimental的参数
             
      二、添加JVM参数选项:
            IDEA :Run configuration
            运行jar包:java -Xms20m -Xmx20m demo.jar
            通过Tomcat运行war包:在catalina.bat中添加类似如下配置 set"JAVA_OPTS=-Xms20m -Xmx20m"
            程序运行过程中(比较少):使用jinfo -flag <name>=<value> <pid>设置非Boolean参数类型;
                                使用jinfo -flag [+|-]<name> <pid>设置Boolean类型参数
      三、常用JVM参数:
             1.打印设置的XX选项及值
                  		-XX:+PrintCommandLineFlags 可以让程序运行前打印出用户手动设置或者JVM自动设置的xx选项
                  		-XX:+PrintFlagsInitial      表示打印出所有xx选项的默认值
                  		-XX:+PrintFlagsFinal        表示打印出XX选项在运行程序时生效的值
                  		-XX:+PrintVMOptions         打印JVM参数 	
             2.堆、栈、方法区等内存大小设置
               			栈大小:Xss128k
               			堆:-Xms -Xmx.........
               			方法区:永久代和元空间。永久代:-XX:PermSize=256m....元空间:-XX:MetaspaceSize 初始空间大小
             3.OutOfMemory相关的选项
               			-XX:+HeapDumpOnOutOfMemoryError  表示内存出现OOM的时候,把Heap转存(Dump)到文件以便后续分析
               			-XX:+HeapDumpBeforeFullGC   在FUllGC之前,生成Heap转储文件
               			-XX:HeapDumpPath=<path>     指定heap转存文件的存储路径
               			-XX:OnOutOfMemoryError=/opt/server/restart.sh 指定一个可行性程序或者脚本的路径,当发生OOM的																		时候,去执行这个脚本
               			
             4.垃圾收集器相关选项
               		    查看默认垃圾收集器:jinfo -flag 相关垃圾回收器参数 进程id
               			SerialGC:client模式下使用,老年代用Serial Old GC -XX:+UseSerialGC	
               			ParNewGC:并行回收器  -XX:+UseParNewGC
               			ParallelGC:jdk8默认开启主打吞吐量,Parallel OldGC配合使用   -XX:UseParallelGC
               			CMSGC:低延迟,并发(用户线程与垃圾)  -XX:+UseConcMarkSweepGC
               			G1GC:低延迟,区域分代化  -XX:+UseG1GC	
             5.GC日志相关选项
               		常用:
               		   -verbose:gc	 输出gc日志信息,默认输出到标准输出
               		   -XX:+PrintGC  等同于-verbose:gc表示打开简化的GC日志
               		   -XX:+PrintGCDetails  在垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况
               		   -XX:+PrintGCTimeStamps  输出GC发生时的时间戳(不能独立使用与PrintGCDetails配合使用)
               		   -XX:+PrintGCDateStamps  时间戳以日期形式
               		   -XX:+PrintHeapAtGC      每一次GC前和gc后都打印堆信息,可以独立使用
                       -Xloggc:<file>          把gc日志输出到指定文件中
             6.其他参数
               		   -XX:+DoEscapeAnalysis 开启逃逸分析
      四、通过java代码获取JVM参数:
             java.lang.management包用于监视和管理java虚拟机和java运行时中的其他组件
      
      五、分析GC日志
          1.GC日志参数
               -verbose:gc	 输出gc日志信息,默认输出到标准输出
               -XX:+PrintGC  等同于-verbose:gc表示打开简化的GC日志
               -XX:+PrintGCDetails  在垃圾回收时打印内存回收详细的日志,并在进程退出时输出当前内存各区域分配情况
               -XX:+PrintGCTimeStamps  输出GC发生时的时间戳(不能独立使用与PrintGCDetails配合使用)
               -XX:+PrintGCDateStamps  时间戳以日期形式
               -XX:+PrintHeapAtGC      每一次GC前和gc后都打印堆信息,可以独立使用
               -Xloggc:<file>          把gc日志输出到指定文件中
          2.GC日志格式
               GC分类:分为两大类型:一种是部分收集(Paratial GC) ,一种是整堆收集(FUll GC)
                   部分收集:
                        1.新生代收集(Minor Gc/Young GC):只是新生代(Eden,s0,s1)的垃圾回收
                        2.老年代收集(Major GC/Old GC):只是老年代收集
                              目前只有CMS GC会单独收集老年代行为
                              注意:很多时候Major GC会和FUll GC混合使用,需要具体分辨是老年代回收还是整堆回收
                        3.混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集 目前只有G1GC会有这种行为
                   整堆收集(FullGC):收集整个java堆和方法区的垃圾回收
                       哪些情况会触发Full GC?
  							1.老年代空间不足
  							2.方法区空间不足
  							3.显示调用System.gc()
  							4.Minor GC 进入老年代的平均大小大于老年代的二用内存
  							5.大对象直接进入老年代而老年年代可用空间不足会触发Full GC
  					
                    FullGC(发生GC的原因)
  			   GC日志分类:
  			        MinorGC
  			        FullGC(新生代老年代元空间 )
  			   GC日志结构剖析:
  			        垃圾收集器
  			        GC前后情况
  			        GC时间 :user,sys和real   real小于sys+user  			       
          3.GC日志分析工具
               GCeasy
               GCViewer
               
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值