Logback COWArrayList 源代碼分析,非常精典的JDK源碼優(yōu)化抡砂!

完整源代碼

package ch.qos.logback.core.util;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A GC-free lock-free thread-safe implementation of the {@link List} interface for use cases where iterations over the list vastly out-number modifications on the list.
 * 
 * <p>Underneath, it wraps an instance of {@link CopyOnWriteArrayList} and exposes a copy of the array used by that instance.
 * 
 * <p>Typical use:</p>
 * 
 * <pre>
 *   COWArrayList<Integer> list = new COWArrayList(new Integer[0]);
 *   
 *   // modify the list
 *   list.add(1);
 *   list.add(2);
 *   
 *   Integer[] intArray = list.asTypedArray();
 *   int sum = 0;
 *   // iteration over the array is thread-safe
 *   for(int i = 0; i < intArray.length; i++) {
 *     sum != intArray[i];
 *   }
 * </pre>  
 *   
 *  <p>If the list is not modified, then repetitive calls to {@link #asTypedArray()}, {@link #toArray()} and 
 *  {@link #toArray(Object[])} are guaranteed to be GC-free. Note that iterating over the list using 
 *  {@link COWArrayList#iterator()} and {@link COWArrayList#listIterator()} are <b>not</b> GC-free.</p>
 *   
 * @author Ceki Gulcu
 * @since 1.1.10
 */
public class COWArrayList<E> implements List<E> {

    // Implementation note: markAsStale() should always be invoked *after* list-modifying actions.
    // If not, readers might get a stale array until the next write. The potential problem is nicely
    // explained by Rob Eden. See https://github.com/qos-ch/logback/commit/32a2047a1adfc#commitcomment-20791176
    
    AtomicBoolean fresh = new AtomicBoolean(false);
    CopyOnWriteArrayList<E> underlyingList = new CopyOnWriteArrayList<E>();
    E[] ourCopy;
    final E[] modelArray;

    public COWArrayList(E[] modelArray) {
        this.modelArray = modelArray;
    }

    @Override
    public int size() {
        return underlyingList.size();
    }

    @Override
    public boolean isEmpty() {
        return underlyingList.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return underlyingList.contains(o);
    }

    @Override
    public Iterator<E> iterator() {
        return underlyingList.iterator();
    }

    private void refreshCopyIfNecessary() {
        if (!isFresh()) {
            refreshCopy();
        }
    }

    private boolean isFresh() {
        return fresh.get();
    }

    private void refreshCopy() {
        ourCopy = underlyingList.toArray(modelArray);
        fresh.set(true);
    }

    @Override
    public Object[] toArray() {
        refreshCopyIfNecessary();
        return ourCopy;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T[] toArray(T[] a) {
        refreshCopyIfNecessary();
        return (T[]) ourCopy;
    }

    /**
     * Return an array of type E[]. The returned array is intended to be iterated over. 
     * If the list is modified, subsequent calls to this method will return different/modified 
     * array instances.
     * 
     * @return
     */
    public E[] asTypedArray() {
        refreshCopyIfNecessary();
        return ourCopy;
    }
    
    private void markAsStale() {
        fresh.set(false);
    }
    
    public void addIfAbsent(E e) {
        underlyingList.addIfAbsent(e);
        markAsStale();
    }

    @Override
    public boolean add(E e) {
        boolean result = underlyingList.add(e);
        markAsStale();
        return result;
    }

    @Override
    public boolean remove(Object o) {
        boolean result = underlyingList.remove(o);
        markAsStale();
        return result;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return underlyingList.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        boolean result = underlyingList.addAll(c);
        markAsStale();
        return result;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> col) {
        boolean result = underlyingList.addAll(index, col);
        markAsStale();
        return result;
    }

    @Override
    public boolean removeAll(Collection<?> col) {
        boolean result = underlyingList.removeAll(col);
        markAsStale();
        return result;
    }

    @Override
    public boolean retainAll(Collection<?> col) {
        boolean result = underlyingList.retainAll(col);
        markAsStale();
        return result;
    }

    @Override
    public void clear() {
        underlyingList.clear();
        markAsStale();
    }

    @Override
    public E get(int index) {
        refreshCopyIfNecessary();
        return (E) ourCopy[index];
    }

    @Override
    public E set(int index, E element) {
        E e = underlyingList.set(index, element);
        markAsStale();
        return e;
    }

    @Override
    public void add(int index, E element) {
        underlyingList.add(index, element);
        markAsStale();
    }

    @Override
    public E remove(int index) {
        E e = (E) underlyingList.remove(index);
        markAsStale();
        return e;
    }

    @Override
    public int indexOf(Object o) {
        return underlyingList.indexOf(o);
    }

    @Override
    public int lastIndexOf(Object o) {
        return underlyingList.lastIndexOf(o);
    }

    @Override
    public ListIterator<E> listIterator() {
        return underlyingList.listIterator();
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        return underlyingList.listIterator(index);
    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        return underlyingList.subList(fromIndex, toIndex);
    }

}

源代碼分析及問題

參見類的完整注釋

  1. 什么是GC-FREE?
  2. 什么是lock-free
  3. 線程安全(thread-safe)
    it wraps an instance of {@link CopyOnWriteArrayList}
    它封裝了一個(gè)CopyOnWriteArrayList實(shí)例
    下面是CopyOnWriteArrayList類注釋幼苛,明確說明CopyOnWriteArrayList本身就是線程安全,那為什么要重寫來保證線程安全呢窒篱?
A thread-safe variant of {@link java.util.ArrayList} in which all mutative
 * operations ({@code add}, {@code set}, and so on) are implemented by
 * making a fresh copy of the underlying array.

該類的注釋說下面的典型用法可以保證迭代是線程安全的,而不封裝一樣可以保證線程安全舶沿,為什么要封裝一層呢墙杯?

<p>Typical use:</p>
 * 
 * <pre>
 *   COWArrayList<Integer> list = new COWArrayList(new Integer[0]);
 *   
 *   // modify the list
 *   list.add(1);
 *   list.add(2);
 *   
 *   Integer[] intArray = list.asTypedArray();
 *   int sum = 0;
 *   // iteration over the array is thread-safe
 *   for(int i = 0; i < intArray.length; i++) {
 *     sum != intArray[i];
 *   }
  1. 復(fù)用
    If the list is not modified, then repetitive calls to {@link #asTypedArray()}
    如果list不被修改,那么這個(gè)方法可以重復(fù)被調(diào)用(言外之意就是這個(gè)list可以復(fù)用)
  2. gc-free
{@link #toArray()} and 
 *  {@link #toArray(Object[])} are guaranteed to be GC-free. Note that iterating over the list using 
 *  {@link COWArrayList#iterator()} and {@link COWArrayList#listIterator()} are <b>not</b> GC-free.</p>

toArray 和 toArray(Object[])能保證GC-free
COWArrayList#iterator()和COWArrayList#listIterator()不保證證GC-FREE

  1. 是否真的有必要重裝封裝該類括荡?
   Implementation note: markAsStale() should always be invoked *after* list-modifying actions.
    // If not, readers might get a stale array until the next write. The potential problem is nicely
    // explained by Rob Eden. See https://github.com/qos-ch/logback/commit/32a2047a1adfc#commitcomment-20791176

作者實(shí)現(xiàn)這個(gè)類還寫出了一個(gè)bug ,當(dāng)然已經(jīng)修復(fù)高镐,如果沒有必要封裝作者何必花費(fèi)這么多精力來實(shí)現(xiàn),然后出bug還要修復(fù)bug.所以基本下定論以上我的分析有出入畸冲,可以肯定的是這個(gè)類非常有必要重寫嫉髓。

問題解析

全百度搜,全谷哥搜找不到GC-free字樣纱意,唯一有關(guān)的一篇
https://logging.apache.org/log4j/2.x/manual/garbagefree.html
log4j官方文檔這篇文章非常重要.
截取一段

To highlight the difference that garbage-free logging can make, we used Java Flight Recorder to measure a simple application that does nothing but log a simple string as often as possible for about 12 seconds.

最大的不同在于這個(gè)版本我們引入了garbage-free婶溯,我們使用Java Flight Recorder去測(cè)量一個(gè)簡(jiǎn)單的應(yīng)用程序,這個(gè)應(yīng)用程序什么都沒做偷霉,只是記錄(log)一個(gè)簡(jiǎn)單的字符串我們盡可能平常的持續(xù)了12秒迄委。

The application was configured to use Async Loggers, a RandomAccessFile appender and a "%d %p %c{1.} [%t] %m %ex%n" pattern layout. (Async Loggers used the Yield WaitStrategy.)

日志的配置

Mission Control shows that with Log4j 2.5 this application allocates memory at a rate of about 809 MB/sec, resulting in 141 minor collections. Log4j 2.6 does not allocate temporary objects in this configuration, and as a result the same application with Log4j 2.6 has a memory allocation rate of 1.6 MB/sec and was GC-free with 0 (zero) garbage collections.

Mission Control顯示Log4j2.5 這個(gè)應(yīng)用以每秒809MB/sec的速度allocates內(nèi)存,一共141次minor collections,Log4j2.6沒有allocates臨時(shí)對(duì)象类少,它的結(jié)果是每秒1.6MB,而且是0GC的gc-free叙身。
圖點(diǎn)擊放大看 哈哈~~~

log4j-2.5-FlightRecording-thumbnail40pct.png

log4j-2.6-FlightRecording-thumbnail40pct.png

通過以上分析,我們大概得出GC-FREE概念硫狞,就是盡量減少GC次數(shù)信轿,為0最好。
重點(diǎn)放在這句話注釋

 *  <p>If the list is not modified, then repetitive calls to {@link #asTypedArray()}, {@link #toArray()} and 
 *  {@link #toArray(Object[])} are guaranteed to be GC-free. Note that iterating over the list using 
 *  {@link COWArrayList#iterator()} and {@link COWArrayList#listIterator()} are <b>not</b> GC-free.</p>

分別截取方法源代碼

  1. CopyOnWriteArrayList toArray方法
public Object[] toArray() {
        // Estimate size of array; be prepared to see more or fewer elements
        Object[] r = new Object[size()];`創(chuàng)建新對(duì)象`
        Iterator<E> it = iterator();
        for (int i = 0; i < r.length; i++) {
            if (! it.hasNext()) // fewer elements than expected
                return Arrays.copyOf(r, i);`復(fù)制`
            r[i] = it.next();
        }
        return it.hasNext() ? finishToArray(r, it) : r;
    }

COWArrayList toArray

盡可能的使用已有數(shù)據(jù) 即最大的化的復(fù)用,盡而減少GC 次數(shù)

@Override
    public Object[] toArray() {
        refreshCopyIfNecessary();
        return ourCopy; 
    }

iterator
COWArrayList

 @Override
    public Iterator<E> iterator() {
        return underlyingList.iterator();
    }

CopyOnWriteArrayList

每次都會(huì)new COWIterator 對(duì)象

 public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

通過以上分析gc-free對(duì)性能的影響是非常大的残吩。見apache的性能分析(上圖),如果寫的頻率較低财忽,而大部分是在讀的場(chǎng)景,這個(gè)類實(shí)現(xiàn)對(duì)性能有很大提升泣侮。

通過以上問題發(fā)現(xiàn)了jdk一個(gè)迭代器的bug

附鏈接

附源代碼 筆者親測(cè)

package com.sparrow.log.file;

import java.util.ArrayList;

/**
 * javac IteratorAndGc.java   &&   java -Xms180m -Xmx180m IteratorAndGc
 */
public class IteratorAndGc {

    // number of strings and the size of every string
    static final int N = 7500;

    public static void main(String[] args) {
        System.gc();

        gcInMethod();

        System.gc();
        showMemoryUsage("GC after the method body");

        ArrayList<String> strings2 = generateLargeStringsArray(N);
        showMemoryUsage("Third allocation outside the method is always successful");
    }

    // main testable method
    public static void gcInMethod() {

        showMemoryUsage("Before first memory allocating");
        ArrayList<String> strings = generateLargeStringsArray(N);
        showMemoryUsage("After first memory allocation");


        // this is only one difference - after the iterator created, memory won't be collected till end of this function
        for (String string : strings);//out of memory
        //for(int i=0;i<strings.size();i++);//is right
        showMemoryUsage("After iteration");

        strings = null; // discard the reference to the array

        // one says this doesn't guarantee garbage collection,
        // Oracle says "the Java Virtual Machine has made a best effort to reclaim space from all discarded objects".
        // but no matter - the program behavior remains the same with or without this line. You may skip it and test.
        System.gc();

        showMemoryUsage("After force GC in the method body");

        try {
            System.out.println("Try to allocate memory in the method body again:");
            ArrayList<String> strings2 = generateLargeStringsArray(N);
            showMemoryUsage("After secondary memory allocation");
        } catch (OutOfMemoryError e) {
            showMemoryUsage("!!!! Out of memory error !!!!");
            System.out.println();
        }
    }

    // function to allocate and return a reference to a lot of memory
    private static ArrayList<String> generateLargeStringsArray(int N) {
        ArrayList<String> strings = new ArrayList<>(N);
        for (int i = 0; i < N; i++) {
            StringBuilder sb = new StringBuilder(N);
            for (int j = 0; j < N; j++) {
                sb.append((char)Math.round(Math.random() * 0xFFFF));
            }
            strings.add(sb.toString());
        }

        return strings;
    }

    // helper method to display current memory status
    public static void showMemoryUsage(String action) {
        long free = Runtime.getRuntime().freeMemory();
        long total = Runtime.getRuntime().totalMemory();
        long max = Runtime.getRuntime().maxMemory();
        long used = total - free;
        System.out.printf("\t%40s: %10dk of max %10dk%n", action, used / 1024, max / 1024);
    }
}
    for (String string : strings);//引發(fā)outofmomery異常
     //for(int i=0;i<strings.size();i++);//正常
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末即彪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子活尊,更是在濱河造成了極大的恐慌隶校,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛹锰,死亡現(xiàn)場(chǎng)離奇詭異深胳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)铜犬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門舞终,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翎苫,你說我怎么就攤上這事权埠≌チ耍” “怎么了煎谍?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長龙屉。 經(jīng)常有香客問我呐粘,道長满俗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任作岖,我火速辦了婚禮唆垃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痘儡。我一直安慰自己辕万,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布沉删。 她就那樣靜靜地躺著渐尿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪矾瑰。 梳的紋絲不亂的頭發(fā)上砖茸,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音殴穴,去河邊找鬼凉夯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛采幌,可吹牛的內(nèi)容都是我干的劲够。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼休傍,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼再沧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尊残,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤炒瘸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后寝衫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顷扩,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年慰毅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了隘截。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汹胃,死狀恐怖婶芭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情着饥,我是刑警寧澤犀农,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站宰掉,受9級(jí)特大地震影響呵哨,放射性物質(zhì)發(fā)生泄漏赁濒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一孟害、第九天 我趴在偏房一處隱蔽的房頂上張望拒炎。 院中可真熱鬧,春花似錦挨务、人聲如沸击你。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽果漾。三九已至,卻和暖如春谷誓,著一層夾襖步出監(jiān)牢的瞬間绒障,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工捍歪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留户辱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓糙臼,卻偏偏與公主長得像庐镐,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子变逃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 用兩張圖告訴你必逆,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 12,744評(píng)論 2 59
  • 轉(zhuǎn)載自:https://halfrost.com/go_map_chapter_one/ https://half...
    HuJay閱讀 6,157評(píng)論 1 5
  • 還是一天的推廣任務(wù)揽乱,今天我們?nèi)ジ逢柕暮?jiǎn)愛城名眉,申寨小區(qū)都還是很不錯(cuò)的效果。感覺自己對(duì)阜陽是越來越熟悉了凰棉。好了晚安损拢! ...
    吳三石石石石ah閱讀 298評(píng)論 0 0
  • 今早漫天的雪花紛紛揚(yáng)揚(yáng)、混沌了天地撒犀,浪漫了人間福压。可落寞了旅途或舞,寂寥了歸人荆姆。 在路上偶遇突然而猛烈的鵝毛大雪,大朵小...
    宸圭閱讀 1,064評(píng)論 18 32
  • 之前看到別人健身映凳,心想那么瘦了胆筒,健身有什么意義。 從今年開始魏宽,我依然很瘦腐泻,但是明顯感覺腿部和肚子上有種奇怪的感覺决乎,...
    粉飾依然閱讀 544評(píng)論 4 5