2. JVM概览
2.1 java启动顺序
启动顺序:启动类加载器->扩展类加载器->应用类加载器
Javac: .java->.class
2.2 类文件结构
魔数/文件格式版本/常量池/访问标志/当前类/超类/接口/字段/方法/属性
My very cute animal turns savage in full moon areas
Magic version const access this super interface fields method attributes
2.3 Hotspot
零成本实现原则:不需要为用不到的功能付出代价。更进一步,你要使用的东西,性能已经非常好,手动优化也不会做的更好了
即时编译JIT
1. 编译的单元是方法和循环
2. 对执行的最频繁的部分进行编译和优化
2.4 内存管理
2.5 线程和内存模型
2.6 JVM版本
OpenJDK / Oracle / Zulu / Zing(超大堆)/ IcedTea / J9 /Avian /Android
2.7 JVM监控和工具
• Java 管理扩展(Java management extension,JMX)
• Java代理
• 使用
java.lang.instrument
接口修改方法的字节码• 安装方式,在启动时增加标志
\-javaagent:<path-to-agent-jar>=<options>
• java中包含一些清单文件(mainifest),并包含remain-class属性(代理类名称,其中需要有静态的premain方法,充当其注册钩子)
• 其他方式:JVMTI(C或C++编写)
• SA
viusalVM
3.硬件与操作系统
3.1 现代硬件简介
3.2内存
• CPU的发展远快于存储的发展
高速缓存
L1/L2 ----核心独有,L3 核心共用,通过北桥连接到主存
高速缓存一致性协议MESI
• 已修改(Modified),但尚未刷新到主存
• 独占(exclusive),只出现在当前高速缓存中,但数据和主存一致
• 共享(shared),可能出现在其他高速缓存中,但数据和主存一致
• 无效(invalid),可能未被使用,会尽快丢弃
3.3 现代处理器
1. 翻译后备缓冲器(tranlation lookaside buffer,TLB)---页表(地址映射)的高速缓存
2. 分支预测和推测执行
3. 硬件存储模型--javac,jit,cpu都允许改变执行顺序(store load)
3.4 操作系统
进程抢占的稀缺资源:CPU时间/内存
内存管理单元(MMU)以及页表的虚拟寻址是内存访问的关键,以保证一个进程的内存资源不被其他进程破坏
3.4.1 调度器
线程放弃时间片的方式:sleep / wait
3.4.2 时间问题
与操作系统有关
3.4.3 上下文切换
从用户模式切到内核模式会损失很大性能(TLB或其他高速缓存可能失效)
处理方式:vDSO(virtaul dynamically shared object)----用内存代替调用
3.5 简单系统调用层次
操作系统及硬件/JVM或容器/应用程序本身/外部系统 /外部请求流量
3.6 基本的探测策略
3.6.1利用CPU
Vmstat命令
Vmstat 1
可运行线程(r)/阻塞线程(b)
交换区(swpd)/空闲区(free)/缓冲区(buff)/高速缓存(cache)
磁盘交换的内存量(si)/交换到磁盘上的内存量(so)
从IO设备接到个数(bi)/发送到IO设备的块个数(bo)
中断次数(in)/上下文切换次数(cs)
用户时间(us)/内核时间(sy)/空闲时间(id)/等待时间(wa)/被盗时间(st,用于虚拟机)
3.6.2 垃圾收集
垃圾收集日志的成本很低,可以在生产环境上开启
3.6.3 IO
内核旁路IO
3.6.4 机械共鸣
作为一个赛车手,你不必成为工程师,但量一定要有机械共鸣。---Jackie Stewart
3.7 虚拟化
3.8 JVM和操作系统
System.currentTimeMillis()/JVM_CurrentTimeMillis()/OS::javaTimeMillis()
4.性能测试模式与反模式
4.1 性能测试的类型
好的性能测试是量化的
• 延迟测试 /SLA
• 吞吐量测试/系统开始降级之前得到的最大吞吐量
• 负载测试
• 压力测试/缓慢增加并发,直到数据开始下降
• 耐久性测试/以天为单位,缓慢内存泄漏,高速缓存污染,内存碎片化,full gc
• 容量规划测试
• 退化测试
4.2 最佳实践入门
原则
1. 确定关注点,想好衡量方式
2. 优化重要的东西,不是容易的东西
3. 抓住要点
4.2.1 自上而下的测试方式
从整个应用程序性能行为入手
4.2.3 创建测试环境
4.2.3 确定能要求
将系统作为一个整体,考虑对用户以及管理层重要的观测量
4.2.4 JAVA特有问题
需要确认哪些方法被JIT编译了
未编译的原因:
1. 运行频率不够高
2. 方法太大
4.2.5 将性能测试作为软件生命周期的一部分
4.3 性能反模式
反模式指我们在软件项目或团队中不希望出现,但在大量项目中又可以看到行为
厌倦/填充简历/同侪压力/缺乏理解/被错误理解的问题或不存在的问题
4.4 反模式目录
4.4.1 被热门技术分心
• 描述: 使用最新或最酷的热门技术
• 评论:
这是初期故障,我们需要弄清真相
• 现实:只是瞎猜/没有理解新技术/网络上的资料不一定是企业应用的案例,可能只是小规模实践
• 讨论:任何性能问题都应该看一下是否是由于采用新技术引起
• 解决:通过测量确定真正瓶颈/确定新组件有足够的日志/不能看演示,要看最佳实践/确保团队了解了新技术,并可以建立最佳实践
4.4.2 被简单分心
• 描述:团队只看中系统中最简单的部分,而不是系统整体
• 评论:
先从我们理解的部分入手吧
,这是John写的,他正在休假,等他回来吧
• 现实:原始开发人员了解自己编写的部分/没有知识共享或结对编程,成为领域单一专家
• 讨论:是一种防御性策略,不愿意接触不熟悉的领域
• 解决:对于测量确定瓶颈/向领域专家求助/确保开发人员了解系统所有组件
4.4.3 性能调优天才
• 描述: 管理层相信天才黑客的存在
• 评论:
我想我知道问题出在哪里了
• 现实:魔法师或超级英雄唯一可能做的事情就是奇装异服
• 讨论:会使团队有疏离感/促进分享知识
• 解决:测量确实瓶颈/保证专家愿意分享
4.4.5 把责任归咎给驴
• 描述:某些组件总会被当成问题所在,虽然他们与问题毫无关系
• 评论:
总是XX的问题
• 现实:没有经过充分分析/怀疑对象成为调查唯一目标/不愿意进行更大范围的研究调查
• 讨论:通过管理或业务人员表现/技术人员成为牺牲品
• 解决:不要急于得出结论/认真执行分析/向所有干系人通告结果
4.4.6 忽略大局
• 描述:只做小理改,没有意识到整体影响
• 评论:
如果只修改这些设置
/如果能加快分派时间
• 现实:没有充分理解更改的影响/没有在新设置在对系统全面分析/微基准测试对整体系统的影响不确定
• 讨论:jvm有上百个开关/性能调优是统计学活动
• 解决:生产环境中测量/每次改动一个开关/测试环境压力点一致
4.4.7 用户验收环境就是我的计算机
• 描述:在自己的机器上测试
• 评论:
和生产环境完全一致的用户验收测试环境成本太高了
• 现实:环境差异造成的服务中断成本比建一个测试环境的成本要大的多
• 讨论:测试环境必须与生产环境一致
• 解决:评估事故成本/建立与生产环境一致的测试环境
4.4.8 类似生产环境的数据很难表示
• 描述:简化测试数据集
• 评论:
数据保持同步太难了
/数据符合预期太难了
/生产环境数据受保护
• 现实:用户测试环境中的数据必须与生产环境中类似/可以混淆数据
• 讨论:假数据的测试结果是虚假的/不符合预期
• 解决:迁移生产数据到测试环境/对发布做好准备
4.5 认知偏差与性能测试
认知偏差是一种会导致人脑得出错误结论的心理效应。问题的重点在于表现出这种偏差的人通常认识不到这一点,而且很可能认为自己是理性的。
4.5.1 还原思维论
认识偏差认为如果能把一个系统分成足够小的组成部分,就可以通过理解每一部分来理解他。忽略了系统的复杂性
4.5.2 确认偏差
当选择一个糟糕的测试集或没有结果进行统计分析时,会引入确认偏差
4.5.3 战争迷雾(行动偏差)
在性能不及预期或是服务中断时表现出来,如:
1. 更改了基础设施,没有意识到会产生影响
2. 更改了依赖的库
3. 在最忙的时候遇到了奇怪的bug
错把行动当成了速度 要把紧张的情绪从环境中抽离出来
4.5.4 风险偏差
抗拒风险,厌恶改变
小的,经过计算的风险是可以接受的。
4.6.5 坎尔斯伯格悖论
与未知的未知相比,人们更喜欢已知的未知