代码调优不是为了修复bug,而是对正确的代码进行修改,以提高其性能。
代码调优通常是针对小规模的变化,比如针对类,某条代码执行的路径,或者更简单的,几行代码的变化。
代码调优是最后做的工作,其他方面都无法再优化时,再考虑代码调优。
1.调优不会减少代码行数
代码行数与性能之间没有必然的联系
2.调优时不应猜测怎样会提高性能,而应该有明确的目标并衡量结果
通过度量发现热点和瓶颈,代码调优建立在对程序性能的精确度量基础上。 当程序做过某些调整之后,要重新profiling并重新了解需要优化的性能瓶颈,微小的变化能导致优化方向大不相同。在一个环境下提高性能的调优在另一个环境下可能降低性能。
3.不要边写程序边调优
在没有完整程序之前,无法获知性能瓶颈
在开发阶段进行调优,容易忽视其他更重要的质量指标
4.性能从不是追求的第一目标,正确性比性能更重要
调优不是代码性能优化的第一选择。
代码调优的过程:
备份→通过度量发现热点瓶颈→分析原因,评判代码调优的必要性→调优→每次调优后都要重新度量→若无效果或负效果,则回滚
很多。I/O,paging,operators,object creation,GC…
强制client只能创建一个object实例,避免因为new操作所带来的时空性能(尤其是GC)的损失,也便于复用。
优点:只能获取到一个实例
减小命名空间(?)
通过类中封装的代码确保对象的重复使用,客户端易于操作
重复使用意味着更好的性能:
节约了创建新对象的时间,节省内存
单例模式:
设置静态变量来存储单一实例对象
将构造器设置为private,从而client无法new
在构造器中new新实例
提供静态方法来获取单一实例对象
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {...}
public static Singleton getInstance() { return instance; }
// other operations and data
}
在装载到程序时创建类
Lazy Load:在需要的时候再new,而非提前构造出来
public class Singleton {
private static final Singleton instance = null;
private Singleton() {...}
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
// other operations and data
}
在应用中不同部分共享使用objects,降低大量objects带来的时空代价。
每个对象有内部特征和外部特征,外部特征需要在不同场合分别指派/计算其值。
ConcreteFlyweight里存储的状态是内部特征
FlyweightFactory:创建,管理flyweight对象。客户端请求flyweight对象时,FlyweightFactory返回或创建一个实例。
例:
定义“外特征”:
public enum Color {Red, Green, Blank, Blue, Yellow}
可共享对象的抽象接口:
public interface IAlien {
String Shape = null; //intrinsic state
String getShape();
Color getColor(int madLevel); //extrinsic state
}
两种具有不同内特征的共享对象:
class LargeAlien implements IAlien{
private String shape = "Large Shape";
public String getShape() { return shape; }
public Color getColor(int madLevel) {
if (madLevel == 0)
return Color.Green;
else if (madLevel == 1)
return Color.Red;
else return Color.Blue;
}
}
class LittleAlien implements IAlien {
private String shape = "Little Shape";
public String getShape() { return shape; }
public Color getColor(int madLevel) {
if (madLevel == 0)
return Color.Red;
else if (madLevel == 1)
return Color.Blue;
else
return Color.Green;
}
}
public class AlienFactory {
private Map list = new HashMap<>();
public void SaveAlien(String index, IAlien alien) {
list.put(index,alien);
}
public IAlien GetAlien(String index) {
return list.get(index);
}
}
client:
AlienFactory factory = new AlienFactory();
factory.SaveAlien("LargeAlien", new LargeAlien());
factory.SaveAlien("LittleAlien", new LittleAlien());
IAlien a = factory.GetAlien("LargeAlien");
IAlien b = factory.GetAlien("LittleAlien");
System.out.println("Showing intrinsic states...");
System.out.println("Alien of type LargeAlien is " + a.getShape());
System.out.println("Alien of type LittleAlien is " + b.getShape());
System.out.println("Showing extrinsic states...");
System.out.println("Alien of type LargeAlien is " + a.getColor(0).toString());
System.out.println("Alien of type LargeAlien is " + a.getColor(1).toString());
System.out.println("Alien of type LittleAlien is " + b.getColor(0).toString());
System.out.println("Alien of type LittleAlien is " + b.getColor(1).toString());
Flyweight | Singleton |
---|---|
同一事物有不同表现形式 | 统一用一个实例表示 |
immutable | mutable |
当直接new对象的时空代价高时,通过clone而不是new来创建object
对于普通ADT,clone不会带来性能的提升。
需要override clone()方法,并将可见性设为public
Object.clone()是protected:它可以被同包(java.lang)下以及它(java.lang.Object)的子类访问。
自定义类无法直接使用Object.clone():没有访问权限(invisible),故需要override。 (??)
如果在没有实现Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常。
Shape clonedShape1 = (Shape) rectangle.clone();
Shape clonedShape2 = (Shape) square.clone();
clone()方法返回的是object类型的对象,需要强转
拷贝:
引用拷贝:两个reference变量指向同一个对象
对象拷贝:两个reference变量指向不同对象,包括深拷贝和浅拷贝
浅拷贝:使用一个已知实例的成员变量对新创建实例的成员变量逐个赋值(缺省)
深拷贝:复制对象的所有非引用成员变量值,并为引用类型的成员变量创建新的实例,并且初始化为原对象的值。
对象复用
把一组已初始化的可用对象作为pool,而不是需要时创建对象,不需要时再回收对象。
客户端从pool中请求对象,在返回的对象上进行操作
操作结束后,将对象返回到pool中,而不是丢弃它
代价:原本可被GC的对象,现在要留在pool中,导致内存浪费——用空间换时间
Singleton和Flyweight本质也都是Object Pool
不保留对象的多个拷贝,仅保留少量Object
字符串常量池
例:只用到少量integer对象的情况:将对象规范化
public class IntegerManager {
public static final Integer ZERO = new Integer(0);
public static final Integer ONE = new Integer(1);
public static final Integer TWO = new Integer(2);
……
public static final Integer NINE = new Integer(9);
public static final Integer TEN = new Integer(10);
}
如果用普通方式创建Integer对象,会重复创建很多对象,而且比较的时候要用integerValue()比较
规范化之后减少了new对象的次数和垃圾回收的次数,且可以通过地址比较
规范化的技术也常用于用int取代其他常量object
例:用这样的枚举类型取代字符串"male",“female”
public interface GENDER {
public static final int FEMALE=1;
public static final int MALE=2;
}
枚举类型的时空性能更好:占内存小,通过地址比较,比较块
canonicalization techniques:减少创建object的数量,避免GC的代价。
pooling technique:通过复用对象减少了创建对象,从而减少GC;持续使用已分配内存的对象,减少内存释放。
另一项技术:避免使用不需要的对象
String string = “55”; 在String pool中分配,无需GC
int theInt = new Integer(string).intValue(); 创建一个Integer
int theInt = Integer.parseInt(string); 无需创建对象
尽可能使用简单数据类型,对类的成员变量也是如此。
内存中基本数据类型的变量也需要回收,但回收代价低:它和它所属于的对象同时被回收,所以影响小
例如:如果一个对象有一个int类型的成员变量,回收该对象的实例时GC一次;如果该对象有一个Integer类型的成员变量,回收该对象的实例时GC两次(Integer和这个对象本身)。
局部的简单数据类型的变量在栈中存储,不需GC。
使用简单数据类型替代复杂数据类型:
能用int存储诸如"1492", "1997"的数据就不要用String
用int/long代替Date对象 (但转换需要的计算可能花费更多时间)
其他建议:
1.减少临时对象的使用,尤其是在循环里
2.优先使用StringBuffer而不是用“+”连接字符串
3.分辨哪些方法直接在对象上操作,而哪些方法返回的是对象的复制
4.使用基本数据类型时,避免使用处理Object的类,例如,没有必要把ints包装成Integers,再使用Vector,而是可以实现一个直接存储ints的IntVector类
(前三点没有看懂,哪位读者朋友看懂了麻烦联系我一下给我讲讲)
对于无法避免创建大量对象的情况,可以把创建对象的时间转移到程序中有空闲的时候,并储存这些对象直到它们被使用。
Late (Lazy) Initialization:在第一次使用对象时才创建对象
通过检查该对象是否为空来判断它是否被初始化
public getSomething(){
if (something = = null)
something = defaultSomething( );
return something;
}
字符串常量池是堆中的一块存储区域,保存对字符串对象的引用。
创建String对象的方式:
1.String s = “java”;
JVM在字符串常量池中查找“java”,如果找到,取它的引用,如果没找到,在字符串常量池中创建“java”。
2.String s = new String(“java”);
在堆中创建对象。
String s1 = "java";
String s2 = "java";
String s3 = new String("java");
如果字符串可以在编译阶段确定,使用“+”效率更高
如果字符串在运行阶段确定,使用StringBuffer效率更高
使用不复制字符串中字符的,效率比较高的方法
避免使用使用复制字符串中字符的,效率比较低的方法
1.在能得出答案时自动停止判断
(1)short-circuit evaluation:如“if ( x>5 && x<10 )”。也是条件操作的一部分,相当于:
if (x>5)
{ if (x <10 ) { …}
}
(2)循环中,如果已经得出结果,跳出循环
2.用查表替代复杂的逻辑判断
3.使用Lazy Evalution: avoids doing any work until the work is needed
4.将判断外提:循环里的分支判断会在循环每次执行时都执行,如果循环过程中分支判断不变,可以把判断提到循环外面
5.减少数组维度