記一次調優過程

GC策略 & jconsole遠程:增長jmx啓動配置

/data/app/jdk1.8.0_151/bin/java
-Djava.rmi.server.hostname=127.0.0.1 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=7018 -Dcom.sun.management.jmxremote.rmi.port=7019
-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
-jar ./bussiness-0.0.1-SNAPSHOT.jar
-Xms2048m -Xmx2048m -Xmn1024g -Xss2m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:ParallelGCThreads=4
-XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=3 -XX:+UseParNewGC
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+PrintGCApplicationConcurrentTime -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -Xloggc:../log/gc.loghtml

GC信息統計

jstat -gcutil pid 【間隔時間】 【次數】java

free -h
du   -h
df    -h正則表達式

Cpu耗時太高

  1. top
    查找到CPU佔用率太高的PID
  2. top -Hp PID
     查找到子線程TID
  3. printf "%x\n" TID
    TID 轉16進制
  4. jstack -l PID | grep --color=auto  TID -A  [日誌條數]
    獲取JVM指定線程的堆棧信息

具體問題

NIO的epoll空輪詢bug

EPollArrayWrapper.epollWait 致使CPU佔用率太高

Q

CPU佔用率高的線程堆棧信息: 線程狀態一直是RUNNABLE數組

S

參見 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6403933 :安全

這是Linux上poll(和epoll)的問題。已鏈接套接字的文件描述符在輪詢中若是使用掩碼0來標識事件,高併發下,大量鏈接忽然終止(RST),事件集合設置的POLLHUP(POLLERR)位將喚醒選擇器!!但因爲SocketChannel的事件key爲0,這意味着沒有任何選定的事件,select不阻塞並當即返回0,將再次進行輪詢併發

須要解決此錯誤以免在重置鏈接時選擇器旋轉。若是事件掩碼爲0,則能夠經過取消註冊文件描述符(若是事件集合已更改,則從新註冊)來解決此問題。當key爲非0時,POLLHUP(POLLERR)將須要轉換爲就緒集中的OP_READ或OP_WRITE事件,以便應用程序有機會處理IOException。app

解決方案能夠參考:
https://blog.csdn.net/zhouhao88410234/article/details/76041702
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719
http://www.javashuo.com/article/p-gkittkpn-t.html
http://www.javashuo.com/article/p-gtgascco-cc.html高併發

Netty的解決辦法

對Selector的select操做週期進行統計,每完成一次空的select操做進行一次計數,若在某個週期內連續發生N次空輪詢,則觸發了epoll死循環bug。重建Selector,判斷是不是其餘線程發起的重建請求,若不是則將原SocketChannel從舊的Selector上去除註冊,從新註冊到新的Selector上,並將原來的Selector關閉。oop

netty的解決代碼在package io.netty.channel.nio.nioEventLoop這個類下面。性能

上述問題也佐證了: 因NIO要輪詢斷定,對比BIO而言CPU的佔用率可能會高些(BIO在阻塞時會釋放CPU資源)。

正則表達式須要預編譯

Q

使用過程當中,使用了錯誤的方式處理: 每次都從新生成Pattern

Pattern pattern = Pattern.compile(datePattern1);
Matcher match = pattern.matcher(sDate);

致使系統線程長時間RUNNING在

S

Pattern要定義爲static final靜態變量,以免執行屢次預編譯.

private static final Pattern pattern = Pattern.compile(regexRule);
 
private void func(...) {
    Matcher m = pattern.matcher(content);
    if (m.matches()) {
        ...
    }

在Pattern 的 matcher具體實現中經過synchronized來保證線程安全。

ArrayList&LinkedList&HashSet 的選擇

Q

二者在remove指定對象時,都進行了遍歷了List。

ArrayList:
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

LinkedList:
    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

經過比較源碼:
ArrayList底層實現是數組;LinkedList則是雙向鏈表(內部類Node封裝外部Object)

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

  1. get(int index): 
           ArrayList直接數組操做性能會好些;
           LinkedList 須要遍歷雙向鏈表定位index下的Node。
  2. add(Object o) :
           ArrayList可能要作擴容Arrays.copyOf(核心仍是System.arraycopy)。
           LinkedList則直接將原末尾Node的next指針指向新封裝Node對象。
  3. add(int index, E element) : 
            ArrayList須要作System.arraycopy操做。
            LinkedList最壞狀況下須要遍歷查找指定index上的Node作先後指針的從新指向。(最好狀況:直接是在隊尾,經過斷定index = size)
  4. 刪除時,
          ArrayList須要作System.arraycopy操做, remove(Object o) 須要提早遍歷查找。
          LinkedList不管是remove(int index)仍是remove(Object o),都須要遍歷雙向鏈表查找指定的Node。但性能應該仍是比ArrayList要快。

總結而言:

LinkedList的性能在修改時仍是比ArrayList要好些。 但不足以支持當前大數據量的測試要求。

S

選擇使用 HashSet!

hashSet的底層實現是經過HashMap來實現hashCode散列定位(hashMap的底層也仍是數組,內部有Node來封裝外部數據Object)。

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
       public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

而hashMap在作查找、put、 remove等操做時,都會先經過對象hashCode計算定位在數組table上的具體存儲位置。

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

      public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

在具體實踐中:

ArrayList.forEach(t -> HashSet.remove(t)) 比 HashSet.removeAll(ArrayList) 效率快。
ArrayList.forEach:
經過遍歷數組下標順序查找對象, hashSet.remove(t)  使用hashMap.remove;
hashSet.removeAll(ArrayList):
使用ArrayList.Iterator()循環獲取對象,具體實現也是經過變量數組下標順序查找;關鍵在於: 若是size相等,在循環處理過程當中增長了ArrayList.contains斷定,會再次循環遍歷ArrayList。 時間複雜的從O(n)上升爲O(n2)

HashSet extends AbstractSet:
    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        boolean modified = false;

        if (size() > c.size()) {
            for (Iterator<?> i = c.iterator(); i.hasNext(); )
                modified |= remove(i.next());
        } else {
            for (Iterator<?> i = iterator(); i.hasNext(); ) {
                if (c.contains(i.next())) {
                    i.remove();
                    modified = true;
                }
            }
        }
        return modified;
    }

ArrayList:
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

ArrayList$Itr:
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
相關文章
相關標籤/搜索