摘要:代码优化,图片优化,工具优化

1、尽早释放无用对象的引用。 好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为 null,暗示垃圾收集器来收集该对象,防止发生内存泄露。对于仍然有指针指向的实例,jvm 就不会回收该资源,因为垃圾回收会将值为 null 的对象作为垃圾,提高 GC 回收机制效率;

2、定义字符串应该尽量使用 String str="hello"; 的形式 ,避免使用 String str = new String ("hello"); 的形式。因为要使用内容相同的字符串,不必每次都 new 一个 String。例如我们要在构造器中对一个名叫 s 的 String 引用变量进行初始化,把它设置为初始值,应当这样做:

public class Demo {
    private String s;
    ...
            public Demo {
            s = "Initial Value";
            }
    ...
}

//而不是
s = new String("Initial Value");

后者每次都会调用构造器,生成新对象,性能低下且内存开销大,并且没有意义,因为 String 对象不可改变,所以对于内容相同的字符串,只要一个 String 对象来表示就可以了。也就说,多次调用上面的构造器创建多个对象,他们的 String 类型属性 s 都指向同一个对象。

3、我们的程序里不可避免大量使用字符串处理,避免使用 String,应大量使用 StringBuffer ,因为 String 被设计成不可变 (immutable) 类,所以它的所有对象都是不可变对象,请看下列代码;

String s = "Hello"; 
s = s + " world!";

在这段代码中,s 原先指向一个 String 对象,内容是 "Hello",然后我们对 s 进行了 + 操作,那么 s 所指向的那个对象是否发生了改变呢?答案是没有。这时,s 不指向原来那个对象了,而指向了另一个 String 对象,内容为 "Hello world!",原来那个对象还存在于内存之中,只是 s 这个引用变量不再指向它了。 通过上面的说明,我们很容易导出另一个结论,如果经常对字符串进行各种各样的修改,或者说,不可预见的修改,那么使用 String 来代表字符串的话会引起很大的内存开销。因为 String 对象建立之后不能再改变,所以对于每一个不同的字符串,都需要一个 String 对象来表示。这时,应该考虑使用 StringBuffer 类,它允许修改,而不是每个不同的字符串都要生成一个新的对象。并且,这两种类的对象转换十分容易。

4、尽量少用静态变量 ,因为静态变量是全局的,GC 不会回收的;

5、尽量避免在类的构造函数里创建、初始化大量的对象 ,防止在调用其自身类的构造器时造成不必要的内存资源浪费,尤其是大对象,JVM 会突然需要大量内存,这时必然会触发 GC 优化系统内存环境;显示的声明数组空间,而且申请数量还极大。 以下是初始化不同类型的对象需要消耗的时间:

运算操作 	示例		标准化时间
本地赋值	i = n 		1.0
实例赋值	this.i = n 	1.2
方法调用	Funct() 	5.9
新建对象	New Object() 	980
新建数组	New int[10]	3100

从表 1 可以看出,新建一个对象需要 980 个单位的时间,是本地赋值时间的 980 倍,是方法调用时间的 166 倍,而新建一个数组所花费的时间就更多了。

6、尽量在合适的场景下使用对象池技术 以提高系统性能,缩减缩减开销,但是要注意对象池的尺寸不宜过大,及时清除无效对象释放内存资源,综合考虑应用运行环境的内存资源限制,避免过高估计运行环境所提供内存资源的数量。

7、大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理 ,然后解决一块释放一块的策略。

8、不要在经常调用的方法中创建对象 ,尤其是忌讳在循环中创建对象。可以适当的使用 hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次 new 之后又丢弃。

9、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error 的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

10、尽量少用 finalize 函数 ,因为 finalize () 会加大 GC 的工作量,而 GC 相当于耗费系统的计算能力。

11、不要过滥使用哈希表 ,有一定开发经验的开发人员经常会使用 hash 表(hash 表在 JDK 中的一个实现就是 HashMap)来缓存一些数据,从而提高系统的运行速度。比如使用 HashMap 缓存一些物料信息、人员信息等基础资料,这在提高系统速度的同时也加大了系统的内存占用,特别是当缓存的资料比较多的时候。其实我们可以使用操作系统中的缓存的概念来解决这个问题,也就是给被缓存的分配一个一定大小的缓存容器,按照一定的算法淘汰不需要继续缓存的对象,这样一方面会因为进行了对象缓存而提高了系统的运行效率,同时由于缓存容器不是无限制扩大,从而也减少了系统的内存占用。现在有很多开源的缓存实现项目,比如 ehcache、oscache 等,这些项目都实现了 FIFO、MRU 等常见的缓存算法

12. 充分利用单例机制。实用单例可以减少对资源的加载,缩短运行的时间,提高系统效率。但是,单例并不是所有地方都适用于。简单来说,单例可以适用于以下两个方面:1. 控制资源的使用,通过线程同步来控制资源的并发访问;2. 控制实例的产生,以达到节约资源的目的;

13. 使用 final 修饰符,带有 final 修饰符的类是不可派生的。在 Java 核心 API 中,有许多应用 final 的例子,例如 java.lang.String。为 String 类指定 final 防止了使用者覆盖 length () 方法。另外,如果一个类是 final 的,则该类所有方法都是 final 的。Java 编译器会寻找机会内联(inline)所有的 final 方法(这和具体的编译器实现有关)。此举能够使性能平均提高 50%。

14. 尽量使用局部变量,调用方法时传递的参数以及在调用中创建的临时变量都保存在分配给改方法的栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。

15. 处理好包装类型和基本类型两者的使用场所,虽然包装类型和基本类型在使用过程中是可以相互转换,但它们两者所产生的内存区域是完全不同的,基本类型数据产生和处理都在栈中处理,而包装类型是对象,是在堆中产生实例。在集合类对象,有对象方面需要的处理适用包装类型,其他的情况,建议提倡使用基本类型。

16. 学会用 StringBuilder 和 StringBuffer,这个两个类的区别就不用说了吧,单线程使用 StringBuilder,多线程情况下使用 StringBuffer,这样性能会有很大提升。

17. 学会使用 HashMap、ArrayList,HashTable、Vector 等使用在多线程的场合,内部使用了同步机制,这个会降低程序的性能。

18. 深入理解 HashMap 原理,当你要创建一个比较大的 hashMap 时,充分利用另一个构造函数,public HashMap (int initialCapacity, float loadFactor) 避免 HashMap 多次进行了 hash 重构,扩容是一件很耗费性能的事,在默认 initialCapacity 只有 16,而 loadFactor 是 0.75,需要多大的容量,你最好能准确的估计你所需要的最佳大小,同样的 Hashtable,Vectors 也是一样的道理。

19. 减少对变量的重复计算,并且在循环中应该避免使用复杂的表达式,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。

//如
for(int i=0;i<list.size();i++)
//应该改写为:
for(int i=0,len=list.size();i<len;i++)
//或者
for(int i = list.size(); I > -1; i--)

20. 避免不必要的对象创建,

//如
A a = new A();
    if(i==1){list.add(a);
}
//应该改为
if(i==1){
    A a = new A();
    list.add(a);
}

21. 尽量在 finally 块中释放资源,程序中使用到的资源应当被释放,以避免资源泄漏。这最好在 finally 块中去做。不管程序执行的结果如何,finally 块总是会执行的,以确保资源的正确关闭。

22. 使用移位来代替乘法或者除法('a/b',仅适合 2^n 情况)的操作,"/" 是一个代价很高的操作,使用移位的操作将会更快和更有效,

//如
int num = a / 4;
int num = a / 8;
//应该改为
int num = a >> 2;
int num = a >> 3;
//但注意的是使用移位应添加注释,因为移位操作不直观,比较难理解
//同样的,对于'*'操作,使用移位的操作将会更快和更有效
//如
int num = a * 4;
int num = a * 8;
//应该改为
int num = a << 2;
int num = a << 3;

23. 确定 StringBuffer 的容量,StringBuffer 的构造器会创建一个默认大小(通常是 16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建 StringBuffer 的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。如:

StringBufferbuffer = new StringBuffer(1000);

24. 尽量早释放无用对象的引用,大部分时,方法局部引用变量所引用的对象 会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部,引用变量显式设为 null。

25. 尽量避免使用 split。除非是必须的,否则应该避免使用 split,split 由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资源,如果确实需要频繁的调用 split,可以考虑使用 apache 的:

StringUtils.split(string,char);

,频繁 split 的可以缓存结果。

26.ArrayList & LinkedList。一个是线性表,一个是链表,一句话,随机查询尽量使用 ArrayList,ArrayList 优于 LinkedList,LinkedList 还要移动指针,添加删除的操作 LinkedList 优于 ArrayList,ArrayList 还要移动数据,不过这是理论性分析,事实未必如此,重要的是理解好数据结构。

27. 尽量使用 System.arraycopy () 代替通过来循环复制数组。System.arraycopy () 要比通过循环来复制数组快的多

28. 慎用异常。当创建一个异常时,需要收集一个栈跟踪 (stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。当需要创建一个 Exception 时,JVM 不得不说:先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。如果您创建一个 Exception ,就得付出代价。好在捕获异常开销不大,因此可以使用 try-catch 将核心内容包起来。从技术上讲,您甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是 throw 操作 —— 尽管在没有预先创建异常的情况下就抛出异常是有点不寻常。真正要花代价的是创建异常。幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则。

29. 如果需要使用经常用到的图片,可以使用 soft 应用类型。它尽可能把图片保存在内存中

30. 注意集合数据类型,包括数组、树、图、链表等数据结构,这些数据结构对 GC 来说,回收更为复杂。

参考:
https://my.oschina.net/chumingcheng/blog/635047

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Jalen Chu 微信支付

微信支付

Jalen Chu 支付宝

支付宝

Jalen Chu 公众号

公众号