Week 9 性能优化 Java & 秒杀
数据库基本原理
PrepareStatement 预编译
PrepareStatement 会预先提交带占位符的 SQL 到数据库进行预处理,提前生成执行计
划,当给定占位符参数,真正执行 SQL 的时候,执行引擎可以直接执行,效率更好一点。
PrepareStatement 可以防止 SQL 注入攻击。
索引
聚簇索引
MySQL 数据库的主键就是聚簇索引,主键 ID 和所在的记录行存储在一个 B+树中。
非聚簇索引
非聚簇索引在叶子节点记录的就不是数据行记录,而是聚簇索引,也就是主键。
通过非聚簇索引找到主键索引,再通过主键索引找到行记录的过程也被称作回表
合理使用索引
不要盲目添加索引,尤其在生产环境中
添加索引的 alter 操作会消耗较长的时间(分钟级)
Alter 操作期间,所有数据库的增删改操作全部阻塞,对应用而言,因为连接不能释放,事实 上,查询也被阻塞。
删除不用的索引,避免不必要的增删开销
使用更小的数据类型创建索引
int 4 字节 bigint 8 字节,Timestamp 4 字节 Datetime 8 字节
数据事务
事务特性 ACID
原子性(Atomicity):事务要么全部完成,要么全部取消。如果事务崩溃,状态回到事
隔离性(Isolation):如果 2 个事务 T1 和 T2 同时运行,事务 T1 和 T2 最终的结果是相同
持久性(Durability):一旦事务提交,不管发生什么(崩溃或者出错),数据要保存在数
一致性(Consistency):只有合法的数据(依照关系约束和函数约束)才能写入数据库。
数据日志
进行事务操作时,事务日志文件会记录更新前的数据记录,然后再更新数据库中的记录,
如果全部记录都更新成功,那么事务正常结束,如果过程中某条记录更新失败,那么整
个事务全部回滚,已经更新的记录根据事务日志中记录的数据进行恢复,这样全部数据
都恢复到事务提交前的状态,仍然保持数据一致性。
LSN:一个按时间顺序分配的唯一事务记录日志序列号。TransID:产生操作的事务 ID。PageID:被修改的数据在磁盘上的位置。PrevLSN:同一个事务产生的上一条日志记录的指针。UNDO:取消本次操作的方法,按照此方法回滚。REDO:重复本次操作的方法。
数据库架构
SQL -》 连接器 -》语法分析器 -》语义分析与优化器 -》执行引擎
连接器
数据库连接器会为每个连接请求分配一块专用的内存空间用于会话上下文管理。建立连接对数据库而言相对比较重,需要花费一定的时间,因此应用程序启动的时候,通常会初始化建立一些数据库连接放在连接池里,这样当处理外部请求执行 SQL 操作的时候,就不需要花费时间建立连接了。
语法分析器
语法树
语义分析与优化器
语义分析与优化器就是要将各种复杂嵌套的 SQL 进行语义等价转化,得到有限几种关系代数计算结构,并利用索引等信息进一步进行优化。
执行计划
Key:索引类型,NULL 无索引
Rows:需要处理的行数
Possible_keys:潜在可以利用的索引
Java 性能优化
JVM 虚拟机
JVM 组成架构
Java 字节码文件
Java 如何实现在不同操作系统、不同硬件平台上 , 都可以不用修改代码就能顺畅地执
行?
计算机领域的任何问题都可以通过增加个中间层(虚拟层)来解决
Java 所有的指令有 200 个左右,一个字节( 8 位)可以存 储 256 种不同的指令信息,一个这样的字节称为字节码( Bytecode )。在代码的执行过程中, JVM 将字节码解释执行,屏蔽对底层操作系统的依赖, JVM 也可以将字节码编译执行,如果是热点代码,会通过 JIT 动态地编译为机器码,提高执行效率。
字节码执行流程
Java 字节码编译过程
类加载器的双亲委托模型
自定义加载器类型
隔离加载类:同一个 JVM 中不同组件加 载同一个类的不同版本。
扩展加载源:从网络、数据库等处加载字节码。
字节码加密:加载自定义的加密字节码, 在 ClassLoader 中解密。
堆 &栈
堆:每个 JVM 实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程共享。
堆栈:JVM 为每个新创建的线程都分配一个堆栈。也就是说,对于一个 Java 程序来说,它的运行就是通过对堆栈的操作来完成的。
Java 中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的引用而已。
方法区 &程序计数器
方法区主要存放从磁盘加载进来的类字节码,而在程序运行过程中创建的类实例则存放在堆里。程序运行的时候,实际上是以线程为单位运行的,当 JVM 进入启动类的 main 方法的时候,就会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执行,每个线程有自己的 Java 栈,栈里存放着方法运行期的局部变量。而当前线程执行到哪一行字节码指令,这个信息则被存放在程序计数寄存器。
Java 运行环境
JVM 垃圾回收
JVM 垃圾回收就是将 JVM 堆中的已经不再被使用的对象清理掉,释放宝贵的内存资源。
JVM 通过一种可达性分析算法进行垃圾对象的识别,具体过程是:从线程栈帧中的局部变量,或者是方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记的对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是被使用的对象,而那些没有被标记的对象就是可回收的垃圾对象了。
进行完标记以后,JVM 就会对垃圾对象占用的内存进行回收,回收主要有三种方法
清理:将垃圾对象占据的内存清理掉,其实 JVM 并不会真的将这些垃圾内存进行清理,而是将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象。
压缩:从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其余的空间就是连续的空闲空间。
复制:将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标记过的可用对象复制到另一个空间中。
JVM 分代垃圾回收
JVM 垃圾回收算法
-XX:MaxGCPauseMillis
Java 启动参数
标准参数,所有的 JVM 实现都必须实现这些参数的功能,而且向后兼容
运行模式 -server,-client
类加载路径 -cp,-classpath
运行调试 –verbose
系统变量 –D
-Xms 初始堆大小
-Xmx 最大堆大小
-Xmn 新生代大小
-Xss
非 Stable 参数, 此类参数各个 jvm 实现会有所不同,将来可能会随时取消
• -XX:-UseConcMarkSweepGC 启用 CMS 垃圾回收
JVM 诊断工具
JPS
JPS 用来查看 host 上运行的所有 Java 进程的 pid(jvmid),一般情况下使用这个工具的目的只是为了找出运行的 JVM 进程 ID,即 lvmid,然后可以进一步使用其它的工具来监控和分析 JVM
• -l 输出 Java 应用程序的 main class 的完整包• -q 仅显示 pid
• -m 输出传递给 main 方法的参数
• -v 输出传递给 JVM 的参数。在诊断 JVM 相关问题的时候,这个参数可以查看 JVM 相关参数的设置
JSTAT
JSTAT( “Java Virtual Machine statistics monitoring tool” )是 JDK 自带的一个轻量级小工具。主要对 Java 应用程序的资源和性能进行实时的命令行的监控,包括了对 Heapsize 和垃圾回收状况的监控。
语法结构如下:jstat [Options] vmid [interval] [count]
Options -- 选项,我们一般使用 - gcutil 查看 gc 情况
vmid -- VM 的进程号,即当前运行的 Java 进程号
interval-- 间隔时间,单位为毫秒
count -- 打印次数,如果缺省则打印无数次
S0 -- Heap 上的 Survivor space 0 区已使用空间的百分比
S1 -- Heap 上的 Survivor space 1 区已使用空间的百分比
E -- Heap 上的 Eden space
O -- Heap 上的 Old space 区已使用空间的百分比
YGC -- 从应用程序启动到采样时发生 Young GC
YGCT-- 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC -- 从应用程序启动到采样时发生 Full GC
FGCT-- 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT -- 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)
JMAP
JMAP 是一个可以输出所有内存中对象的工具,甚至可以将 VM 中的 heap,以二进制输出成文本。
使用方法
jmap -histo pid>a.log 可以将其保存到文本中去,在一段时间后,使用文本对比工具,可以
jmap -dump:format=b,file=f1 PID 可以将该 PID 进程的内存 heap 输出出来到 f1 文件里。
JSTACK
jstack 可以查看 JVM 内的线程堆栈信息
Java 代码优化
合并并谨慎使用多线程
使用场景(I/O 阻塞,多 CPU 并发)资源争用与同步问题 java.util.concurrent
启动线程数 = [任务执行时间 / (任务执行时间 - IO 等待时间)] * CPU 内核数
• 最佳启动线程数和 CPU 内核数量成正比,和 IO 阻塞时间成反比。如果任务都是 CPU 计算型任务,那么线程数最多不超过 CPU 内核数,因为启动再多线程,CPU 也来不及调 度;相
反如果是任务需要等待磁盘操作,网络响应,那么多启动线程有助于提高任务并 发度,提高系统吞吐能力,改善系统性能。
Java 线程安全
允许被多个线程安全执行的代码称作线程安全的代码。
方法局部变量
局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。
方法局部的对象引用
如果在某个方法中创建的对象不会逃逸出该方法,那么它就是线程安全的。
对象成员变量
对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。
ThreadLocal
创建一个 ThreadLocal 变量(X 类静态成员变量):
public static ThreadLocal myThreadLocal = new ThreadLocal();
存储此对象的值(A 类 a 方法):
X.myThreadLocal.set("A thread local value");
读取一个 ThreadLocal 对象的值(B 类 b 方法):
String threadLocalValue = (String)X.myThreadLocal.get();
Java 内存泄漏
如果程序保留对永远不再使用的对象的引用,这些对象将会占用并耗尽内存。
长生命周期对象
静态容器
缓存
合理使用线程池和对象池
复用线程或对象资源,避免在程序的生命期中创建和删除大量对象
池管理算法(记录哪些对象是空闲的,哪些对象正在使用)
对象内容清除(ThreadLocal 的清空)
使用合适的 JDK 容器类(顺序表,链表,Hash)
LinkList 和 ArrayList 的区别及适用场景
HashMap 的算法实现及应用场景
使用 concurrent 包,ConcurrentHashMap 和 HashMap 的线程安全特性有什么不同?
缩短对象生命周期,加速垃圾回收
减少对象驻留内存的时间
在使用时创建对象,用完释放
创建对象的步骤(静态代码段-静态成员变量-父类构造函数-子类构造函数)
使用 IO buffer/NIO
延迟写/提前读
异步无阻塞 IO
优先使用组合代替继承
减少对象耦合
避免太深的继承层次带来的对象创建性能损失
合理使用单例模式
无状态对象
线程安全
计算机的任何问题都可以通过虚拟层(或者中间层)解决
面向接口编程
7 层网络协议
JVM
编程框架
一致性 hash 算法的虚拟化实现
版权声明: 本文为 InfoQ 作者【evildracula】的原创文章。
原文链接:【http://xie.infoq.cn/article/2414d726a2e2d0e09e0b53775】。文章转载请联系作者。
评论