Apache common pool2 對象驅(qū)逐解析

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.pool2.impl;

import org.apache.commons.pool2.PooledObject;

/**
 * To provide a custom eviction policy (i.e. something other than {@link
 * DefaultEvictionPolicy} for a pool, users must provide an implementation of
 * this interface that provides the required eviction policy.
 *
 * @param <T> the type of objects in the pool
 *
 * @since 2.0
 */
public interface EvictionPolicy<T> {

    /**
     * This method is called to test if an idle object in the pool should be
     * evicted or not.
     *
     * @param config    The pool configuration settings related to eviction
     * @param underTest The pooled object being tested for eviction
     * @param idleCount The current number of idle objects in the pool including
     *                      the object under test
     * @return {@code true} if the object should be evicted, otherwise
     *             {@code false}
     */
    boolean evict(EvictionConfig config, PooledObject<T> underTest, int idleCount);
}

默認(rèn)實現(xiàn)

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.pool2.impl;

import org.apache.commons.pool2.PooledObject;

/**
 * Provides the default implementation of {@link EvictionPolicy} used by the
 * pools. Objects will be evicted if the following conditions are met:
 * <ul>
 * <li>the object has been idle longer than
 *     {@link GenericObjectPool#getMinEvictableIdleTimeMillis()} /
 *     {@link GenericKeyedObjectPool#getMinEvictableIdleTimeMillis()}</li>
 * <li>there are more than {@link GenericObjectPool#getMinIdle()} /
 *     {@link GenericKeyedObjectPoolConfig#getMinIdlePerKey()} idle objects in
 *     the pool and the object has been idle for longer than
 *     {@link GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} /
 *     {@link GenericKeyedObjectPool#getSoftMinEvictableIdleTimeMillis()}
 * </ul>
 * <p>
 * This class is immutable and thread-safe.
 * </p>
 *
 * @param <T> the type of objects in the pool
 *
 * @since 2.0
 */
public class DefaultEvictionPolicy<T> implements EvictionPolicy<T> {

    @Override
    public boolean evict(final EvictionConfig config, final PooledObject<T> underTest,
            final int idleCount) {

        if ((config.getIdleSoftEvictTime() < underTest.getIdleTimeMillis() &&
                config.getMinIdle() < idleCount) ||
                config.getIdleEvictTime() < underTest.getIdleTimeMillis()) {
            return true;
        }
        return false;
    }
}

對應(yīng)配置

 /**
     * Create a new eviction configuration with the specified parameters.
     * Instances are immutable.
     *
     * @param poolIdleEvictTime Expected to be provided by
     *        {@link BaseGenericObjectPool#getMinEvictableIdleTimeMillis()}
     * @param poolIdleSoftEvictTime Expected to be provided by
     *        {@link BaseGenericObjectPool#getSoftMinEvictableIdleTimeMillis()}
     * @param minIdle Expected to be provided by
     *        {@link GenericObjectPool#getMinIdle()} or
     *        {@link GenericKeyedObjectPool#getMinIdlePerKey()}
     */
    public EvictionConfig(final long poolIdleEvictTime, final long poolIdleSoftEvictTime,
            final int minIdle) {
        if (poolIdleEvictTime > 0) {
            idleEvictTime = poolIdleEvictTime;
        } else {
            idleEvictTime = Long.MAX_VALUE;
        }
        if (poolIdleSoftEvictTime > 0) {
            idleSoftEvictTime = poolIdleSoftEvictTime;
        } else {
            idleSoftEvictTime  = Long.MAX_VALUE;
        }
        this.minIdle = minIdle;
    }

 final EvictionConfig evictionConfig = new EvictionConfig(
                    getMinEvictableIdleTimeMillis(),
                    getSoftMinEvictableIdleTimeMillis(),
                    getMinIdlePerKey());

啟動驅(qū)逐器

 /**
     * Sets the number of milliseconds to sleep between runs of the idle object evictor thread.
     * <ul>
     * <li>When positive, the idle object evictor thread starts.</li>
     * <li>When non-positive, no idle object evictor thread runs.</li>
     * </ul>
     *
     * @param timeBetweenEvictionRunsMillis
     *            number of milliseconds to sleep between evictor runs
     *
     * @see #getTimeBetweenEvictionRunsMillis
     */
    public final void setTimeBetweenEvictionRunsMillis(
            final long timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        startEvictor(timeBetweenEvictionRunsMillis);
    }

timeBetweenEvictionRunsMillis >0時才啟動

     * <p>Starts the evictor with the given delay. If there is an evictor
     * running when this method is called, it is stopped and replaced with a
     * new evictor with the specified delay.</p>
     *
     * <p>This method needs to be final, since it is called from a constructor.
     * See POOL-195.</p>
     *
     * @param delay time in milliseconds before start and between eviction runs
     */
    final void startEvictor(final long delay) {
        synchronized (evictionLock) {
            EvictionTimer.cancel(evictor, evictorShutdownTimeoutMillis, TimeUnit.MILLISECONDS);
            evictor = null;
            evictionIterator = null;
            if (delay > 0) {
                evictor = new Evictor();
                EvictionTimer.schedule(evictor, delay, delay);
            }
        }
    }

驅(qū)逐定時器

/**
     * Adds the specified eviction task to the timer. Tasks that are added with a
     * call to this method *must* call {@link #cancel()} to cancel the
     * task to prevent memory and/or thread leaks in application server
     * environments.
     *
     * @param task      Task to be scheduled.
     * @param delay     Delay in milliseconds before task is executed.
     * @param period    Time in milliseconds between executions.
     */
    static synchronized void schedule(
            final BaseGenericObjectPool<?>.Evictor task, final long delay, final long period) {
        if (null == executor) {
            executor = new ScheduledThreadPoolExecutor(1, new EvictorThreadFactory());
            executor.setRemoveOnCancelPolicy(true);
        }
        final ScheduledFuture<?> scheduledFuture =
                executor.scheduleWithFixedDelay(task, delay, period, TimeUnit.MILLISECONDS);
        task.setScheduledFuture(scheduledFuture);
    }

驅(qū)逐器線程name commons-pool-evictor-thread 并只有一個線程,多個對象共享

類 EvictionTimer
 /** Executor instance */
    private static ScheduledThreadPoolExecutor executor; 

 /**
     * Thread factory that creates a daemon thread, with the context class loader from this class.
     */
    private static class EvictorThreadFactory implements ThreadFactory {

        @Override
        public Thread newThread(final Runnable runnable) {
            final Thread thread = new Thread(null, runnable, "commons-pool-evictor-thread");
            thread.setDaemon(true); // POOL-363 - Required for applications using Runtime.addShutdownHook().
            AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
                thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader());
                return null;
            });

            return thread;
        }
    }

不足minIdle 時補足 ensureMinIdle ( Inner classes)



    /**
     * The idle object evictor {@link TimerTask}.
     *
     * @see GenericKeyedObjectPool#setTimeBetweenEvictionRunsMillis
     */
    class Evictor implements Runnable {

        private ScheduledFuture<?> scheduledFuture;

        /**
         * Run pool maintenance.  Evict objects qualifying for eviction and then
         * ensure that the minimum number of idle instances are available.
         * Since the Timer that invokes Evictors is shared for all Pools but
         * pools may exist in different class loaders, the Evictor ensures that
         * any actions taken are under the class loader of the factory
         * associated with the pool.
         */
        @Override
        public void run() {
            final ClassLoader savedClassLoader =
                    Thread.currentThread().getContextClassLoader();
            try {
                if (factoryClassLoader != null) {
                    // Set the class loader for the factory
                    final ClassLoader cl = factoryClassLoader.get();
                    if (cl == null) {
                        // The pool has been dereferenced and the class loader
                        // GC'd. Cancel this timer so the pool can be GC'd as
                        // well.
                        cancel();
                        return;
                    }
                    Thread.currentThread().setContextClassLoader(cl);
                }

                // Evict from the pool
                try {
                    evict();
                } catch(final Exception e) {
                    swallowException(e);
                } catch(final OutOfMemoryError oome) {
                    // Log problem but give evictor thread a chance to continue
                    // in case error is recoverable
                    oome.printStackTrace(System.err);
                }
                // Re-create idle instances.  補足min idle
                try {
                    ensureMinIdle();
                } catch (final Exception e) {
                    swallowException(e);
                }
            } finally {
                // Restore the previous CCL
                Thread.currentThread().setContextClassLoader(savedClassLoader);
            }
        }

空閑鏈接超過maxIdle 時會直接destroy
maxIdleSave <= idleObjects.size() [ idleObjects.size()>=maxIdleSave]

 final int maxIdleSave = getMaxIdle();
        if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) {
            try {
                destroy(p);
            } catch (final Exception e) {
                swallowException(e);
            }
            try {
                ensureIdle(1, false);
            } catch (final Exception e) {
                swallowException(e);
            }
        }

連接池dbcp 和luttence redis pool都是common pool實現(xiàn)

連接池druid 與dbcp 不同

 public class DestroyTask implements Runnable {

        @Override
        public void run() {
            shrink(true, keepAlive);

            if (isRemoveAbandoned()) {
                removeAbandoned();
            }
        }
    }
  • 縮身 驅(qū)逐方法

idleMillis < minEvictableIdleTimeMillis 不驅(qū)逐

  if (idleMillis < minEvictableIdleTimeMillis) {
                        break;
                    }

idleMillis > maxEvictableIdleTimeMillis 時驅(qū)逐

 if (idleMillis > maxEvictableIdleTimeMillis) {
                        evictConnections[evictCount++] = connection;
public void shrink(boolean checkTime, boolean keepAlive) {
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            return;
        }

        int evictCount = 0;
        int keepAliveCount = 0;
        try {
            if (!inited) {
                return;
            }

            final int checkCount = poolingCount - minIdle;
            final long currentTimeMillis = System.currentTimeMillis();
            for (int i = 0; i < poolingCount; ++i) {
                DruidConnectionHolder connection = connections[i];

                if (checkTime) {
                    if (phyTimeoutMillis > 0) {
                        long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
                        if (phyConnectTimeMillis > phyTimeoutMillis) {
                            evictConnections[evictCount++] = connection;
                            continue;
                        }
                    }

                    long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;

                    if (idleMillis < minEvictableIdleTimeMillis) {
                        break;
                    }

                    if (checkTime && i < checkCount) {
                        evictConnections[evictCount++] = connection;
                    } else if (idleMillis > maxEvictableIdleTimeMillis) {
                        evictConnections[evictCount++] = connection;
                    } else if (keepAlive) {
                        keepAliveConnections[keepAliveCount++] = connection;
                    }
                } else {
                    if (i < checkCount) {
                        evictConnections[evictCount++] = connection;
                    } else {
                        break;
                    }
                }
            }

            int removeCount = evictCount + keepAliveCount;
            if (removeCount > 0) {
                System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
                Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
                poolingCount -= removeCount;
            }
            keepAliveCheckCount += keepAliveCount;
        } finally {
            lock.unlock();
        }

        if (evictCount > 0) {
            for (int i = 0; i < evictCount; ++i) {
                DruidConnectionHolder item = evictConnections[i];
                Connection connection = item.getConnection();
                JdbcUtils.close(connection);
                destroyCountUpdater.incrementAndGet(this);
            }
            Arrays.fill(evictConnections, null);
        }

        if (keepAliveCount > 0) {
            this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
            // keep order
            for (int i = keepAliveCount - 1; i >= 0; --i) {
                DruidConnectionHolder holer = keepAliveConnections[i];
                Connection connection = holer.getConnection();
                holer.incrementKeepAliveCheckCount();

                boolean validate = false;
                try {
                    this.validateConnection(connection);
                    validate = true;
                } catch (Throwable error) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("keepAliveErr", error);
                    }
                    // skip
                }

                if (validate) {
                    holer.lastActiveTimeMillis = System.currentTimeMillis();
                    put(holer);
                } else {
                    JdbcUtils.close(connection);
                }
            }
            Arrays.fill(keepAliveConnections, null);
        }
    }

mysql內(nèi)存泄露 5.1.46

/**
     * Creates a connection to a MySQL Server.
     * 
     * @param hostToConnectTo
     *            the hostname of the database server
     * @param portToConnectTo
     *            the port number the server is listening on
     * @param info
     *            a Properties[] list holding the user and password
     * @param databaseToConnectTo
     *            the database to connect to
     * @param url
     *            the URL of the connection
     * @param d
     *            the Driver instantation of the connection
     * @exception SQLException
     *                if a database access error occurs
     */
    protected ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info,
            String databaseToConnectTo, String url)
            throws SQLException {
    
        ...

        NonRegisteringDriver.trackConnection(this);
    }

protected static void trackConnection(Connection newConn) {
        
        ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl) newConn, refQueue);
        connectionPhantomRefs.put(phantomRef, phantomRef);
    }


protected static final ConcurrentHashMap<ConnectionPhantomReference, ConnectionPhantomReference> connectionPhantomRefs = new ConcurrentHashMap<ConnectionPhantomReference, ConnectionPhantomReference>();
    protected static final ReferenceQueue<ConnectionImpl> refQueue = new ReferenceQueue<ConnectionImpl>();

清除線程

Thread referenceThread = new Thread("Abandoned connection cleanup thread") {
              public void run() {
                  while (true) {
                      try {
                          Reference<? extends ConnectionImpl> ref = refQueue.remove();
                          try {
                              ((ConnectionPhantomReference) ref).cleanup();
                          } finally {
                              connectionPhantomRefs.remove(ref);
                          }
                      } catch (Exception ex) {
                        // no where to really log this if we're static
                      }
                  }
              }
          };
          
          referenceThread.setDaemon(true);
          referenceThread.start();
    }

清除線程會處理物理鏈接 ((ConnectionPhantomReference) ref).cleanup();

static class ConnectionPhantomReference extends PhantomReference<ConnectionImpl> {
        private NetworkResources io;
        
        ConnectionPhantomReference(ConnectionImpl connectionImpl, ReferenceQueue<ConnectionImpl> q) {
            super(connectionImpl, q);
            
            try {
                io = connectionImpl.getIO().getNetworkResources();
            } catch (SQLException e) {
                // if we somehow got here and there's really no i/o, we deal with it later
            }
        }
        
        void cleanup() {
            if (io != null) {
                try {
                    io.forceClose();
                } finally {
                    io = null;
                }
            }
        }
    }

驅(qū)逐線程會調(diào)用 ConnectionImpl close方法

public void close() throws SQLException {
        try {
            synchronized(this.getConnectionMutex()) {
                if (this.connectionLifecycleInterceptors != null) {
                    Iterator var2 = this.connectionLifecycleInterceptors.iterator();

                    while(var2.hasNext()) {
                        ConnectionLifecycleInterceptor cli = (ConnectionLifecycleInterceptor)var2.next();
                        cli.close();
                    }
                }

                this.realClose(true, true, false, (Throwable)null);
            }
        } catch (CJException var7) {
            throw SQLExceptionsMapping.translateException(var7, this.getExceptionInterceptor());
        }
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末癌蓖,一起剝皮案震驚了整個濱河市枪孩,隨后出現(xiàn)的幾起案子撒犀,更是在濱河造成了極大的恐慌跨跨,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疯特,死亡現(xiàn)場離奇詭異撵割,居然都是意外死亡,警方通過查閱死者的電腦和手機辙芍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羹与,“玉大人故硅,你說我怎么就攤上這事∽莞椋” “怎么了吃衅?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腾誉。 經(jīng)常有香客問我徘层,道長,這世上最難降的妖魔是什么利职? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任趣效,我火速辦了婚禮,結(jié)果婚禮上猪贪,老公的妹妹穿的比我還像新娘跷敬。我一直安慰自己,他們只是感情好热押,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布西傀。 她就那樣靜靜地躺著斤寇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拥褂。 梳的紋絲不亂的頭發(fā)上娘锁,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音饺鹃,去河邊找鬼莫秆。 笑死,一個胖子當(dāng)著我的面吹牛尤慰,可吹牛的內(nèi)容都是我干的馏锡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼伟端,長吁一口氣:“原來是場噩夢啊……” “哼杯道!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起责蝠,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤党巾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后霜医,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體齿拂,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年肴敛,在試婚紗的時候發(fā)現(xiàn)自己被綠了署海。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡医男,死狀恐怖砸狞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镀梭,我是刑警寧澤刀森,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站报账,受9級特大地震影響研底,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜透罢,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一榜晦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羽圃,春花似錦芽隆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牙躺。三九已至,卻和暖如春腕扶,著一層夾襖步出監(jiān)牢的瞬間孽拷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工半抱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脓恕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓窿侈,卻偏偏與公主長得像炼幔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子史简,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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

  • 斗魚殤 不祥預(yù)兆突然綻放一朵黑云 完全遮蔽起先嬌羞的滿月…… 初衷乃秀,是欲為你換個更干凈的境遇 可上帝早將你的熱情判...
    蘭知雪閱讀 777評論 1 1
  • 這一生當(dāng)個詩人 圖個千秋萬歲名 才不枉這世間行一趟 可翻遍行囊 卻得不出幾篇像樣 遇到你前的日子也都尋常 到頭來,...
    夜輕我閱讀 152評論 0 3
  • 李蘋瑕 焦點網(wǎng)絡(luò)中級10期 原創(chuàng)分享第 751天 2019年06月21日 星期六 考試 成年人的學(xué)習(xí)圆兵,大體上都是因...
    暖暖的初春閱讀 139評論 0 4
  • 上次為了向新人推薦區(qū)塊鏈殉农,收集了糖果刀脏。今天我想再潑下冷水。 巴菲特說區(qū)塊鏈的結(jié)局就是悲劇超凳。我也想說99.9%的人愈污,...
    Hellc閱讀 210評論 0 0