Java性能优化指南,及唯品会的实战
如果无法正常显示,请先停止浏览器的去广告插件。
1. Java性能优化指南,
及唯品会的实战
肖桦(江南白衣)
唯品会资深架构师
2017.thegiac.com
www.top100summit.com
2. ABOUT ME
70后,喜欢编码的架构师
唯品会平台架构部
服务化框架,容器云平台
SpringSide 春天的旁边
3. WRONG THINGS
RIGHT THINGS
JIT & MICRO BENCHMARK
TOOLS
PRACTICES
4. 01
WRONG THINGS
从过时的经验开始说
5. 互联网时代的Java优化经验
For many years, one of the top 3 hits on Google for “Java Performance
Tuning” was an article from 1997.8, the page housed advice that was
completely out of date
-- 两位大大<Optimizing Java> 开篇的吐槽
Java演进凡二十年,不同时代的信息全堆到网上
-- 缺乏 失效机制
谁都可以分享自己的经验,然后被搬运网站转载,再被搜索引擎展示
-- 缺乏stackoverflow式 纠错机制
6. 过时的经典语录 - 设为NULL
将用完的变量设为NULL,能加快回收?
-- No, JDK 比你想象的更好
有效的优化:
• 对象定义贴近使用它的逻辑分支
(scanned by Sonar)
7. 过时的经典语录 – Getter/Setter
(初级版)直接访问对象属性,能提升性能?
减少函数调用的消耗? -- No, JIT 方法内联
(高级版) -XX:+UseFastAccessors ?
-- No , Only for JDK6
还有拿已是默认值的来骗感情,如指针压缩
[生产环境同版java] [应用完整参数] -XX:+PrintFlagsFinal –version | grep [待查参数]
8. 过时的经典语录 - final
(初级版)函数参数设为 final,能提升性能?
-- No,已经没人能说出为什么了
(中级版) 不变对象,能加快GC跨代扫描速度?
如果老生代对象的属性老变,就会指向新生代中的对象?
-- YES,但只在乎属性实际变没变,不在于final定义
(高级版)类设为final,能有助内联?
JIT想为调用者内联方法时,不知道该内联哪个子类的方法,
设成final能提示没有子类了?
-- No,JDK自己有类层次分析(CHA)
9. 不盲信,多看代码 - slf4j
logger.info(“Hello {}”,name);
有魔术吗? -- MessageFormatter类
for (int i = 0; i < args.length; i++) {
j = messagePattern.indexOf(“{}”, i);
……
}
没有预编译,输出每条日志, 都要循环查找“{}”,
然后subString取出“Hello”, 最后拼接参数name
如果日志确定输出,自己直接拼接字符串更快
10. 不盲信,多做试验 – 魔幻参数
-XX:ParGCCardsPerStrideChunk=32K
第一次出现在LinkedIn工程师的博客,
大家不明觉厉,先抄为敬
Twitter试验说, 8K 才是合适的
俄国哥们(这个参数的发明者)的测试,4K 最优
难道他们说的都是真的?,视乎应用的新老生代特征
11. 02
靠谱的
人和书
看代码,看代码
12. 可靠的书
其他如某Java调优圣经,10年前了
13. 可靠的人
R大 笨神
RednaxelaFX,莫枢 原阿里JVM
原Azul JVM ,现Databricks 公众号:你假笨
知乎上的1368个回答 小程序:JVMPocket
JavaEye上的N个帖子
知乎:R大是谁
(JVM参数锦囊)
14. 最可靠的,是代码
遇到问题,会否看 com.sun.*, 与C写的native方法,
是一个 分水岭
根据tags下载生产环境同款的小版本
http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/
http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/
15. 03
微基准测试你的优化
先懂一点JIT
然后知道微基准测试的必要
16. JIT浅说
JIT = 动态编译 + 优化
JIT后,Java并不比C慢
JNI 调 C方法,不用于加速
17. JIT – Code
文本源码 机器直接执行的机器码
*.java Java
*.py 原生支持,内存中
*.php Python 某些非官方版本
PHP
鸟哥在捣鼓
编译
02
01
JIT
03
VM解析执行的字节码
*.class
*.pyc
opCache (php5.5+)
18. JIT – Compiler
C1 编译器 C2 编译器 JDK8 多层编译 JDK9 AOT
立即编译 采集1万次 启动时,C1编译 启动前,静态
静态轻量优化 方法调用样本后 样本足够,C2编译 编译*.so文件
动态深度优化 样本足够,JIT
未深度优化 1万次前 刚启动时, 试验阶段
及温热方法, 狂吃CPU
执行慢 更多停顿
19. JIT – 方法内联
public int a(int count)
{
count = count*2;
return b(count);
}
private int b(int count
{
return count+1;
}
public int a(int count) {
count = count*2;
return count+1;
}
内联条件
第一次访问:-XX:MaxInlineSize=35 Byte
频繁的访问:-XX:FreqInlineSize=325 Byte
每个方法只有几行的 优雅代码 最多18层内联,及其他条件….
的基础
Getter/Setter 无需优化的原因 可视化工具: JITWatch
20. JIT – 逃逸分析
分析对象有否逃逸出当前线程,当前方法
1. 同步消除 2. 标量替换
如果实际只有一条线程访问对象 局部对象只在方法内被使用
消除对象方法的Synchronized
例外:StringBuffer,因内联父
类失败
R大说: 不是传说中的栈上分配对象
而是只创建对象的属性
堆内存 线程栈内存
Java对象 局部原子变量/引用
方法结束即弹栈,没有GC
A a = new A();
1;
a.b = 1;
int b =
21. JIT – 代码优化
JVM工程师十多年的积累与骄傲所在, 一些人肉优化变得多余
无用代码消除
循环优化
空值检测消除
数组边界检查消
除
公共子表达式消
JIT优化项一览(2009)
除
……
for (int i=0; i <10000; i++)
{
int x =str.indexOf(c);
}
变量x上下文没有作用
整个循环被消除
22. 测试一切-微基准测试的要点
没有测试数据证明的论断,都是 可疑 的。
一个简单的main()测试,也是 不可靠 的。
01 02 03
预热 防止无用 避免干扰
代码消除
触发JIT的调用次数
后台编译所需的时间
GC
生成测试数据的消耗
23. 简化基准测试编写- JMH
像JUnit那样,编写测试代码,标注annotation,JMH完成剩下一切
运行预热循环
与被测函数交互防止代码消除
数据准备接口
迭代前主动GC
并发线程控制
结果统计
上例对比两种SecureRandom生成算法的性
24. 04
工具
先定位问题,再解决问题
25. 应用缓慢的众多原因
SQL慢占了大头,所以先定位问题,别急着优化Java代码
26. Profiler的两大分类
Instrumentation-based Sampling-based
AOP式在方法前后植入代码 Thread Dump式采样
JProfiler的Instrument模式 统计Stack Trace上的方法
能准确统计每个方法的 只能统计方法的热度,
调用次数,耗时 相对百分比
性能成量级的衰退, 性能损耗 <
结果无意义
植入代码导致方法膨胀, 可在生产环境运行
无法内联
10%
Instrumentation模式只适合监控少量方法
27. Sampling的两大分类
ThreadDump AsyncGetCallTrace
JProfiler的Sampling模式 Java Mission Control
JVMTop ….. Honest Profiler …..
JvmtiEnv::GetAllStackTraces()
ThreadMXBean.dumpAllThreads()
Hostpot隐藏API
有停顿 无停顿
不可用于延迟敏感应用 采样间隔可低至10ms
JMC:JDK7 u40 以上自带,注意License
对热身后的JVM采样,定制化settings文件,-XX:+DebugNonSafepoints
28. 在线排查 - BTrace
BTrace是神器,每一个需要每天解决线上问题,
但完全不用BTrace的Java工程师,都是可疑的。
- by 凯尔文。肖
通过自己编写的脚本,attach进应用获得一切信息
不再需要修改代码,加上System.out.println()
然后重启,然后重启,然后 重启!!!
只要定义脚本时不作大死,可在 生产环境 打开
29. BTrace – 典型的场景
1. 服务偶发慢,找出慢在哪一步?
2. 下列情况发生时,上下文是什么?
什么情况下进入了这个处理分支?
谁调用了System.gc() ?
谁构造了一个超大的ArrayList ?
打印实现了OspFilter接口的Filter链中,执行时间超过了1毫秒的Filter
执行命令:./btrace <pid> HelloWorld.java
30. TBJMap and DIY
排查对象缓慢泄漏时,临时对象的干扰太大
我可以只看老生代的对象统计吗?
-- TBJMap
我可以只看Survivor区 age>3的对象统计吗?
-- Attach JVM 后,DIY,少年
hotspotAgent.attach(pid);
//暂停应用
CollectedHeap heap = VM.getVM().getUniverse().heap();
hotspotAgent.detach();
//恢复应用
31. 05
实践
应用停顿排查
性能调优案例
32. STOP THE WORLD
Java程序员最头痛的事情
学名:安全点
33. GC
YGC
停顿时间与GC后剩下对象的多少,成 正比 关系
让持久对象尽快晋升:降低晋升阈值, Cache对象预热阶段主动GC
CMS GC
对两个STW阶段,讲究个见招拆招,如Remark前先YGC等
JDK9因找不到人维护,可能放弃
G1
JDK9默认,8G 以上Heap建议
重新踩坑,重新学习
ZGC
最终效果类似Azul Zing的C4
大如2TB 的Heap,停顿只在 10毫秒 以下
34. 停顿时间
GC的停顿, 不止是垃圾收集
-- 等待所有线程停止的时间,等待GC日志写入的时间
[DefNew:… [Times: user=0.29, sys=0.00,real=0.015 secs]
Total time for which application threads were stopped: 1.021 seconds
JVM的停顿,不止是GC
-- JIT,Class Redefinition(AOP),取消偏向锁,Thread Dump…
35. 停顿时间的查看
GC日志的应用停顿时间:
必需品,获得大部分JVM记得打印的停顿
-XX:+PrintGCApplicationStoppedTime
jHiccup:
获得实际的停顿时间
包含底层操作系统对JVM的影响
安全点日志:
http://gceasy.io:
获得非GC的停顿原因等细节
在线分析GC日志
36. 一些意外的GC停顿
无数种的可能,八卦网上每一篇实战分析
使用了Swap
vm.swappiness = 0
写GC日志被锁
日志写到 /dev/shm目录
(内存文件系统)
抢不到CPU
限制非业务进程的CPU
减少GC线程数
写/tmp/hsperfdata被
锁
关闭pefdata,用 JMX 代替 jstat
37. 调优案例
各种思路的典型,随便聊聊
38. 面向GC编程 - int vs Integer
更少内存,更少GC
4 bytes vs 16 bytes
Why Integer?
Java对象最小 16 bytes 能表达Null语义
12 bytes的固定header 集合/泛型需要
避免无谓转换
int<->Integer的自动转换是有代价的,尽量一致
Integer缓存数组,默认缓存-128~127
-XX:AutoBoxCacheMax=20000后,应用整体提升
4% QPS
39. 面向GC编程 – Netty的优化
AtomicIntegerFieldUpdater
写法当有海量对象,海量数字属性时:
n * int + 静态的updater VS n * AtomicInteger
IntObjectHashMap
1. int[] keys, V[] values的数据结构
2 开发地址法 vs 链表法
不能更省的数据结构, 微基准测试性能提升 50%
40. 并发与锁 – 无锁
锁,是应用慢的Top3原因
CAS 是lock-free,不是wait-free
竞争写时有等待, 有消耗
Atomic* 系列
ConcurrentLinkedQueue
ThreadLocal
不变对象
不修改对象属性
直接替换为新对象
CopyOnWriteArrayList
脑洞大开
ThreadLocalRandom 普通线程池,有锁的任务队列
很多非线程安全类的处理 Netty EventLoop:
N个容量为1的线程池
任务随机分配给一个池
TLAB, Heap内存分配的避免冲突
41. 应用局部异常时的性能-Exception StackTrace
异常的StackTrace很有用,但构造的消耗很大
出处明确时,取消StackStrace
别取消我的StackStrace
绕过调fillStakTrace()的构造函数 大量重复的JDK Exception会被优化
1. Netty的静态异常 但日志滚动后,就看不到NPE的出处
2. Cloneable异常 -XX:-OmitStackTraceInFastThrow
平衡 - 性能与问题排查便捷性
42. Fast Enough,不求极致-反射
反射好用,很多框架类库在用, 但会慢么?
Java 是一门以Fast Enough为设计理念的语言
Method对象的获取,慢
缓存起来反复使用, Dozer vs Apache BeanUtils
Method的调用,快
开始,基于NativeAccessor
15次后,生成GeneratedMethodAccessorXXX类,
bytecode直调实际方法
43. 好工具库
不要过早优化,不要过细的优化,但….
一直按好的习惯来编程, 很多时候没有额外的成本
把实践都封装起来, 让业务开发 默认 就获得 最优 的性能
公司级别的工具库
直接使用: Apache Commons Lang 与 Google Guava
参考吸收:
-大厂工具库:Facebook JCommon,twitter commons,linkedin-utils等
-开源项目中的工具库:ElasticSearch,Cassandra,Netty等
44. 广告时间
公众号:春天的旁边
唯品会即将开源的VJTools项目
45. 扫码关注GIAC公众号
2017.thegiac.com
www.top100summit.com