Java性能调优(四):GC策略和程序调优

GC策略的优化

选择不同的GC收集器,会有不同的效果,CMS GC多数动作是和应用并发进行的,确实可以减少GC动作给应用造成的暂停。

对于web应用而言,在G1还不够成熟的情况下,CMS GC是不错的选择。

程序调优

CPU消耗严重的解决方法

1.CPU us高的解决方法

CPU us高的原因主要是执行程序无任何挂起动作,且一直执行,导致CPU没有机会去调度执行其他的线程,导致线程饿死的现象。

以上图片增加sleep,这种修改方式是以损失单次执行性能为代价,但由于降低CPU的消耗,对多线程的应用而言,反而提高总体的平均性能。

对GC频繁造成的CPU us高的现象,则要通过JVM调优或程序调优,降低GC的执行次数。

2.CPU sy高的解决方法

CPU sy高的原因主要是线程的运行状态经常切换,这种情况,最简单的优化方法是减小线程数。

造成CPU sy高的原因除了启动的线程过多以外,还有一个重要的原因是线程之间锁竞争激烈,造成线程状态经常要切换,因此尽可能降低线程间的锁竞争也是常见的优化方法。

文件IO消耗严重的解决方法

造成文件IO消耗严重的原因主要是多线程在写大量的数据到同一个文件,导致文件很快变得很大,从而写入速度越来越慢,并造成各线程激烈争抢文件锁。通常采用几种方式调优:

1.异步写文件

将写文件的同步动作改成异步动作。

2.批量读写

频繁的读写操作对IO消耗很严重,批量操作将大幅度提升IO操作的性能。

3.限流

将文件IO消耗控制到一个能接受的范围。

4.限制文件大小

对于每个输出文件,都应做大小的限制。

网络IO消耗严重的解决方法

常用的调优方法为进行限流,限流通常是闲置发送packet的频率,从而在网络IO消耗可接受的情况下来发送packet。

对内存消耗的严重情况

1.释放不必要的引用,例如使用ThreadLocal

2.使用对象缓存池

3.采用合理的缓存实效算法,例如FIFO和LRU策略的CachePool

4.合理使用softReference和WeakReference

对象资源消耗不多,但程序执行慢的情况

锁竞争激烈

锁竞争的状态会比较明显,这时线程很容易处于等待锁的状况,从而导致性能小将以及CPU sy上升。

1.使用并发包中的类

给予CAS来做无需lock就可以实现资源一致性保证,主要的实现nonblocking的算法

2.使用Treiber算法

Treiber算法主要用于实现stack,基于Treiber算法实现无阻塞Stack。

public class ConcurrentStack<E> {
    AtomicReference<Node<E>> head = new AtomicReference<Node<E>>();
 
    public void push(E item) {
        Node<E> newHead = new Node<E>(item);
        Node<E> oldHead;
        do {
            oldHead = head.get();
            newHead.next = oldHead;
        } while (!head.compareAndSet(oldHead, newHead));
    }
 
    public E pop() {
        Node<E> oldHead;
        Node<E> newHead;
        do {
            oldHead = head.get();
            if (oldHead == null)
                return null;
            newHead = oldHead.next;
        } while (!head.compareAndSet(oldHead,newHead));
        return oldHead.item;
    }
 
    static class Node<E> {
        final E item;
        Node<E> next;
 
        public Node(E item) { this.item = item; }
    }
}
3.使用Michael-Scott非阻塞队列算法

和Treiber算法类似,也是基于CAS以及AtomicReference来实现队列的非阻塞操作,ConcurrentLinkedQueue就是典型的基于Michael-Scott实现的非阻塞队列。

从上面两种算法来看,基于CAS和AtomicReference来实现无阻塞算法是不错的选择。但值得注意的是,由于CAS是基于不断的循环比较来保证资源一致性的,对于冲突较多的应用场景而言,CAS会带来更高的CPU消耗,因此不一定采用CAS实现无阻塞的就一定比采用Lock方式的性能好。业界还有一些无阻塞算法的改进,如MCAS、WSTM20等

4.尽可能少用锁

尽可能让锁仅在需要的地方出现,通常没必要对整个方法加锁,而只对需要控制的资源做加锁操作。尽可能让锁最小化,例如一个操作中需要保护的资源只有HashMap,那么在加锁时则可只synchronized(map),而没必要synchronized(this).

5.拆分锁

把独占锁拆分为多把锁,常见的有读写锁拆分及类似ConcurrentHashMap中默认拆分为16把锁的方法。需要注意的是采用拆分锁后,全局性质的操作会变得比较复杂,例如ConcurrentHashMap的size操作。

6.去除读写操作的互斥锁

在修改时加锁,并复制对象进行修改,修改完后切换对象的引用,而读取操作时则不加锁,这种方式称为CopyOnWrite。CopyOnWriteArrayList就是其中的典型实现。这种做法好处是可以明显提升读的性能,适用读多写少的场合,坏处是造成更多的内存消耗。

未充分使用硬件资源

1.未充分使用CPU

对于JAVA应用而言,通常原因就是在能并行处理的场景中未使用足够的线程。

另外,单线程的计算,也可以拆分为多线程来分别计算,最后合并结果,JDK7中的fork-join框架可以给以上场景提供一个好的支撑方法

2.未充分使用内存

如数据的缓存、耗时资源的缓存(如数据库连接,网络连接)、页面片段的缓存等。


猜你喜欢

转载自blog.csdn.net/oeljeklaus/article/details/80666703