优化代码性能

如果在一个用户界面中循环做分配对象操作,这样会产生一个定期的垃圾收集事件,使得界面会比较卡,因此应该避免创建短期的临时对象,越少的对象创建意味着越少的垃圾回收

Date date=new Date();
if(requiredCondition){
    ....
}

可以将生成Date()对象的代码放入if条件语句中,这样的话,只有在if条件成立的时候才创建对象,避免了不必要的创建对象工作

在对象初始化的时候尽量声明其大小
Vector v=new Vector();

当创建一个vector对象时,当他的容量多于我们所声明的大小,Vector会默认生成一个两倍大小的新的Vector,然后再将原Vector中的内容复制一份到新Vector,这样做的后果导致了在垃圾回收时产生的性能问题,所以强烈建议在初始化时声明其大小

不要多次声明对象
使用自身方法

处理字符串的时候,尽可能多的使用String.indexOf(),String.lastIndexOf()等对象自身带有的方法,因为这些方法使用C/C++来实现,要比在一个Java循环中做同样的事情快10-100倍

使用虚拟优于使用接口

假设你有个HashMap对象,则可以声明他是一个HashMap或只是一个Map,下面是演示代码:

Map myMap1=new HashMap();
HashMap myMap2=new HashMap();

究竟哪个更好呢,一般来说我们会使用Map因为他允许我们改变Map接口执行上面的任意一个方法,但是相对于通过具体的引用进行虚拟函数的调用,通过接口引用来调用会花费两倍以上的时间,所以尽量使用第二种方式

使用静态优于使用虚拟

如果没有必要去访问对象的外部,那么使我们的方法成为静态方法,他会被更快的调用,因为他不需要一个虚拟函数导向表,这同时也是一个很好地实践,因为他告诉我们如何区分方法的性质,调用这个方法不会改变对象的状态

尽量避免使用内在的set,get方法

我们应该在外部调用时使用get和set函数,但是在内部调用时,我们应该直接调用

优化代码变量,使用StringBuffer

当使用

String str="welcome"+"to";

我们应该用StringBuffer替代

StringBuffer sb=new StringBuffer(50);
sb.append("Welcome");
sb.append("to");

StringBuffer的最大长度设置为50,这使得在使用StringBuffer的过程中,不需要考虑自增长的问题

避免使用枚举

枚举变量非常方便但是是以牺牲执行速度和大幅增加文件体积为前提的,他会产生一个900字节的.class文件,在他被首次调用时,这个类会调用初始化方法来准备每个枚举变量,每个枚举项都会被声明一个静态变量,并被赋值,然后将这些静态变量放在一个名为$VALUES的静态数组变量中

慎重使用for增强型循环语句for-each

for-each在数组里面表现的很好,但是当和iterable对象一起使用时要谨慎,因为这里多了一个对象的创建

避免在条件判定语句中重复调用函数
for(int i=0;i

应该写成下面的方式,因为这样会减少时间开销

int j=str.length();
for (int i=0;i
建议使用Drawable,而不是Bitmap
getResource().getDrawable(R.drawable.img);
BitmapFactory.decodeResource(getResource(),R.drawable.img);

如此,加载1000个Drawable对象完全没有问题,但是当使用Bitmap时,加载到第8张图片程序就会报OOM,由此可以看出,使用Drawable保存图片对象,占用更小的内存空间,而使用Bitmap对象,则会占用相对较大的内存空间

加载Apk文件和Dex文件
  1. 预置应用,Android会在系统编译后,生成优化文件,以ODEX后缀结尾,这样在发布时除APK文件(不包含Dex)外,还有一个相应的ODEX文件
  2. 对于非预置应用,在运行前,Android会优化DEX文件,在第一次启动应用时,执行文件的dex被优化成DEY文件并放在/data/dalvik-cache目录,如果在使用APK文件的过程中不发生变化,则就不会重新生成DEX文件,这样便加快了以后的启动速度

dex文件由header,string_ids,type_ids,proto_ids,field_ids,method_ids,class_defs,data等几部分组成

dx工具会将同一个应用的所有class文件整合到一个dex文件中,这样就减少了整体的文件尺寸,IO操作也提高了类的查找速度,原来每个class文件中的常量池,在dex文件中由一个常量池来统一管理

zygote是一个虚拟机进程,同时也是一个虚拟机实例的孵化器,每当系统要求执行一个Android应用程序,Zygote就会fork出一个子进程来执行该应用程序,zygote进程是在系统启动时产生的,他会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等操作,而在系统需要一个新的虚拟机实例时,zygote通过复制自身,最快的提供给系统,对于一些只读的系统库,所有虚拟机实例都和zygote共享一块内存区域,大大节省了内存开销

Dalvik虚拟机垃圾收集机制
  • 引用计数法
    使用一个变量来记录这块内存或者对象的使用次数,当一个COM对象给不同的线程使用时,由于不同线程的生命周期不一致,因此没有办法知道这个COM对象到底在哪个线程被删除,此时可以通过引用计数来控制删除,否则需要在不同线程之间添加同步机制,这样是非常麻烦和复杂的,如果COM对象有很多,则基本上就不能实现了

引用计数法的优点
1 在对象变成垃圾时,可以马上进行回收,回收效率和成本都是最低
2 内存使用率最高,基本上没有时间花费,不需要把所有访问COM对象线程都停下来

引用计数法的缺点:
1 引用计数会影响执行效率,每引用一次都需要更新引用计数,在java里是由编译程序来控制的,因此引用次数非常多
2 不能解决交叉引用或者环形引用的问题

  • 标记清除算法
    依赖于对所有存活对象进行一次全局遍历来确定哪些对象可以回收,遍历的过程从根出发,找到所有可达对象,其他不可到达的对象就是垃圾对象,可被回收

与引用计数不同的是标记清除算法不需要监测每一次内存分配和指针操作,只需要在标记阶段进行一次统计就可以了,标记清除算法可以非常自然的处理环形问题,另外在创建对象和销毁对象时少了操作引用计数值的开销,不过该算法有个缺点就是需要在标记和清除阶段中把所有对象停止执行,在垃圾回收器运行过程中,应用程序必须暂时停止,并等到垃圾回收器全部运行完成后,才能重新启动应用程序运行

并发标记
为了运行垃圾收集,需要停止虚拟机的运行,这可能会导致程序较长时间的停顿,垃圾收集的主要工作位于标记阶段,虚拟机使用了并发标记的技术以缩短停顿时间,在并发标记中引入了一个单独的gc线程,由该线程去跟踪自己的根集合中所有可访问的对象,同时所有其他的线程也在运行,这也是并发一词的含义,但是为了回收内存,即运行清除阶段,必须停止虚拟机的运行,这会导入一个问题,即在gc线程标记对象的时候,其他线程的运行又引入了新的访问对象,因此在清除阶段,又重新运行了标记阶段,但是在这个阶段,对于已经标记的对象来说,可以不用继续递归追踪了,这样从一定程度上降低了程序的停顿时间。

垃圾回收的时机

垃圾回收主要有两种方式,一种是虚拟机线程自动运行,一种是手动运行,所谓自动方式,就是虚拟机创建一个线程,这个线程定时进行,虚拟机在初始化的时候就创建这个线程

触发垃圾收集的原因

1 内存分配失败时触发
2 当分配的对象大小超过384KB时触发
3 对垃圾收集的显式调用(System.gc)
4 外部内存分配失败时触发

Dalvik虚拟机采用了基于寄存器的指令集,而JVM采用了基于zhan

你可能感兴趣的:(优化代码性能)