JAVA面试 (八) | JVM性能监控与调优

Scroll Down

背景

在我们开发阶段无法用到,但是到了生产环境,程序出问题不可能每次都重启服务,这个时候就需要我们对程序进行监控以及性能调优

Jvm

  • jps
  • jstat
  • jstatd
  • jmc

一、Jvm参数类型

  • 标准类型 (基本不变)
    • -help
    • -server client
    • -version -showversion
  • X参数类型 (非标准化参数)
    • -Xint 解释执行
    • -Xcomp 第一次使用就编译成本地代码
    • -Xmixed 混合模式,JVM自己决定是否便以本地代码
  • XX参数类型 (不稳定、主要用在调优)
    • -XX:[+-] 表示启用或者禁用name属性
    • -XX:= 例如 -Xmx

Jstat查看JVM统计信息

Jstat pid
  • 类装载
    • 例如:jstat -class 52122 1000 10
  • 垃圾收集
    • 例如:jstat -gc 52122 1000 10
  • JIT编译 (编译的一些信息)
    • 例如:jstat comoiler pid

Jvm内存结构

image.png

二、内存溢出

2.1、种类

  • 堆内存溢出
    实现原理(一直循环向全局集合中存值)
List<User> userList = Lists.newArrayList();

    @GetMapping("/heap")
    public void heap(){
        int i = 0;
        while (true) {
            userList.add(new User(i++, UUID.randomUUID().toString()));
        }
    }

image.png

  • 非堆内存溢出

2.2、内存溢出自动导出

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./

2.3、jmap命令手动导出

2.4、MAT查看内存泄漏

  • 对象数量
  • 对象内存

三、使用JVisualVM可视化工具监控本地或者远程的java进程

详细请见第六篇Jvm调优

注意

其中连接远程只是添加主机,但是监控某一个进程就需要修改tomcat的Catalina.sh文件
image.png

四、基于Btrace监控调优

  • 运行的程序不需要重启情况下注入最终代码动态修改字解码
  • JavaComplierApi、JVMTI、Agent、Instrumentation+ASM

4.1、安装

  • 新建环境变量 BTRACE_HOME 路径
  • 添加Path:%BTRACE_HOME%\bin

4.2、使用

  • 编写一个Btrace类
  • 进入该目录输入指令:btrace pid 文件名.java
    image.png

4.3、拦截方法

  • 普通方法@OnMethod(class="", method="")
  • 构造函数@OnMethod(class="", method="")

4.4、拦截时机

  • Kind.ENTRY:入口、默认
  • Kind.RETURN:返回
  • Kind.THROW:异常
  • Kind.Line:行

注意

  • 只能本地运行
  • 生产环境可以使用、但修改后字节码不会被还原

五、Tomcat性能调优

5.1、步骤

  • 启动添加脚本
-agentlib:jdwp=transport=dt_socket,adress=54321,server=y,suspend=n
  • IDEA 或者Eclipse添加一个remote启动方式

5.2、tomcat-manager监控

  • 文档:docs/manager-howto.html
  • 步骤:
    • 1、conf/tomcat-users.xml 添加用户
      image.png
    • 2、新建conf/Catalina/localhost/manager.xml配置允许远程连接
      image.png
    • 3、重启
      image.png

5.3、PSI监控

5.4、tomcat优化

  • 内存优化
  • 线程优化(doc/config/http.html)
    • maxConneions
    • acceptCount
    • maxThreads
    • minSpareThreads
  • 配置优化
    • autoDeploy设置为true(文档:docs/config/host.html)
    • enableLookups(文档:docs/config/http.html)
    • reloadable生产设置false,可以查看class和静态文件变化
    • protocol( 文档:conf/server.xml)
    • session:(jsp页面,应该禁用session)

六、nginx优化

6.1、安装

6.2、原生连接信息监控连接信息(ngx_http_stub_status)

location = /nginx_status {
	stub_status on;
	access_log off;
	allow 127.0.0.1;
	deny all;
}

6.3、ngxto监控请求信息

6.4、nginx-rrd图形化监控

6.5、nginx优化

  • 增加工作线程数和并发连接数
    image.png
  • 启用长连接
    image.png
  • 启用缓存、压缩
    (提高性能)
    image.png
  • 操作系统优化
    • 关于tcp连接协议的优化
      image.png
    • 进程打开文件限制的设定
      image.png
    • 其他优化
      image.png

七、JVM调优

7.1、JVM内存结构

  • 运行时数据区
    image.png
  • 程序计数器
  • 虚拟机栈:线程的堆栈、内存的模型
  • 堆:区间共享,存放对象实例
  • 方法区(非堆):区间共享,存放类常量编译后代码等数据
  • 产量词:存放编译期生成的各种字面量和符号应用
  • 本地方法栈
    image.png
-Xms -Xmx	最大内存	最小内存
-XX:NewSize -XX:MaxNewSize	新生代大小	新生代最大大小
-XX:NewRatio -XX:SurvivorRatio	比例
-XX:MetaspaceSize -XX:MaxMetaspaceSize	Metaspace区大小(包含下面四个大小)
-XX:+UseCompressedClassPointers	是否启用压缩类指针启用CCS,最大1G大小
-XX:CompressedClassSpaceSize	压缩类空间大小
-XX:InitialCodeCacheSize	CodeCache大小
-XX:ReservedCodeCacheSize	CodeCache最大大小

7.2、垃圾回收算法

创建对象后,一直被程序保持有占用,自动垃圾回收无法回收

  • 思想:枚举根节点,做可达性分析

  • 根节点:类加载、Thread、虚拟机本地变量表、static成员、常量引用、本地方法栈的变量等等

  • 标记清除

    • 算法:分为标记和清除两个阶段:首先标记所有需要回收的对象,再标记完成后统一回收所有
    • 缺点:效率不高,易产生碎片,碎片太多会导致没内存,提前GC
  • 标记整理

    • 算法:和标记清除一样,但后续步骤不是对对象直接清除,而是将存活的对象向一端移动,然后直接清理边界外的内存
    • 缺点:没有内存碎片,但比较耗时
  • 复制算法

    • 算法:他可将内存划分大小相同两块,每次只使用其中一块,当一块用完了,会将存活的对象复制到另一块上,然后把已使用的内存一次清理掉
    • 优缺点:实现简单,当空间利用效率低
  • 分带垃圾回收

    • Young区使用复制算法
    • Old区使用标记清除和标记整理
  • 对象分配

    • 对象优先在Eden区分配
    • 大对象直接进入老年代:-XX:PretenureSizeThreshold
    • 长期存活的对象进入老年代:- XX:MaxTenuringThreshold
      -XX:+PrintTenuringDistribution -XX:TargetSurcicorRatio

7.3、垃圾收集器

种类

  • 串行收集器Serial:Serial、Serial Old。单线程收集器
      • XX:+UseSerialGC -XX:+UseSerialOldGC
  • 并行收集器Parallel:Paraller Scavenge、Parallel Old、吞吐量
    指多条垃圾及线程并行工作,但此时用户线程仍然处于等待状态。适合科技计算、后台处理等弱交互场景
    • 吞吐量优先
      • XX:+UseParallelGC -XX:+UseParallelOldGC
    • Server模式下默认收集器
  • 并发收集器:Concurrent:CMS、G1、停顿时间
    指用户线程和垃圾收集线程同时执行(但不一定是并行,也可能会交替执行),垃圾收集线程在执行时候不会停顿用户程序的运行,适合对响应时间有要求的场景,如web
    • 响应时间优先
    • CMS:XX:UseConcMarkSweepGC -XX:+UseParNewGC
    • G1:-XX:+UseG1GC

停顿时间VS吞吐量

  • 停顿时间:垃圾收集器做垃圾回收时候中断应用执行的时间
    -XX:MacGCPauseMillis
  • 吞吐量:垃圾回收的时间和应用时间的占比
    -XX:GCTimeRatio=,垃圾收集时间占比1/1+n

垃圾收集器搭配

image.png

如何选择收集器

  • 优先调整堆大小让服务器自己选择
  • 如果内存小于100M,使用串行收集器
  • 如果是单核,没有停顿时间要求,串行或者JVM自己选择
  • 如果允许停顿超过1s,选择并行或者Jvm自己选择
  • 如果响应时间重要,并且不能超过1S,使用并发收集器

并行收集器Parallel Collector

  • -XX:useParallelGC 手动开启,Server默认开启
  • -XX:ParallelGCThreads=多少个GC线程
    CPC > 8 N=5/8
    CPU < 8 N=CPU
    • XX:MaxGCPauseMillis=
  • -XX:GCTimeRatio=
  • -XMx

动态内存调整

  • -XX:YoungGenerationSizeIncrement=
  • -XX:TenuredGenerationSizeIncrement=
  • -XX:AdaptiveSizeDecrementScaleFactor=

CMS垃圾收集过程

1、CMS initial mark:初始标记Root,STW
2、CMS concurrent mark:并发标记
3、CMS-concurrent-preclean:并发预清理
4、CMS remark:重新标记,STW
5、CMS concurrent sweep:并发清除
6、CMS concurrent reset:并发重置

  • 缺点
    • CPU敏感(CPU占用)
    • 浮动垃圾
    • 空间碎片
  • 相关参数
    • -XX:ConcGCThreads:并发GC线程数
    • -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩
    • -XX:CMSFullGCsBeforeCompaction:多少次FullGC后压缩一次
    • -XX:CMSInitiatingOccupancyFraction:处罚FullGC
    • -XX:+UseCMSInitiatingOccupancyOnly:是否动态调
    • -XX:+ CMSScavengeBeforeRemark:FullGC之前先做YGC
    • -XX:+CMSClassUnloadingEnabled:启用回收perm区

G1垃圾收集

新生代和老生代的收集器

YoungGC

  • 新对象进入Eden区
  • 存活对象拷贝到Survivor区
  • 存活时间达到年龄阀值,就存到Old区

7.4 可视化GC理智分析工具

  • 在线工具:http//gceasy.io/
  • GCViewer

7.7、GC调优步骤

根据日志里面信息调参数

  • 打印GC日志
  • 根据日志得到关键性性能指标
  • 分析GC原因,调优JVM参数

实战GC调优

image.png

paralleGC调优

  • 除非确定,否则不要设置最大堆内存
  • 优先设置吞吐量
  • 如果吞吐量不达标,调大最大内存,不能让OS设置Swap,如果还不达标,则降低目标
  • 吞吐量能达标,GC时间过长,设置停顿时间目标
    image.png

G1 GC调优

image.png

关于MixGC调优

image.png

八、字节码指令

ii区别

  • 区别:
    先引用再+1,++i先+1再赋值引用

字符串拼接+

每次循环时候,每次都需要new Builder

Try-Finally

会将变量拷贝两份,一个用于返回,一个用于finally赋值

public static String f1() {
	String str = "hello"
try {
	return str;
} finally {
	str = "imooc"
}
}

九、常用代码优化

  • 尽量重复使用对象,不要循环创建对象
  • 容器初始化指定长度
  • ArrayList随机遍历快,LinkList添加删除快
  • 集合遍历尽量减少重复计算
  • 遍历Map使用Entry
  • 大数组拷贝尽量使用system.arraycopy
  • 尽量使用基本类型,而不是包装类型
  • 不要手动system.gc
  • 及时消除过期对象的引用,防止内存泄漏
  • 尽量使用局部变量,减少变量的作用域
  • 尽可能用非同步的容器ArrayList和Vector(内部加锁)
  • 尽量减少同步作用范围,synchronized方法块VS代码块、
  • 尽量延迟加载
    // 单例,由JVM类加载机制加载,不引用类不会加载,线程安全
    image.png
  • 少用反射,改成第一次使用反射,后面使用缓存取
  • 尽量使用连接池、线程池、对象池、缓存
  • 及时释放资源、IO流、socket、数据库连接
  • 慎用异常,不要用抛异常来表示正常业务逻辑
  • String尽量少用正则表达式。例:replace VS replaceAll
  • 日志输出注意不同的级别
  • 日志参数拼接使用占位符