java性能优化指南


Performance Tuning Guide 1.4 Java By 江南白衣 70后,喜欢编码的架构师
 ABOUT ME 才子词人,自是白衣卿相。
 青春都一饷。忍把浮名,换了浅斟低唱。 --柳永《鹤冲天》 唯品会平台架构部 SpringSide 春天的旁边 BASIC RULES TUNING JVM TUNING CODE TOOL BOX CONCENTS BASIC RULES01 Don’t Trust it, Test it
 
 Show me the code 简单即正义 Java已发展十几年,不同版本的新旧信息全堆在一起。 谁都可以把自己的理解放到网上,然后被各种
 网站转载,转载。。。但缺少一个纠错的机制。 这份幻灯片也会有同样的过时或有失偏颇的命运。 网上的信息很丰富 ? 函数参数设为 final 能提升性能 不使用getter/setter,直接访问属性提升性能 设置+XX:+UseFastAccessors 提升getter性能 将变量设为Null能加快内存回收 一些过时的经典语录 可靠资料推荐 那些老外大能 
 http://java-performance.info/ 笨神 你假笨,寒泉子,阿里JVM
 公众号:你假笨 毕玄 Bluedavy,阿里 公众号:HelloJava R大 RednaxelaFX,莫枢, Azul JVM
 JavaEye,知乎
 
 知乎:R大是谁 基本原则 Don’t Trust it, Test it Show me the code 简单即正义 怀疑一切,微基准测试一切 阅读JDK的代码,阅读一切的代码 优化不优化? 测试一切-微基准测试的要点 防止JIT消除 无用代码 
 for(…) { int i =str.indexOf(f); } 如果循环内的代码对上
 下文都没有作用 01 预热 
 JIT编译优化
 触发JIT的调用次数 后台编译的时间
 
 避免其他干扰
 测试数据生成的时间
 如调用random()
 GC ,除非也要测它 02 03 没有测试数据证明的论断,都是 可疑 的。 一个简单的main(),也是 不可靠 的。 简化基准测试编写- JMH 微基准的要点 
 运行预热循环
 与被测函数交互防止无用代码 数据准备接口
 迭代前主动GC
 并发线程控制 
 同步开始与结束的时间 结果统计与打印 
 QPS,响应时间,分位数响应时间
 监控GC,热方法等 Introduction to JMH Profilers 像Junit那样,编写测试代码,标注annotation,JMH完成剩下一切 
 http://openjdk.java.net/projects/code-tools/jmh/ 使用JMH进行微基准测试:不要猜,要测试! - 译by 张洪亮 JMH Code Example - 比较SecureRandom两种实现的性能 JMH Result Example # Warmup Iteration 1: 0.057 ±(99.9%) 0.003 ms/op …
 # Warmup Iteration 5: 0.053 ±(99.9%) 0.002 ms/op 
 Iteration 1: 0.062 ±(99.9%) 0.002 ms/op
 …
 Iteration 10: 0.057 ±(99.9%) 0.004 ms/op Histogram, ms/op: [ 0.000, 5.000) = 3997132 [ 5.000, 10.000) = 3938 [10.000, 15.000) = 1074 ….. Native 2946835 count
 Native·min ≈ 10⁻³ ms/op Native·p0.50 0.001 ms/op Native·p0.9999 6.824 ms/op Native·max 278.397 ms/op 
 SHA1 6151975 count SHA1·min ≈ 10⁻⁴ ms/op SHA1·p0.50 0.002 ms/op SHA1·p0.9999 6.298 ms/op SHA1·max 391.459 ms/op Benchmark Show me the code - JDK源码 JDK的代码,其实比很多开源项目 工整,易读 遇到问题,是否看 com.sun.*, 与C写的native方法,是一个 分水岭 翻源码的例子: SecureRandom的江湖偏方与真实效果
 Netty SSL性能调优 根据tags下载对应小版本的zip包 http://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/ http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/ Slf4j的故事 logger.info(“Hello {}”,name); 好失望,循环查找,拼接再加上各种兼容处理,比自己拼字符串慢一截。 有魔术吗? 我得去学学 -- MessageFormatter类 for (L = 0; L < argArray.length; L++) { j = messagePattern.indexOf(“{}”, i); …… } 只有日志不确定会否输出时,才使用此经典写法。 要不要优化?-简单即正义 C P C P 避免过早优化 按正确的方式
 写代码并无额外成本 避免效果不明显
 的优化 效果由大量细微的优化
 累积而成 始终编写 清晰,直接,易理解 的代码 如果违背了简单性,则考虑 复杂度 与性能提升的 性价比 TUNNING JVM02 JIT浅说
 JVM参数 GC停顿 JDK的版本选择 Oracle JDK 大小版本总览
 https://en.wikipedia.org/wiki/Java_version_history JDK7 
 u40,集成JMC,621 bug fix
 u60,性能优化完成, 130 bug fix u80,最后一个免费版本, 104 bug fix 
 JDK8
 
 
 
 Azul Zing
 知乎:Azul Systems 是家什么样的公司
 
 超低的GC停顿时间: 无论堆多大, 不调优<10ms, 调优 <1ms 建议至少u60 建议至少u60 u20, 669 bug fix
 u40, 645 bug fix u60, 480 bug fix … u102, 118 bug fix JIT浅说 Java程序员需要知道的常识 JIT浅说 – 编译 01 02 03 Source 
 Code Byte 
 Code 文本源码 VM解析执行的字节码
 Binary 
 Code Java 原生支持 Python 某些非官方版本
 PHP 鸟哥在捣鼓 JIT-机器直接执行的二进制码 *.java
 *.py *.php *.class
 *.pyc opCache (php5.5+) JIT浅说 – 代码优化 JVM工程师十多年的积累与骄傲所在, 一些代码级的优化变得多余 函数内联 逃逸分析 更多 JIT优化项一览(2009) 对象有否逃逸出当前线程和方法
 
 1.同步消除,e.g. StringBuffer
 
 每个函数只有几行的 优雅代码 的基础
 
 R大:内联是JIT优化之母
 e.g. 没有充分内联就无法判断
 微基准测试提到的无用代码是否真正无用 无用代码消除 循环展开
 空值检测消除
 数组边界检查消除 公共子表达式消除
 …… 2.标量替换,只创建对象的属性而不是对象 A a = new A(); //堆上分配的对象 a.b = 1; ->
 int b = 1; //栈上分配的原子类型局部变量 JIT浅说 – 编译器 C1 编译器
 
 无采样,立即编译 轻量优化 C2 编译器 64位 JVM默认编译器 采集1万次调用样本后深度优化 但每次GC,计算器会衰减一半
 温热,总是达不到阀值,始终解析执行 禁止衰减:-XX:-UseCounterDecay JDK8 多层编译 
 启动时,以C1编译
 样本足够后,以C2编译 但,有时JDK8 反而比JDK7 略差? 有些函数C1编译后C2不再编译了 禁止多层编译:-XX:-TieredCompilation 让函数更容易被内联 JIT浅说 – 内联 内联条件
 
 第一次访问:-XX:MaxInlineSize=35 Byte 频繁的访问:-XX:FreqInlineSize=325 Byte 
 最多18层内联,还有其他条件…. JITWatch
 可视化JIT及内联的情况,提供优化的建议 
 https://github.com/AdoptOpenJDK/jitwatch/ 1. R大最认可的缩短的方式 :拆分不常访问的路径 if(most case){ …..
 } else {
 //e.g. 异常处理 seldomAccessPath(); } 怎样让你的代码更好的被JVM JIT Inlining 2. final关键字有助内联?No,JIT有CHA等等优化 By 戎码一生 JVM参数调优 兼顾性能,稳定性,问题排查的便捷性 JVM参数 – 原则 有没有一些好的开源例子? Cassandra的 jvm.options JDK的默认值,在小版本间不断变化,参数间互相影响 Java [生产环境参数] -XX:+PrintFlagsFinal –version | grep [待查证的参数] 反面例子:无参裸奔的ZooKeeper 确认最终值的方法: JVM参数选释 – 性能 取消偏向锁 : -XX:-UseBiasedLocking 数字对象AutoBox的缓存:-XX:AutoBoxCacheMax=20000 关键业务系统的JVM启动参数推荐 JDK的偏向锁优化,但只适用于非多线程高并发应用,禁止它 int<->Integer, 默认缓存-128~127,设置后我的应用提升4%QPS JVM参数选释 – 监控 不忽略重复异常的栈 -XX:-OmitStackTraceInFastThrow 
 OOM时打印HeapDump : -XX:+HeapDumpOnOutOfMemoryError Crash时输出Dump: -XX:ErrorFile 与 CoreDump的启用 JDK的优化,大量重复的JDK异常不再打印其StackTrace
 但如果日志滚动了,当前日志里的NPE不知如何引起的 JVM参数 – 内存大小设置 一个2G堆大小的JVM,要预留多少内存? 
 堆内存+ 线程数*线程栈 + 永久代 + 二进制代码 + 堆外内存 2G + 1000 * 1M + 256M + 48/240M + (~2G) = 5.5G (3.5G) 堆内存: 存储Java对象,默认为物理内存的1/64 线程栈: 存储局部变量(原子类型,引用)及其他,默认为1M 永久代: 存储类定义及常量池,注意JDK7/8的区别 二进制代码:JDK7与8,打开多层编译时的默认值不一样,从48到240M 堆外内存: 被Netty,堆外缓存等使用,默认最大值约为堆内存大小 GC停顿 Stop The World, Java程序员最头痛的事情 GC问题排查: 基本设置 Old GC Young GC 不要禁止: -XX:+DisableExplicitGC ( No!) 要CMS: -XX:+ExplicitGCInvokesConcurrent System.gc() CMS vs G1 ? - R大推荐 8G 为界
 G1从理论到实现已忙活六七年,细节太复杂。 -XX:MaxTenuringThreshold,默认为15 常见误解 1. YGC不会STW,关注FULL GC即可 2. 混淆Old GC 与 Full GC 停顿时间与GC后剩下对象的多少,成 正比 关系 新生代的大小:GC间隔 远大于 临时对象的生命周期,GC时只有少量在途对象 新生代的热身:把真正长久对象的升到老生代 GC问题排查: 停顿时间 1. GC的停顿, 不止是垃圾收集的时间
 2. JVM的停顿,不止是GC JIT,Class Redefinition(AOP),取消偏向锁,Thread Dump… -XX:+PrintGCApplicationStoppedTime 安全点日志-JVM的Stop The World,安全点,黑暗的地底世界 在GC日志打印一切原因的,真实的停顿时间 GC问题排查:一条正常的YGC日志 sys cpu time ≈ 0 real cpu time ≈ user cpu time / GC线程数 application stopped time ≈ real cpu time
 
 GC线程数 = 8 + ( Processor - 8 ) ( 5/8 ), e.g. 24核时为18 [Times: user=0.29, sys=0.00,real=0.015 secs] Total time for which application threads were stopped: 0.0154 seconds GC问题排查:一些意外的GC停顿 服务时延过长的三个追查方向 写入GC日志被锁? 
 
 将日志放入 /dev/shm目录
 (tmpfs内存文件系统) 写入/tmp/hsperfdata文件被锁?
 
 使用JMX代替jstat 
 -XX:+PerfDisableSharedMem 
 抢不到CPU? 
 
 检查有无其他后台辅助工具
 偶发大量消耗CPU,用cgroup限制
 使用了Swap?
 
 vm.swappiness = 1 JVM参数 – 完整例子 -XX:-UseBiasedLocking -XX:-UseCounterDecay -XX:AutoBoxCacheMax=20000 
 -XX:+PerfDisableSharedMem -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 
 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=6 
 -XX:+ExplicitGCInvokesConcurrent -XX:+ParallelRefProcEnabled 性能相关 CMS GC 相关 -Xms4096m -Xmx4096m -Xmn2048m -XX:MaxDirectMemorySize=4096m
 -XX:PermSize=256m -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=240M 内存大小相关(JDK7) JVM参数 – 完整例子(2) -Xloggc:/dev/shm/app-gc.log -XX:+PrintGCApplicationStoppedTime 
 -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:-OmitStackTraceInFastThrow -XX:ErrorFile=${LOGDIR}/hs_err_%p.log 
 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${LOGDIR}/ GC 日志 相关 异常 日志 相关 -Dcom.sun.management.jmxremote.port=${JMX_PORT} -Dcom.sun.management.jmxremote
 -Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote.authenticate=false 
 -Dcom.sun.management.jmxremote.ssl=false 
 JMX 相关 TUNING CODE03 面向GC编程 并发,并发 杂项 面向GC编程 降低内存需求 减少GC频率 面向GC的编程 面向GC的Java编程 by 沐剑 不可变对象的好处 
 老生代GC时减少扫描新生代 实际上的不变,与final无关 e.g. ArrayList, HashMap 容量不足时会成倍复制式扩容
 注意Map的加载因子 内存分配的优化
 没有魔术,每条线程在堆区划分
 TLAB来减少分配冲突 引用设置为Null的传说
 
 设为null已无用 缩小对象范围(局部 vs private) 局部对象定义贴近使用的地方 对象重用
 
 创建局部对象的成本远比想象的低
 线程池,连接池,Netty堆外内存池 Thread Safe对象,全局重用, 如JSON Factory 非Thread Safe对象,ThreadLocal重用, 如SimpleDateFormat Array Base的集合必须指定初始化大小 面向GC的编程 – 更多抠门的优化 int vs Integer 
 ArrayList vs LinkedList
 
 AtomicIntegerFieldUpdater vs AtomicInteger Netty大量使用此方法,代码非常复杂。
 海量对象,多个数字属性时:int + 静态的updater vs AtoimicInteger Array-Based的容器,直接在数组里存放对象 (4 bytes per element),而且是连续性存储的(缓存行预加载) Pointer-Based的容器,每个元素是Node对象,里面含真正对象及前后节点的指针(24 bytes per element) 4 bytes vs 16 bytes。
 Java对象最小16 bytes, 12 bytes的固定header,按8 bytes对齐 StringBuilder的故事 StringBuilder sb = new StringBuilder(); for(int i=0;i<129;i++){ sb.append("a"); } 生成 129 个字符的字符串要花多少内存? 总共需要 496+129 = 525 字符,并若干次 内存复制 StringBuilder.toString() == return new String(value, 0, count) 从默认16字符开始,append()过程中,4次扩容复制,16 + 32+ 64 +128 + 256 = 496 还没完…… StringBuilder的故事(2) StringBuilder在高性能场景下的正确用法 Liferay的StringBundler: BigDecimal的StringBuilderHolder: 完全重用同一个StringBuilder,同一个char[],
 每次重置count属性即可。 append 时先不往char[] 里添加,而是用String[] 暂存,
 最后计算一个总长度再申请char[],避免了扩容。 面向GC的编程 – Map家族 高性能场景下,HashMap的优化使用建议 原子类型集合 
 
 Large HashMap overview: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove
 
 key为原子类型
 哈希冲突从数组+链表 的 链表法
 改为双数组的开放地址法,性能与空间俱佳 遗憾,不支持并发 GuavaCache EnumMap 以枚举的oridnal()为下标来访问的数组,性能与空间俱佳 但,Map.get(Object o)接口导致多余的isValidKey()判断 支持并发的WeakReferenceHashMap 1.Holder类静态变量
 
 推荐,利用ClasssLoader加载类时的锁 面向GC的编程 – 延时初始化 3. 正确写法的DoubleCheck
 
 防止重排序,变量定义为volatile 访问时始终有判断是否为空的成本, 适用于不一定需要创建的变量 2. 枚举
 
 更好支持序列化,写法稍复杂 private static class LazyObjectHolder { static final LazyObject instance
 = new LazyObject ();
 } String.intern() 堆外内存是新大陆吗? 字符串池,将重复的字符串转化为唯一引用
 
 没有魔术,一个HashMap形式的字符串池 只适合长期存在的对象中,有海量重复的字符串 面向GC的编程 – 一些美丽的诱惑 String.inter()怯魅 申请与释放的成本大,必须 池化
 ByteBuf.bytes<-> Object的序列化 or 按位访问的黑科技 并发与锁 减少锁 减少上下文切换 并发,并发,锁 01 02 分离锁 ReadWriteLock 
 多线程并发读锁,单线程写锁 BlockingQueue 
 ArrayBlockingQueue : 全局一把锁 LinkedBlockingQueue : 队头队尾两把锁 03 synchronized 尽量短的代码块 缩短锁 但如果有多个断断续续的同步块
 可考虑合并粗化 分散锁 ConcurrentHashMap 
 LongAdder(JDK8) 代替AtomicLong
 计数时分散多个cell
 取值时将所有cell求和 分散成16把锁 JODD Cache的故事 public V get(K key) { readLock.lock(); // 读写锁,可被并发读 CacheObject co = cacheMap.get(key);
 readLock.unlock(); } public V put(K key, V object) { writeLock.lock(); CacheObject co = new CacheObject(…);
 // 循环遍历清理Key,但链表结构已被破坏 if (isReallyFull()) pruneCache(); cacheMap.put(key, co); writeLock.unlock(); } public V get(Object key) { Node e=getNode(hash(key), key) //按访问次序调整元素在链表的位置,不支持并发读
 if (accessOrder) afterNodeAccess(e); return e.value; } AbstractCacheMap LRUCache基于LinkedHashMap 无锁(lock –free) 02 ThreadLocal ThreadLocalRandom(JDK8) 01 CAS Compare And Set 自旋 Atomic* 系列 03 不可变对象 不修改对象属性
 直接替换为新对象 CopyOnWriteArrayList ConcurrentLinkedQueue SimpleDateFormat in ThreadLocal 异步日志与无锁队列实现 Logback异步日志的差劲实现
 
 ArrayBlockingQueue
 每次插入前都询问Queue剩余容量,可重写。 同步的日志是重要堵塞根源 Log4j2的三种无锁队列
 • JCToolsQueue • DistruptorQueue • LinkedTransferList JCTools Distruptor • SPSC : 单生产者单消费者队列 • MPSC: 多生产者单消费者队列 • SPMC: 单生产者多消费者队列 • MPMC: 多生产者多消费者队列 并发框架Disruptor译文 by 方腾飞 并发的其它话题(1) ThreadLocal的代价 volatile的代价 没有银弹,开放地址法的HashMap
 Netty的 FastThreadLocal, index原子自增的数组 Happen Before
 
 volatile vs Atomic*,可见性 vs 原子性 
 访问开销比同步块小,但也不可忽视 存到临时变量使用,需要确保可见性时再次赋值 并发的其它话题(2) 2. ForkJoinPool的其他优势:每线程独立任务队列,无锁 遇上阻塞,主动让出线程去做其他事情
 阻塞返回,随便拉一条线程来再续前缘 Netty的EventLoop, 同样由一组容量为1的线程池组成 Java中的纤程库 - Quasar 3. Quasar 协程:少量线程处理大量并发请求 1. 合理设置线程 内存,线程调度的消耗不可忽略 并发的其它话题(3) 了解三方包的底层 System.getProperty() –> HashTable Common Lang 3.4 FastDateFormat –> StringBuffer 其他 性能优化的其它话题(1) JNI调用C代码会比Java快吗? 匿名内部类会慢吗? 写得好的Java代码JIT后,至少是和C代码一样快的,跨越JNI边界消耗更大 是否匿名,在字节码层面没有区别 是否静态内部类,区别是函数调用时多一个this的参数 反射会慢吗? 很多框架都使用了反射,至少不要每次重新反射获取Method Dozer vs Apache BeanUtils(very slow) JNI主要用于调用操作系统的特定函数 性能优化的其它话题(2) 异常处理字符串处理 很多操作的消耗都很大 静态异常 创建异常时,获取StackTrace的代价非常大 Nety静态创建异常 The hidden performance costs of instantiating Throwables format(), split(), indexOf(), replace()
 
 字符 vs 字符串 vs 正则表达式
 Commons Lang, Guava的实现 正确的集合类型 关于Java集合的小抄 ASC vs UTF-8 为90%的场景优化算法 if(a||b) 另一个例子:带权重的随机负载均衡算法 A与B的顺序,尽量让判断条件短路 先判断权重是否一致,99%情况下是一致的
 直接使用不带权重的简单随机算法 for each语法糖的故事 for(String name :list){ } for (Iterator i=list.iterator(); i.hasNext(); ){ String name = i.next(); } for(int i=0,size=list.size(); i
还剩65页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

yuyantao

贡献于2017-03-03

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf