Java 程序优化 (读书笔记)

时间:2023-01-03 21:48:54

--From : JAVA程序性能优化 (葛一鸣,清华大学出版社,2012/10第一版)

 

1. java性能调优概述

    1.1 性能概述 

        程序性能: 执行速度,内存分配,启动时间, 负载承受能力。

        性能指标: 执行时间,CPU时间,内存分配,磁盘吞吐量,网络吞吐量,响应时间。 

        优化策略: 木桶原理,优化性能瓶颈。

    1.2 性能调优的层次

        设计调优,

        代码调优,

        JVM调优,

        数据库调优,

        操作系统调优。

        

2. 设计优化

    2.1 善用设计模式 

        单例模式: 对于巨大对象,节省创建对象的时间空间;

        代理模式: 可用于延迟加载,提升系统启动速度;

        享元模式: 对象和组件复用,节省创建对象的开销;

        装饰者模式: 分离组件,提高可维护性增加模块复用性;

        观察者模式: 避免新开线程循环等待;

    2.2 常用组件优化

        缓冲(漏斗设计), 

        缓存(空间换时间), 

        对象复用“池”, 

        并行代替串行,

        负载均衡(Terracotta), 

        时间换空间,

        空间换时间。

        

3. Java程序优化

    3.1 字符串 

        不变性, 针对常量池优化

        subString 空间换取时间

        split 正则分割

        StringTokenizer 字符串分割

        灵活使用charAt 和 indexOf

        StringBuffer(线程安全) 和 StringBuilder : 初始化时设置最大容量

    3.2 核心数据结构

        List : ArrayList 和  LinkedList

        Map  : Hashtable(线程安全), HashMap, LinkedHashMap, TreeMap(可排序)

        Set  : hashSet, linkedHashSet, TreeSet

        集合访问优化 :

            分离循环中被重复调用的代码 ( i<collection.size() -> i<collectionSize )

            省略相同的操作 (循环内部的声明)

            减少方法调用 (优先直接访问内部变量)

        RandomAccess (随即访问): ArrayList Vector

    3.3 使用NIO提升性能

        NIO的Buffer 和 Channel 配合使用

        Buffer的原理和相关操作

        Buffer内存缓存和堆上内存区别

    3.4 引用类型

        强引用 : 可以直接访问,不会被GC回收,可能导致内存泄露(互相引用)

        软引用 : SoftReference<T> 内存紧张,会被GC回收

        弱引用 : WeakReference<T> GC发现立即回收

        虚引用 : PhantomReference<T> 始终返回null

        WeakHashMap : 弱引用的 HashMap

    3.5 改善性能

        慎用异常,try catch浪费性能 (62ms/110ms)

        使用局部变量,栈的访问比堆快 (78ms/266ms)

        位运算代替乘除法            (31ms/219ms)

        一维数组代替二维数组        (235ms/344ms)

        提取公共表达式

        降低循环次数 如数组赋值 i++ -> i+=3 (31ms/94ms)

        布尔运算代替位运算 (逻辑运算中布尔运算快于位运算,越简单的越靠前)

        使用System.arrayCopy()(native 函数)复制数组(31ms/250ms)

        Buffer进行I/O操作

        使用clone()代替new (一般是浅拷贝,其他需要具体实现clone)

        静态方法代替实例方法

        

4. 并行程序开发及优化

    4.1 并行程序设计模式

        Future 模式 : 提高响应速度

        Master_worker 模式 : 子任务分配,提高响应速度 

        Guarded Suspension 模式 : 队列缓冲,非立即处理,避免因为请求太多而崩溃

        不变模式 : 类似String/Double...不需要同步,线程安全

        生产者消费者模式 : 缓解两者间的性能差

    4.2 JDK多任务执行框架

        无限制线程的缺陷

        简单的线程池

        Exector 框架

        自定义线程池 : ThreadPoolExecutor

        优化线程池大小 : n = Ncpu * Ucpu *(1 +W/C)

        扩展 ThreadPoolExector

    4.3 JDK并发数据结构

        并发 list : CopyOnWriteArrayList

        并发 Set : CopyOnWriteArraySet

        并发 Map : ConcurrentHashMap

        并发 Queue : ConcurrentLinkedQueue (使用CAS无锁) / LinkedBlockingQueue

        并发 Deque : LinkedBlockingDeque (读写都加锁)

    4.4 并发控制方法 

        Java内存模型与 volatile

        同步关键字 synchronized

        ReentrantLock 重入锁

        ReentrantReadWriteLock 读写锁 (并读串写)

        Condition 对象

        Semaphore 信号量 (限制最大访问线程数)

        ThreadLocal 线程局部变量

    4.5 锁的性能和优化

        线程的开销

        避免死锁

        减小锁持有时间

        减小锁粒度

        读写分离锁来替换独占锁

        锁分离 (LinkedBlockingQueue)

        重入锁 (ReentrantLock) 和内部锁(Synchronized)

        锁粗化 Lock Coarsening (多次重复请求消耗资源)

        自旋锁 Spinning Lock (执行空循环来减少被挂起的次数)

        锁消除 Lock Elimination (消除不必要的锁)

        锁偏向 Biased Lock (高并发效率低)

    4.6 无锁的并行计算

        非阻塞的同步/无锁 (基于CAS)

        原子操作 (java.util.concurrent.atomic)

        Amino 框架 : 提供现成安全的,基于无锁的数据结构,内置了多线程调度模式。 

        Amino 集合 : LockFreeList(linked)/LockFreeVector(ArrayList); LockFreeSet

        Amino 树 : LockFreeBSTree(二叉搜索树)

        Amino 图 : UndirectedGraph<E> / DirectedGraph<E>

        Amino 简单调度模式 : Master_worker (静态/动态)

    4.7 协程

        协程的概念 : 进程的分割式线程,线程的分割是协程 

        Kilim 框架 : 当需要大量线程时,使用协程能提高性能,降低线程维护切换的性能

 

5. JVM调优

    5.1 Java 虚拟机内存模型

        程序计数器 : 线程私有的内存空间,互不影响; 当执行java方法,程序计数器为Java字节码地址,当执行native方法程序计数器为空

        Java虚拟机栈 : 线程私有的内存空间, 用于管理Java函数调用, -Xss1M

        本地方法栈 : 管理本地方法调用

        Java堆 : 分为新生代和老年代两部分

        方法区 : 和Java堆一样被所有线程共享;包括类的类型信息,常量池,域信息,方法信息,也叫永久区

    5.2 JVM 内存分配参数

        -Xmx : 设置最大堆内存

        -Xms : 设置初始堆内存

        -Xss : 设置线程栈 (使用系统内存,堆空间分配越大,线程总数越少) JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K

        -Xmn : 设置新生代 (一般为堆空间的1/4~1/3 )

        -XX:NewSize : 设置初始新生代空间

        -XX:MaxNewSize : 设置最大新生代空间    

        -XX:PermSize : 设置初始持久区

        -XX:MaxPermSize : 设置最大持久区

        -XX:SurvivorRatio 设置新生代比例 (eden : survivor) 

        -XX:NewRatio : 设置heap比例 (老年代 : 新生代) 

        -XX:TargetSurvivorRatio : 设置suvivior使用率,超过空间会送入老年代

    5.3 垃圾收集基础

        垃圾收集的作用

        垃圾收集算法与思想 

            1) 引用计算法 : 记录对象引用数,但两个对象的循环引用会导致内存泄露

            2) 标记清除法 : 记录根节点开始的可达对象,但回收后造成空间碎片

            3) 复制算法 : 在存活对象少垃圾多的情况下,系统空间折半,复制有效对象到另一空间,适用于新生代对象(from space to space)

            4) 标记压缩算法 : 在存活对象多垃圾少的情况下,清除无效对象,压缩有效对象,清除空间碎片,适用于老年代

            5) 增量算法 : 避免长时间占据应用程序执行时间,而交替执行,每次清理一小片内存空间,但由于线程频繁上下文切换,会降低系统吞吐量

            6) 分代 : 分为年轻代和老年代,分别使用不同的垃圾收集算法,新生代使用复制算法,老年代使用标记压缩算法

        垃圾收集器的类型 

            线程数 : 串行/并行

            工作模式 : 并发/独占

            碎片处理 : 压缩/非压缩

            分代 : 新生代/老年代 

        评价GC策略的指标 : 吞吐量/垃圾回收器负载/停顿时间/垃圾回收频率/反应时间/堆分配

        新生代串行收集器 : 单线程,独占

        老年代串行收集器 : 标记压缩,串行独占

        并行收集器 : 多线程,独占

        新生代并行回收收集器 : 多线程,独占,关注系统吞吐量

        老年代并行回收收集器 : 标记压缩,关注系统吞吐量

        CMS(concurrent Mark Sweep)收集器 :  标记清除法,多线程并行,非独占

        G1(Garbage First)收集器 : 关注吞吐量和停顿控制,适用标记压缩法

        Stop the World : 整个应用停止等待垃圾回收完成

        收集器对系统性能的影响

        GC相关参数总结

            1) 串行回收器相关参数

                -XX:+UseSerialGC : 新生代串行收集器和老年代串行收集器

                -XX:SurvivorRatio : 设置eden和survivor的比例

                -XX:PretenureSizeThreshold : 设置大对象进入老年代的阈值,对象大小超过此值,直接在老年代分配,只对串行收集器和新生代并行收集器有效,并行回收收集器不识别此参数

                -XX:MaxTenuringThreshold : 设置进入老年代的年龄最大值,每次Minor gc后,对象年龄+1, 默认15

            2) 并行回收器相关参数

                -XX:+UseParNewGC : 新生代使用并行收集器

                -XX:+UseParallelOldGC : 老年代使用并行回收收集器

                -XX:ParallelGCThreads : 垃圾回收线程数

                -XX:MaxGCPauseMillis :  最大垃圾收集停顿时间

                -XX:GCTimeRatio : 设置吞吐量大小n, GCTime < 1/(1+n)

                -XX:+UseAdaptiveSizePolicy : 打开自适应GC策略,新生代大小,eden和survivor比例等自动调整

            3) 与CMS回收器相关参数

                -XX:+UseConcMarkSweepGC : 新生代使用并行收集器,老年代使用CMS+串行收集器

                -XX:parallelCMSThreads : CMS的线程数量

                -XX:CMSInitiatingOccupancyFraction : 设置CMS收集器在老年代空间使用多少后触发,默认68%

                -XX:+UseCMSCompactAtFullCollection : 设置CMS收集器在完成垃圾收集后是否进行一次内存碎片整理

                -XX:CMSFullGCsBeforeCompation : 设置多少次CMS垃圾回收后进行内存压缩

                -XX:+CMSClassUnloadingEnabled : 允许对类元数据进行回收

                -XX:+CMSParallelRemarkEnabled : 启用并行标记

                -XX:CMSInitiatingPermOccupancyFraction : 永久区达到此阈值,启动CMS回收(前提是  -XX:CMSClassUnloadingEnabled激活)

                -XX:UseCMSInitiatingOccupancyOnly : 达到阈值才进行CMS回收

                -XX:+CMSIncrementalMode : 使用增量模式,适合单CPU

            4) 与G1回收器相关参数

                -XX:+UseG1GC : 使用G1回收器

                -XX:+UnlockExperimentalVMOptions : 允许使用实验性参数

                -XX:MaxGCPauseMillis : 设置最大垃圾停顿时间

                -XX:GCPauseIntervalMillis :  设置垃圾收集间隔时间

            5) 其他参数

                -XX:+DisableExplicitGC : 禁用显示GC

    5.4 常用调优案例和方法

        1) 将新对象预留在新生代 : 新生代的垃圾回收速度高于老年代回收,可设置新生代大小或者新生代和老年代比例调整新生代大小

        2) 大对象进入老年代 : 大对象需要空间,可能导致空间不足或者需要移动大量小对象到老年代,设置-XX:PretenureSizeThreshold

        3) 设置对象进入老年代的年龄 : gc一次年龄+1,年龄到达阈值进入老年代,设置阈值 -XX:MaxTenuringThreshold

        4) 稳定与震荡的堆大小 : 空间大GC速度慢,空间小GC速度快,大部分设置最大最小相同

            -XX:MinHeapFreeRatio : 设置堆空间最小空闲比例,默认40;

            -XX:MaxHeapFreeRatio : 设置对空间最大空闲比例,默认70;

        5) 吞吐量优先案例 : java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC

        6) 使用大页案例 : java -Xmx2506m -Xms2506 -Xmn1536m -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC -XX:LargePageSizeInBytes=256m (设置大页大小)

        7) 降低停顿案例 : java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC _XX:+UseParNewGC -XX:SurvivorRatio=8 -XX:TargetSurvivorRatio=90 -XX:MaxTenuringThreshold=31

    5.5 实用JVM参数

        1) JIT(Just-In-Time)编译参数 : 运行时,函数运行次数超过阈值,将字节码编译成本地代码提高执行效率,

            -XX:CompileThreshold : 编译阈值

            -XX:+PrintCompilation : 打印JIT编译信息

            -XX:+CITime 打印JIT编译的基本信息

        2) 堆快照(堆Dump) : 发生OOM时导出Dump文件,可以使用Visual VM等工具分析

            -XX:+HeapDumpOnOutOfMemoryError : 发生OOM时导出当前堆快照

            -XX:HeapDumpPath : 指定堆快照信息保存文件位置

        3) 错误处理 : 发生OOM时运行第三方脚本

            -XX:OnOutOfMemoryError=c:\reset.bat

        4) 取得GC信息

            -verbose:gc

            -XX:+PrintGC

            -XX:+PrintGCDetails

            -XX:+PrintGCTimeStamps : 打印每次GC的发生时间

            -XX:+PrintTenuringDistribution : 察看新生对象晋升老年代的实际阈值

            -XX:+PrintHeapAtGC : 每次GC打印堆的使用情况

            -XX:+PrintGCApplicationStoppedTime : 显示应用程序在GC发生时的停顿时间

            -XX:+PrintGCApplicationConcurrentTime : 应用程序在GC停顿时间的执行时间

            -Xloggc:C:\gc.log : GC日志

        5) 类和对象跟踪

            -XX:+TraceClassLoading : 跟踪类加载情况

            -XX:+TraceClassUnloading : 跟踪类的卸载情况

            -verbose:class :  跟踪类的加载和卸载情况

            -XX:+PrintClassHistogram : 当按下Ctrl + Break,输出系统内类的统计信息

        6) 控制GC

            -XX:DisableExplicitGC : 禁止显示GC操作,避免程序员大量使用System.gc()降低程序性能

            -Xnoclassgc : 禁止回收类(class,而不是对象实例)

        7) 选择类校验器

            -XX:-UseSplitVerifier :实用旧的类校验器,JDK1.6默认开启新的类校验器

            -XX:-FailOverToOldVerifier : 关闭再次校验功能, 默认新的校验器失败会再次使用老的校验器

        8) Solaris下线程控制

            -XX:+UseBoundThreads : 绑定所有用户线程到内核线程,减少线程进入饥饿状态的次数

            -XX:+UseLWPSychronization : 使用内核线程代替线程同步

            -XX:+UseVMInterruptibleIO : 允许运行时中断线程

        9) 使用大页

            -XX:+UseLargePages : 启用大页

            -XX:LargePageSizeInBytes : 指定大页的大小

        10) 压缩指针

            -XX:+UseCompressedOops : 打开指针压缩,压缩class的属性指针(静态成员),对象的属性指针,普通对象的每个元素指针, 能节省内存,但是会消耗一定的性能

    5.6 实战JVM调优

        1) Tomcat简介与启动加速 : 减少GC次数,扩大新生代,禁止显示调用GC

        2) 一个示例web : 每个用户分配1m空间

        3) JMEter介绍和使用 : 基于java的性能测试和压力测试工具,生成包括响应时间,错误数和吞吐量的报告

        4) 调优前Web应用运行状况 : GC过多,吞吐量为38.7/s

        5) 调优过程 : 确定堆内存大小,合理分配新生代和老年代,确定永久区大小,选择垃圾收集器,对垃圾收集器设置,禁用显示gc,进用类元数据回收,禁用类验证等

            设置堆空间和永久区,禁用显示gc,去掉类验证,吞吐量为47.9/s

            -Xmx512M(-Xmx256M)

            -Xms512M

            -XX:permSize=32M

            -XX:MaxPermSize=32M

            -XX:+DisableExplicitGC

            -Xverify:none

            使用并行回收收集器,吞吐量为51/s

            -XX:+UseParallelGC

            -XX:+UseParallelOldGC

            -XX:ParallelGCThreads=8

6 Java性能调优工具

    6.1 Linux命令行工具

        1) top : 显示系统中各个进程的资源占用状况

        2) sar : 周期性对内存,I/O和CPU情况采样 

        3) vmstat : 统计CPU内存使用情况,swap使用情况,可指定周期性采样和次数

        4) iostat : 提供详尽的I/O信息,可指定周期性采样和次数

        6) pidstat : 监测进程和线程的情况,

            监测CPU : 可以配合 jstack -l $ThreadId 察看进程堆栈

            I/O使用监控 

            内存监控

    6.2 Windows工具

        1) 任务管理器 

        2) perfmon性能检测工具 : 可以针对一个进程进行监控

        3) Process Explorer : 需安装

        4) pslist  : 需安装

    6.3 JDK命令行工具

        1) jps : 类似Linux下的ps,用于列出Java进程,-m(主函数的参数),-l(主函数路径),-v(显示JVM参数)

        2) jstat : 观察Java应用程序运行时的信息

        3) jinfo : 察看Java应用程序的扩展参数(JVM参数),可通过此命令来查看默认参数值,可修改参数 

        4) jmap : 生成堆快照和对象的统计信息

        5) jhat : 分析Java应用程序的对快照内容,可在浏览器中访问http://127.0.0.1:7000

        6) jstack : 导出Java应用车功能需的线程堆栈

        7) jstatd : RMI的服务端程序,建立远程监控通信,需要使用java的安全策略在使用

        8) hprof : 非独立监控工具,用于监测Java程序的CPUh和堆信息,监控函数运行时间,导出程序堆内容

    6.4 JConsole工具 : %javahome%/bin/jconsole.exe

        1) JConsole连接Java程序 : 添加启动参数可远程连接

            -Dcom.sun.management.jmxremote.authenticate=false  -Dcom.sun.management.jmxremote.ssl=fals  -Dcom.sun.management.jmxremote.port=9988 -Dcom.sun.management.jmxremote

        2) Java程序概况 : 可查看堆内存,系统线程数量,加载类数量和CPU使用率

        3) 内存监控 : 察看内存详细信息,细化到eden,survivior,老年代,永久区;可以强制Full GC

        4) 线程监控 : 可以监测到所有线程以及栈信息

        5) 类加载情况 : 显示加载空间,类数量

        6) 虚拟机信息 : 显示虚拟机类型,版本,堆信息,虚拟机参数

        7) MBean管理 : 可操作GC信息显示,线程信息查看,峰值线程数量,当前线程数量,死锁检测等 

        8) 使用插件 : 如JDK自带插件JTop,察看线程CPU使用排序

    6.5 Visual VM多合一工具

        1) Visual VM连接应用程序 : 添加JVM参数或者配合jstatd远程监控

        2) 监控应用程序概况 : 类似Jconsole

        3) Thead Dump和分析 

        4) 性能分析 : 在Sampler页面下,可以监测方法调用时间

        5) 快照 : 快照导出 

        6) 内存快照分析 

        7) MBean管理 : 可通过插件继承MBean

        8) TDA使用 : 分析导出的线程快照,可以单独运行,也可以作为Visual VM插件运行

        9) BTrace介绍 : 在不停机的情况下添加代码执行,

            监控指定函数耗时 

            取得任意行代码信息

            定时触发

            监控函数参数

            监控文件

    6.6 Visual VM对OQL的支持

        1) Visual VM的OQL基本语法

        2) 内置head对象 

        3) 对象函数

        4) 集合/统计函数 

        5) 程序化OQL

    6.7 MAT内存工具分析

    6.8 MAT对OQL的支持

    6.9 JProfile简介 : 商业软件

    

        

JVM :      -XX:+UseSpinning (开启自旋锁) -XX:PreBlockSpin (自旋锁的等待次数)        

        -server -XX:+DoEscapeAnalysis (逃逸分析) -XX:+EliminateLocks (锁消除)

        -XX:+UseBiasedLocking (开启偏向锁)

        -Xss1M

        -XX:+PrintGCDetails(输出收集器日志)

        -XX:+UseSerialGC (新生代串行收集器和老年代串行收集器)

        -XX:+UseParallelGC (新生代并行回收收集器和老年代串行收集器,会停止app threads)

        -XX:+UseParNewGC (新生代并行收集器和老年代串行收集器)

        -XX:+UseConcMarkSweepGC (新生代并行收集器和老年代CMS)

            -XX:CMSInitiatingOccupancyFraction (CMS老年代回收阀值n,默认68(%),如果内存使用增长快,n较小时效率高,反之n应该较大)

            -XX:+UseCMSCompactAtFullCollection (CMS收集完成进行碎片整理,整理不是并发进行)

            -XX:CMSFullGCsBeforeCompation (CMS进行n次后,才进行内存压缩)

        -XX:+UseParallelOldGC (新生代老年代并行回收处理器) 

            -XX:MaxGCPauseMillis (最大垃圾收集停顿时间) 

            -XX:GCTimeRatio (设置吞吐量大小 : n=[0~100] (gcTime <= 1/(1+n) ) ) 

            -XX:+UseAdaptiveSizePolicy (自适应GC调节策略) 

            -XX:ParallelGCThreads (收集器线程数,一般等于CPU数量)

        -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC (启动G1 GC)

            -XX:MaxGCPauseMillis -XX:GCPauseIntervalMillis (不超过200ms, 停顿不超过50ms,参数只是目标,不保证执行)

        -XX:DisableExplicitGC (禁用显示gc) -Xnoclassgc(禁用类元数据回收) -Xverify:none (禁用类验证)

        

        

remote debug : 远程debug 

    远程debug顾名思义,能够将远程操作系统上的任何java进行debug,但是有前提是本地需要有同步的代码。

    1.远程debug的步骤是在远程操作系统上启动java进程时增加特殊的

        -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=9977

    2.在Eclipse中新建一个Remote Java Application