• Run Faster-JAVA


     

    又好久没有写点啥了,平时都忙于工作,忙于应付工作中的问题,各种吸收却并没有好好的消化,该是"反刍"一下的时候了。

    本篇名叫"Run Faster,JAVA",其实JAVA发展到现阶段,无论是编译器优化还是运行时优化,都做的很好了,速度早已不像过去那样被人诟病,本篇只是自己日常工作的一些总结,很不全面,但是都是很实际且能立竿见影的,可以当低层次技术文章看看,当然最好能在实际的开发中尝试尝试。(PS:为照顾大家看技术文章容易犯怵的情绪,采用快餐式的讲解,不深入,不解释,只求精神上的共鸣,不喜欢技术的就当杀马特"随便瞄一眼吧。。。)

                常见的JAVA应用下容易出现瓶颈的因素:磁盘I/O,网络操作,CPU,数据库读写,应用单多线程,多线程锁竞争,JAVA GC等。针对这几个因素,一般的调优层次是: 设计调优--->代码调优--->JVM参数调优--->数据库调优--->OS调优,当实际应用出现瓶颈需要调优时,一般的步骤是:代码--->JVM参数--->数据库--->OS--->应用架构(设计),可以看到,首先需要调优的就是代码,那么我们今天先讲讲代码调优的一些口诀:

                1:多用设计模式(主要是单例,享元,观察者,访问者,装饰者,代理等),如果你写了两年以上代码,还没用过常见的模式,我只能说,赶紧洗洗换行当吧。学习设计模式我推荐<<Head First 设计模式>>一书,轻轻松松看下来,找几个例子熟悉,关键是平常开发中有意识的培养自己去实现,每个模式写一两个例子就都了然于胸了。至于外界盛赞的GOF<<设计模式>>,建议当常备参考书放到书桌旁有需要时参考就行了。

                2Buffer/Cache,缓冲/缓存思想,这个应该很容易理解。Buffer是说take it easy,slow down.Cache则是说take it ,you'll user it someday.

                3:多线程并行代替串行(要求一般串行间没有因果或前后关系),说白了就是用多线程替代单线程,充分利用CPU资源,当然很多在单线程下能正常执行的程序在多线程下可能会出现问题,用锁固然可以减少线程冲突,但是锁太多反正抑制了多线程的威力,而且容易出现锁等待,死锁等问题。尽管如此,多线程替代单线程可以大幅提高效率,尽量使用。

                4:应用负载均衡(JVM,需要处理好共享数据的一致性问题),如果应用将来做负载,那么在编写代码的时候就要考虑共享数据的一致性,共享缓存听起来是一个很诱人的东西,可是真正实现的时候会有很大的麻烦,别说你已经很熟悉MappedByteBuffer + RandomAccessFiletoo simple

                5:时间/空间的转换,缓存是这种转换的一种实现,当然还有生产者-消费者模式,如果你来不及计算所有的任务,花点内存先保存这些任务,然后启用线程慢慢计算。还有在循环中存储中间临时结果以备后续调用也算。

                6:使用ValueObject减少计算或请求次数,将请求封装到一起,尽量一次请求就把需要的参数给全,得到所有想要的结果。多次请求总是会耗损资源的,无论是HTTP,TCP还是普通方法调用请求。

    口诀说完了接下来就是招式了,优化的最高境界当然是无招胜有招,不过还得从简单的招式练起,这次先学下面几招入门级的:

                0):不要在循环体里定义引用并创建对象,将定义引用放到外面

                Object obj = null;

    for (int i = 0; i < 10000; ++i) {

       (Object) obj = new Object();

        System.out.println("obj= "+ obj);

    }

    1):避免重复初始化对象

    public class A {

        private Hashtable table = new Hashtable ();  //这个问题一般开发人员都会碰到吧

        public A() {        

            table = new Hashtable(); // Hashtable对象table初始化了两次

        }

    }

                2):字符串优化的几个地方

                            a:substring()方法速度很快,但是容易引起内存溢出问题,因为会保留对原串的引用,所以建议用new String(***.substring(参数))的模式

                            b:对字符串截取可用split,split内部调用的Pattern.complie.split,因此如果拆分规则比较复杂,每次Pattern.complie的时候会有损耗,可以考虑将Pattern.complie静态化然后直接调用split,这样只需要complie一次即可。当然还想速度快点的可以考虑StringTokenizer(sun不建议使用),想最快的可用indexOf+substring自己实现截取

                            c:对字符串的开头/结尾子串判断一般用startsWith/endsWith,由于他们和split一样是基于正则的,会有上面说过的问题。所以最好用charAt来自己实现更快

                            d:对字符串的相加,StringBuild最优,StringBuffer次之(线程安全),String.concat再次之,+/+=最次(此处不考虑静态常量编译器的优化)

                3)ArrayList(Vector)LinkedList比较

                            a:ArrayList基于数组(一块连续的内存空间),LinkedList基于双向链表,因此ArrayList的主要性能耗在扩展空间时数据复制,LinkedList耗在数据遍历(尤其是找中间的数据)

                            b:头尾的新增/删除对LinkedList没有压力,但是每次随机访问需要从头查找,会要亲命

                            c:在尾部添加对ArrayList没有压力,但从头部删除都会数组复制,会要亲命。

                            d:LinkedList的遍历,要么用for-Each,要么Iterator,不要用普通的ini i=0;i<size;i++,因为get(i)是随机查找,每次都从头或尾遍历,会要亲命

                            e:一般实现了RandomAccess接口的类如ArrayListVector才可以在遍历时放心使用get,而且遍历时要尽量减少重复的获取size或元素,用临时变量缓存之

                4)HashMap如果key是一个个重写了hashCode方法的对象,如果此hashCode方法放回相同的int值,则每次放入不同对象会到同一个桶下,同一桶下是用链表组织数据的,如果查找需要遍历链表,会要亲命。

                5)HashMap底层用了数组实现,使用大于等于initialCapacity并且是2的指数次幂作为数组大小,threshold为当前数组总容量与负载因子的乘积,即阀值,当实际容量超阀值     时会进行数组扩展复制,而复制会消耗CPU资源,因此适当的initialCapacity有利于提高性能。另外别忘了HashMap在多线程下并发put会有导致CPU满负荷的bug

                6)LinkedHashMap基于元素进入顺序或被访问先后顺序(被访问的元素放到最后),TreeMap基于传入的Comparetor参数或实现了Comparablekey对象进行排序,适用范围   不一样,前者可以用来实现LRU算法,后者在一致性Hash上大有作用。而HashSet,LinkedHashSet,TreeSet是对应Map的简单封装。

                7):使用NIO(ByteBuffer+Channel)可以提升I/O读写速率,注意duplicate(),asReadOnlyBuffer(),slice()都是共享原始缓存数据的,一般用文件内存映射MappedByteBuffer,结构        化散射接口ScatteringByteChannel,结构化聚集接口GatheringByteChannel将多个ByteBuffer组成一个数组一次写入,用DirectBuffer代替ByteBuffer进行多次读写都可以提高效            率,但DirectBuffer是直接开辟OS内存,如果频繁新建回收,性能反而不如ByteBuffer虚拟机内的相同操作.

                8)4个引用级别:强,软,弱,虚,其作用分别不同,一般软和弱引用可以作为缓存的一种方案避免内存溢出,而虚更多用于跟踪对象回收时机,弱引用WeakHashMap可以       作为简单的缓存对象。

                9):不在循环代码中使用try-catch,尽量放到循环体外,多用局部变量或临时变量存储计算值以避免重复计算,可以考虑在一次循环中做多个操作减少循环次数(i+1,i+2,i+3...),     "阻断性"逻辑运算如&&来代替位运算如&(当然一般也不建议利用条件判断来做一些操作,因为后面的判断默认应该可以不去运行的)

                10):多用一维数组代替二维数组;使用Buffer进行I/O;对构造成本大的函数,用clone代替new(注意该类实现Cloneable,并且默认是浅复制,深复制需要自己重写Objectclone     方法)

                12):技巧:用位运算代替2次幂的乘除;

    13): 多用static静态方法而不是实例方法

    14): 多用native方法如arrayCopy;

     

    to be continued…..

     

     





  • 相关阅读:
    Golang哲学思想
    Outlook与Hotmail的设置
    Android 经验: 5555 端口会被 adb 误认为 emulator
    QT程序探测所需DLL,静态连接和打包
    VS编译的QT程序发布时产生的AppCrash问题
    新语言和旧语言
    MMU和TLB
    FMX的综合评价
    Windows核心编程学习九:利用内核对象进行线程同步
    QListWidget特别简单,但有两种添加item的方式
  • 原文地址:https://www.cnblogs.com/dimmacro/p/4460831.html
Copyright © 2020-2023  润新知