Hibernate3.1.X 多線程下BUG

剛寫過一篇Java筆記-使用JConsole進行JVM性能監測,今天就又遇上99%,樂不開支拍拍手打開JConsole就要收拾它。

 

在Thread選項卡中看到許多HTTP的請求線程都阻塞在org.hibernate.util.SoftLimitMRUCache.get(SoftLimitMRUCache.java:51)   很快就發現下面這個Thread

 

Name: TP-Processor24
State: RUNNABLE
Total blocked: 87  Total waited: 21

 

Stack trace:
org.apache.commons.collections.ReferenceMap.getEntry(Unknown Source)
org.apache.commons.collections.ReferenceMap.get(Unknown Source)
org.hibernate.util.SoftLimitMRUCache.get(SoftLimitMRUCache.java:51)
org.hibernate.engine.query.QueryPlanCache.getNativeSQLQueryPlan(QueryPlanCache.java:107)
org.hibernate.impl.AbstractSessionImpl.getNativeSQLQueryPlan(AbstractSessionImpl.java:140)
org.hibernate.impl.AbstractSessionImpl.list(AbstractSessionImpl.java:147)
org.hibernate.impl.SQLQueryImpl.list(SQLQueryImpl.java:164)
com.mogoko.struts.logic.user.LeaveMesManager.getCommentByShopId(LeaveMesManager.java:302)
com.mogoko.struts.action.shop.ShopIndexBaseInfoAction.execute(ShopIndexBaseInfoAction.java:175)

 

 

 

LeaveMesManager.java:302是下面

 

list = (ArrayList) session.createSQLQuery(queryString).addEntity(“”,Leavemes.class).list();

 

 

 

顯然是TP-Processor24進入SoftLimitMRUCache.get—>ReferenceMap.get–>ReferenceMap.getEntry沒有返回,一共87個Thread被阻塞(21代表什麼呢?)。

 

我們的環境如下:

 

Hibernate 3.1.2

 

Collections:2.1.1

 

分別打開了SoftLimitMRUCache.java和ReferenceMap.java。後者extends自AbstractMap,本身不提供線程安全保證,那就是SoftLimitMRUCache的問題了。看下面它的代碼

 

 

 

 

 


public synchronized Object get(Object key) {
  Object result = softReferenceCache.get( key );//   第51行
  if ( result != null ) {
   strongReferenceCache.put( key, result );
  }
  return result;
 }

 

 public Object put(Object key, Object value) {
  softReferenceCache.put( key, value );
  return strongReferenceCache.put( key, value );
 }

 

 public int size() {
  return strongReferenceCache.size();
 }

 

 public int softSize() {
  return softReferenceCache.size();
 }

 

 public Iterator entries() {
  return strongReferenceCache.entrySet().iterator();
 }

 

 public Iterator softEntries() {
  return softReferenceCache.entrySet().iterator();
 }

 

 

 

get函數外都沒有synchronized,頂你的肺      看樣子是每一次Hibernate’s Query在調用JDBC查詢前都會去這個SoftLimitMRUCache先嚐試從內存中查,從而減少數據庫負載,之所以1次/周,是源於訪問量不大,沒有做好壓力測試啊  

 

再看看ReferenceMap.java

 


    public Object get(Object key) {
        purge();
       
Entry entry = getEntry(key);       //   調用getEntry
        if (entry == null) return null;
        return entry.getValue();
    }


    private Entry getEntry(Object key) {
        if (key == null) return null;
        int hash = key.hashCode();
        int index = indexFor(hash);
       
for (Entry entry = table[index]; entry != null; entry = entry.next) {
            if ((entry.hash == hash) && key.equals(entry.getKey())) {
                return entry;
            }
        }
        return null;
    }


    public Object put(Object key, Object value) {
        if (key == null) throw new NullPointerException(“null keys not allowed”);
        if (value == null) throw new NullPointerException(“null values not allowed”);


        purge();
        if (size + 1 > threshold) resize();


        int hash = key.hashCode();
        int index = indexFor(hash);
        Entry entry = table[index];
       
while (entry != null) {
            if ((hash == entry.hash) && key.equals(entry.getKey())) {
                Object result = entry.getValue();
                entry.setValue(value);
                return result;
            }
            entry = entry.next;
        }
        this.size++;
        modCount++;
        key = toReference(keyType, key, hash);
        value = toReference(valueType, value, hash);
        table[index] = new Entry(key, hash, value, table[index]);
        return null;
    }


    private void resize() {
        Entry[] old = table;
        table = new Entry[old.length * 2];


        for (int i = 0; i < old.length; i++) {
            Entry next = old[i];
            while (next != null) {
                Entry entry = next;
                next = next.next;
                int index = indexFor(entry.hash);
                entry.next = table[index];
                table[index] = entry;
            }
            old[i] = null;
        }
        threshold = (int)(table.length * loadFactor);
    }


注意上面的四行粗藍色代碼,最多有三處會循環遍歷/修改鏈表,多Thread環境下導致鏈表出現環路,結果infinite loop!


在Hibernate官網找到SoftLimitMRUCache的bug,有兩條


1    Concurrent access issues with both SoftLimitMRUCache and SimpleMRUCache


影響版本3.2.0.alpha1, 3.1.3 


再看看這個Infinite Loop Possible Through Non-synchronisd use LRUMap


講的雖然是LRUMap,但根本原因仍在於SoftLimitMRUCache除get函數外沒有同步導致。


對非同步的map多線程下帶來的問題感興趣可以看這裏


HashMap.get() can cause an infinite loop!


2    Use of session.createSQLQuery causes memory leak


 內存泄漏同樣由於線程非安全導致。


 


下載Hibernate3.2.1的源碼如下


 


public synchronized Object put(Object key, Object value) {
  softReferenceCache.put( key, value );
  return strongReferenceCache.put( key, value );
 }


 public synchronized int size() {
  return strongReferenceCache.size();
 }


 public synchronized int softSize() {
  return softReferenceCache.size();
 }


 public synchronized void clear() {
  strongReferenceCache.clear();
  softReferenceCache.clear();
 }


 


新版本中get/put/size/softSize函數和新增的clear函數都加上了synchronized同步。


 


 


Hibernate的bug查詢地址是http://opensource.atlassian.com/projects/hibernate/secure/IssueNavigator.jspa


 


 


趕緊扔掉你的Hibernate3.1.X,換到Hibernate3.2.1以上吧,如果還有死循環問題我會。。

源自:http://www.mogoko.com/p/article/2385

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章