记一次调优过程

我怕爱的太早我们不能终老 提交于 2019-12-04 15:54:35

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.log

GC信息统计

jstat -gcutil pid 【间隔时间】 【次数】

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。

解决方案可以参考:
https://blog.csdn.net/zhouhao88410234/article/details/76041702
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=2147719
https://www.cnblogs.com/JAYIT/p/8241634.html
https://www.cnblogs.com/Jimmy104/p/5258205.html

Netty的解决办法

对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。

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()) {
        ...
    }

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.removeAll 使用ArrayList.Iterator()循环获取对象,但具体实现也是通过下标查找。那差在哪里呢?
hashSet.removeAll的实现中:如果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];
        }

 

    易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
    该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!