Java 性能优化系列之1[设计与程序优化]
性能
一般来说,性能通过以下几个方面来表现:
- 执行速度
- 内存分配
- 启动时间
- 负载承受能力
定量评测的性能指标:
- 执行时间
- CPU时间
- 内存分配
- 磁盘吞吐量
- 网络吞吐量
- 响应时间
调优的层面
- 设计调优
- 代码调优
- JVM调优
- 数据库调优
- 操作系统调优
性能调优必须有明确的目标,不要为了调优而调优,如果当前程序并没有明显的性能问题,盲目地进行调整,其风险可能远远大于收益。
设计优化
1. 单例模式
对于系统的关键组件和被频繁使用的对象,使用单例模式可以有效地改善系统的性能
2. 代理模式
代理模式可以用来实现延迟加载,从而提升系统的性能和反应速度。
另外,可以考虑使用动态代理的方式 。 动态代理的方法有: JDK自带的动态代理, CGLIB, Javassist, 或ASM库。
3. 享元模式
好处:
1) 可以节省重复创建对象的开销
2) 对系统内存的需求减少
4. 装饰者模式
实现性能组件与功能组件的完美分离
5. 观察者模式
观察者模式可以用于事件监听、通知发布等场合。可以确保观察者在不使用轮询监控的情况下,及时收到相关的消息和事件。
6. Value Object 模式
将一个对象的各个属性进行封装,将封装后的对象在网络中传递,从而使系统拥有更好的交互模型,并且减少网络通信数据,从而提高系统性能。
7. 业务代理模式
将一组由远程方法调用构成的业务流程,封装在一个位于展示层的代理类中。
思考:
单例模式, 工厂模式和享元模式的差异?
常用优化组件和方法
1.缓冲
I/O 操作很容易成为性能瓶颈,所以,尽可能在 I/O 读写中加入缓冲组件,以提高系统的性能。
2. 缓存
缓存可以保存一些来之不易的数据或者计算结果,当需要再次使用这些数据时,可以从缓存中低成本地获取,而不需要再占用宝贵的系统资源。
Java缓存框架:
EHCache, OSCache,JBossCache
3. 对象复用 -- "池"
最熟悉的线程池和数据库连接池。
目前应用较为广泛的数据库连接池组件有C3P0 和Proxool.
4.并行替代串行
5. 负载均衡
跨JVM虚拟机,专门用于分布式缓存的框架--Terracotta, 使用Terracotta可以实现Tomcat的Session共享。
6. 时间换空间
7. 空间换时间
程序优化
1. 字符串优化处理
1)
String str1 ="abc";String str2 ="abc";String str3 = new String("abc");System.out.println(str1==str2); //trueSystem.out.println(str1==str3); //falseSystem.out.println(str1==str3.intern()); //true
2) subString() 方法的内存泄漏
如果原字串很长,截取的字串却有比较短,使用以下方式返回:
new String(str1.substring(begin,end));
3) 字符串分割和查找
可以使用的方法:
split()方法 -- 最慢, 写法简单
StringTokenizer 方法 -- 较快,写法一般
indexOf()和subString() 方法 - 最快, 写法麻烦
package performance.program.string;import java.util.StringTokenizer;public class StringSplit {public static void splitMethod(String str) {long beginTime = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {str.split(";");}long endTime = System.currentTimeMillis();System.out.println("splitMethod use " + (endTime - beginTime));}public static void tokenizerMethod(String str) {long beginTime = System.currentTimeMillis();StringTokenizer st = new StringTokenizer(str, ";");for (int i = 0; i < 10000; i++) {while (st.hasMoreTokens()) {st.nextToken();}st = new StringTokenizer(str, ";");}long endTime = System.currentTimeMillis();System.out.println("tokenizerMethod use " + (endTime - beginTime));}public static void IndexMethod(String str) {long beginTime = System.currentTimeMillis();String tmp = str;for (int i = 0; i < 10000; i++) {while (true) {String splitStr = null;int j = tmp.indexOf(";");if(j<0) break;splitStr = tmp.substring(0,j);tmp = tmp.substring(j+1);}tmp = str;}long endTime = System.currentTimeMillis();System.out.println("IndexMethod use " + (endTime - beginTime));}/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubString orgStr = null;StringBuffer sb = new StringBuffer();for (int i = 0; i < 1000; i++) {sb.append(i);sb.append(";");}orgStr = sb.toString();splitMethod(orgStr);tokenizerMethod(orgStr);IndexMethod(orgStr);}}
4) 使用ChartAt 代替 startsWith 和 endsWith
性能要求比较高时,可以使用这条。
5) StringBuffer 和 StringBuilder
String result = "String" + "and" + "string"+"append";
这段看起来性能不高的代码在实际执行时反而会比StringBuilder 来的快。
原因是Java 编译器本身会做优化。
但是不能完全依靠编译器的优化, 还是建议显示地使用StringBuffer 或StringBuffer对象来提升系统性能。
StringBuffer 和 StringBuilder 的差异是:
StingBuffer 几乎所有的方法都做了同步
StringBuilder 并没有任何同步。
所以StringBuilder 的效率好于StringBuffer, 但是在多线程系统中,StringBuilder 无法保证线程安全。
另外,预先评估StringBuilder 的大小,能提升系统的性能。
2. 核心数据结构
1) List 接口
3种List实现: ArrayList, Vector, 和LinkedList
ArrayList 和Vector 使用了数组实现, Vector 绝大部分方法都做了线程同步, ArrayList 没有对任何一个方法做线程同步。(ArrayList 和Vector 看上去性能相差无几)
使用LinkedList 对堆内存和GC的要求更高。
如果在系统应用中, List对象需要经常在任意位置插入元素,则可以考虑使用LinkedList 替代ArrayList
对于ArrayList从尾部删除元素时效率很高,从头部删除元素时相当费时,
而LinkedList 从头尾删除元素时效率相差无几,但是从中间删除元素时性能非常槽糕。
头部 | 中间 | 尾部 | |
ArrayList | 6203 | 3125 | 16 |
LinkedList | 15 | 8781 | 16 |
List 遍历操作
ForEach | 迭代器 | for 循环 | |
ArrayList | 63 | 47 | 31 |
LinkedList | 63 | 47 | 无穷大 |
2) Map 接口
实现类有: Hashtable, HashMap, LinkedHashMap和TreeMap
HashTable 和HashMap 的差异
HashTable大部分方法做了同步, HashTable 不允许key或者Value 使用null值;(性能上看起来无明显差异)
3) Set 接口
4) 优化集合访问代码
1. 分离循环中被重复调用的代码
for(int i=0;i<collection.size();i++)
替换成
int count = collection.size();
for(int i=0;i<count;i++)
5). RandomAccess接口
3. 使用NIO 提升性能
在Java 的标准I/O中, 提供了基于流的I/O 实现, 即InputStream 和 OutputStream. 这种基于流的实现是以字节为单位处理数据, 并且非常容易建立各种过滤器。
NIO是 New I/O 的简称, 与旧式的基于流的 I/O 方法相对, 它表示新的一套Java I/O 标准。是在Java 1.4 中被纳入到JDK中的, 特性有
- 为所有的原始类型提供 Buffer 缓存支持
- 使用Java.nio.charset.Charset 作为字符集编码解码解决方案
- 增加通道对象(Channel), 作为新的原始I/O 抽象
- 支持锁和内存映射文件的文件访问接口
- 提供了基于Selector 的异步网络I/O
与流式的I/O 不同, NIO 是基于块(Block 的), 它以块为基本单位处理数据。
4. 引用类型。
强引用、软引用、弱引用和虚引用
强引用:
a)强引用可以直接访问目标对象
b) 强引用所指向的对象在任何时候都不会被系统回收
c)强引用可能导致内存泄漏
软引用:
软引用可以通过java.lang.ref.SoftReference来使用。 一个持有软引用的对象,不会被JVM很快回收, JVM会根据当前的使用状况来判断何时回收.当堆使用率临近阈值时,才会去回收软引用的对象。只要有足够的内存,软引用便可能在内存中存活相对长一段时间。因此,软引用可以用于实现对内存敏感的Cache.
弱引用:
在系统GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。
虚引用
一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。但试图通过虚引用的get()方法去跌强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪回收过程。
WeakHashMap类及其实现
WeakHashMap 是弱引用的一种典型应用,它可以作为简单的缓存表解决方案。
改善系统性能的技巧
1. 慎用异常
try catch 应用与循环体之外
2. 使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快。
其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。
局部变量的访问速度远远高于类的成员变量。
3. 位运算代替乘除法
4. 替换Switch .
这一条应该注意的是一个新的思路问题, 使用不同的方式代替switch 语句。
这里的例子性能测试起来, switch 的性能反而更好一些。
package com.oscar999.performance.skill;public class SwitchReplaceSkill {/*** @param args*/protected void oldMethod() {int re = 0;for (int i = 0; i < 100000000; i++) {re = switchInt(i);}}public void newMethod() {int re=0;int[] sw= new int[]{0,3,6,7,8,10,16,18,44};for(int i = 0; i < 100000000; i++) {re = arrayInt(sw,i);}}protected int switchInt(int z) {int i = z % 10 + 1;switch (i) {case 1:return 3;case 2:return 6;case 3:return 7;case 4:return 8;case 5:return 10;case 6:return 16;case 7:return 18;case 8:return 44;default:return -1;}}protected int arrayInt(int[] sw,int z){int i=z%10+1;if(i>8||i<1){return -1;}else{return sw[i];}}public static void main(String[] args) {// TODO Auto-generated method stubSwitchReplaceSkill skill = new SwitchReplaceSkill();long begTime = System.currentTimeMillis(); skill.oldMethod();long endTime = System.currentTimeMillis(); System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis(); skill.newMethod();endTime = System.currentTimeMillis(); System.out.println("New Method use: "+(endTime-begTime));}}
5. 一维数组代替二维数组
直接看例子:
package com.oscar999.performance.skill;public class OneDime2TwoDime {protected void oldMethod(){int[][] array = new int[1000][1000];int re = 0;for(int k=0;k<100;k++)for(int i=0;i<array[0].length;i++)for(int j=0;j<array[0].length;j++)array[i][j] = i;for(int k=0;k<100;k++)for(int i=0;i<array[0].length;i++)for(int j=0;j<array[0].length;j++)re=array[i][j]; }protected void newMethod(){int[] array = new int[1000000];int re = 0;for(int k=0;k<100;k++)for(int i=0;i<array.length;i++)array[i] = i;for(int k=0;k<100;k++)for(int i=0;i<array.length;i++)re = array[i]; } /*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubOneDime2TwoDime skill = new OneDime2TwoDime();long begTime = System.currentTimeMillis(); skill.oldMethod();long endTime = System.currentTimeMillis(); System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis(); skill.newMethod();endTime = System.currentTimeMillis(); System.out.println("New Method use: "+(endTime-begTime));}}
Old Method use: 453
New Method use: 281
一维数字用的时间少了很多。
6. 提取表达式
有些重复运算的部分可以提取出来。
package com.oscar999.performance.skill;public class ExtraExpression {protected void oldMethod(){double d = Math.random();double a = Math.random();double b = Math.random();double e = Math.random();double x,y;for(int i=0;i<10000000;i++){x = d*a*b/3*4*a;x = e*a*b/3*4*a;}}protected void newMethod(){double d = Math.random();double a = Math.random();double b = Math.random();double e = Math.random();double x,y,t;for(int i=0;i<10000000;i++){t = a*b/3*4*a;x = d*t;x = e*t;}}/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubOneDime2TwoDime skill = new OneDime2TwoDime();long begTime = System.currentTimeMillis(); skill.oldMethod();long endTime = System.currentTimeMillis(); System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis(); skill.newMethod();endTime = System.currentTimeMillis(); System.out.println("New Method use: "+(endTime-begTime));}}
Old Method use: 109
New Method use: 79
7. 展开循环
展开循环是一种在极端情况下使用的优化手段,因为展开循可能会影响代码的可读性和可维护性, 所以取舍之间, 就要根据实际状况来看了。
看例子:
package com.oscar999.performance.skill;public class ExpandCycle {protected void oldMethod(){int[] array = new int[9999999];for(int i=0;i<9999999;i++){array[i]=i;}}protected void newMethod(){int[] array = new int[9999999];for(int i=0;i<9999999;i+=3){array[i]=i;array[i+1]=i+1;array[i+2]=i+2;}}/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubExpandCycle skill = new ExpandCycle();long begTime = System.currentTimeMillis(); skill.oldMethod();long endTime = System.currentTimeMillis(); System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis(); skill.newMethod();endTime = System.currentTimeMillis(); System.out.println("New Method use: "+(endTime-begTime));}}
Old Method use: 78
New Method use: 47
8. 布尔运算代替位运算
虽然位运算的速度远远高于算术运算,但是在条件判断时,使用位运算替代布尔运算却是非常错误的选择。
对于"a&&b&&c", 只要有一项返回 false, 整个表达式就返回 false.
package com.oscar999.performance.skill;public class BooleanBit {protected void oldMethod(){boolean a = false;boolean b = true;int d = 0;for(int i=0;i<10000000;i++)if(a&b&"Java_Perform".contains("Java"))d = 0;}protected void newMethod(){boolean a = false;boolean b = true;int d = 0;for(int i=0;i<10000000;i++)if(a&&b&&"Java_Perform".contains("Java"))d = 0; }/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubBooleanBit skill = new BooleanBit();long begTime = System.currentTimeMillis(); skill.oldMethod();long endTime = System.currentTimeMillis(); System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis(); skill.newMethod();endTime = System.currentTimeMillis(); System.out.println("New Method use: "+(endTime-begTime));}}
Old Method use: 265
New Method use: 32
9. 使用 arrayCopy()
Java API 提高了数组复制的高效方法: arrayCopy().
这个函数是 native 函数, 通常native 函数的性能要优于普通的函数, 仅出于性能考虑, 在软件开发时, 应尽可能调用native 函数。
package com.oscar999.performance.skill;public class ArrayCopy {protected void oldMethod(){int size = 100000;int[] array = new int[size];int[] arraydst = new int[size];for(int i=0;i<array.length;i++){array[i]=i;}for(int k=0;k<1000;k++)for(int i=0;i<size;i++)arraydst[i]=array[i];}protected void newMethod(){int size = 100000;int[] array = new int[size];int[] arraydst = new int[size];for(int i=0;i<array.length;i++){array[i]=i;}for(int k=0;k<1000;k++)System.arraycopy(array, 0, arraydst, 0, size);}/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubArrayCopy skill = new ArrayCopy();long begTime = System.currentTimeMillis(); skill.oldMethod();long endTime = System.currentTimeMillis(); System.out.println("Old Method use: "+(endTime-begTime));begTime = System.currentTimeMillis(); skill.newMethod();endTime = System.currentTimeMillis(); System.out.println("New Method use: "+(endTime-begTime));}}
Old Method use: 156
New Method use: 63
10. 使用 Buffer 进行 I/O 操作
除NIO 外, 使用Java 进行I/O 操作有两种基本方式
1. 使用基于InputStream 和 OutoutStream的方式
2. 使用Writer 和Reader
无论使用哪种方式进行文件I/O , 如果能合理地使用缓冲, 就能有效提高I/O 的性能
package com.oscar999.performance.skill;import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;public class BufferStream {protected void oldMethod() {int count = 10000;try {DataOutputStream dos = new DataOutputStream(new FileOutputStream("testfile.txt"));for (int i = 0; i < count; i++)dos.writeBytes(String.valueOf(i) + "\r\n");dos.close();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}protected void newMethod(){int count = 10000;try{DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testfile.txt"))); for (int i = 0; i < count; i++)dos.writeBytes(String.valueOf(i) + "\r\n");dos.close();}catch(Exception e){e.printStackTrace();}}/*** @param args*/public static void main(String[] args) {// TODO Auto-generated method stubBufferStream skill = new BufferStream();long begTime = System.currentTimeMillis();skill.oldMethod();long endTime = System.currentTimeMillis();System.out.println("Old Method use: " + (endTime - begTime));begTime = System.currentTimeMillis();skill.newMethod();endTime = System.currentTimeMillis();System.out.println("New Method use: " + (endTime - begTime));}}
Old Method use: 516
New Method use: 0
11. 使用clone() 代替new
对于重量级对象, 优于对象在构造函数中可能会进行一些复杂且耗时额操作, 因此, 构造函数的执行时间可能会比较长。Object.clone() 方法可以绕过对象构造函数, 快速复制一个对象实例。由于不需要调用对象构造函数, 因此, clone 方法不会受到构造函数性能的影响, 快速生成一个实例。
12. 静态方法替代实例方法
使用static 关键字描述的方法为静态方法, 在Java 中, 优于实例方法需要维护一张类似虚函数表的结构,以实现对多态的支持。 与静态方法相比, 实例方法的调用需要更多的资源。 因此,对于一些常用的工具类方法,没有对其进行重载的必要,那么将它们声明为static, 便可以加速方法的调用。
Java 性能优化系列之1[设计与程序优化]相关推荐
- 【MySQL性能优化系列】百万数据limit分页优化
背景 众所周知,在使用limit分页过程中,随着前端传过来的PageSize越来越大,查询速度会越来越慢.那有什么优化的办法呢? 本文将通过百万数据表进行演示和优化, 欲知详情,请看下文分解. lim ...
- iOS性能优化系列篇之“列表流畅度优化”工具篇
这一篇文章是iOS性能优化系列文章的的第二篇,主要内容是关于列表流畅度的优化.在具体内容的阐述过程中会结合性能优化的总体原则进行分析,所以建议大家在阅读这篇文章前先阅读一下上一篇文章:iOS性能优化系 ...
- 百度App网络深度优化系列(一):DNS优化
一.前言 网络优化是客户端几大技术方向中公认的一个深度领域,所以百度App给大家带来网络深度优化系列文章,其中包含系列<一>DNS优化,系列<二>连接优化,系列<三> ...
- Sql优化系列之(1)__where子句条件优化
1.为什么要进行SQL优化 系统优化中一个很重要的方面就是SQL语句的优化.对于海量数据,劣质SQL语句和优质SQL语句之间的速度差别可以达到上百倍,可见对于一个系统不是简单地能实现其功能就可,而是要 ...
- 为JAVA性能而设计(一)
为JAVA性能而设计(一) 发布时间:2007-1-9 15:51:42 来源:JavaWorld 作者:Brian Go- 为JAVA性能而设计(二) 发布时间:2007-1- ...
- 赠书:《Java性能优化实践》,众多业内大佬推荐阅读
没有捷径可走的 Java 性能优化 多年来,用 Google 搜索 Java performance tuning,出现的三篇最热门文章之一是于 1997 年到 1998 年左右发表的文章,这篇文章在 ...
- 新书上市 | 《Java性能优化实践》,众多业内大佬推荐阅读
没有捷径可走的 Java 性能优化 多年来,用 Google 搜索 Java performance tuning,出现的三篇最热门文章之一是于 1997 年到 1998 年左右发表的文章,这篇文章在 ...
- 调整了一下JVM内存,程序快了700%,Java程序优化笔记
Java程序性能优化是一个永远逃不开的话题,优化无处不在!一个偶然间,小编得到了一份Java程序性能优化的笔记,小编读完这份笔记后大为惊叹,实在是写的太好了,不忍独乐乐,于是把这份笔记免费分享出来,供 ...
- WPF性能调试系列 – 内存监测
原文:WPF性能调试系列 – 内存监测 WPF性能调试系列文章: WPF页面渲染优化:Application Timeline WPF页面业务加载优化:Ants Performance Profile ...
最新文章
- docker学习笔记(六)docker-compose
- SpringBoot入门教程(一)详解intellij idea搭建SpringBoot
- Android存储数据方式
- axure怎么做5秒倒计时_罗胖60秒:怎么做一个课程?
- 【java】为什么HashMap桶中节点个数超过8才转为红黑树?
- Microsoft visual studio关闭安全检查
- Lua中实现类似C#的事件机制
- php.ini添加的变量读取,php用ini_get获取php.ini里变量值的方法
- nginx优化配置(转)
- 开启本地git权限_Git入门使用和常见操作
- 电脑tf卡检测不到_电脑不认TF卡,有什么方法
- 生命游戏(Anylogic实现)
- Handler 机制简介
- 计算机网络的最大优点,什么是计算机网络最突出的优点
- Safari安装使用JsonView插件
- 市场的结构,各种投资方法的此消彼长,没有所谓圣杯
- oracle中dlink使用,create dlink(oracle)
- 传输层协议TCP—RTTM(11)
- linux命令行测网速
- 联想E440打开VT(虚拟化)