踩坑JVM溢出-----mybatis一二級緩存

根據需求開發了生產者消費者功能,生產者從Oracle數據庫讀取數據後,通過kafka發送給消費者,消費者從kafka中讀取數據後,寫入到mysql數據庫中,功能需求就這樣,比較簡單,總量5000W數據,通過3個生產者讀取寫入到kafka中。

代碼寫完後經過簡單的功能測試沒問題,開始在測試環境上運行,大概運行了不到1小時發送了不到200萬的數據時出現OOM   GC overhead limit exceeded,重複運行多次後確認存在問題,每次發送大概150W-200W數據時候出現OOM異常。

接下來在本地使用Jprofiler進行代碼測試,發現運行一段時間後有大量的char[] 和String對象沒有釋放,如圖:

而且size還在不斷增大,這是導致OOM的直接原因,那麼問題來了,是什麼導致了這麼多的char[] 和String對象呢?

首先排查代碼,

		String topic="testCsrk1";	
		String clientId="CsrkProducer0";
		int begin=630681;//初始位置  630681    28000000
		int end = 28000000;//結束位置70987838   204138956
		int count=1 ;//循環控制條件
		int posi=100;//一次循環數量
		int id=begin;
		OracleProducer producer=new OracleProducer(topic,false,clientId);
		SqlSession sqlSession=factory.openSession(false);
		OCsrkMapper oCsrkMapper = sqlSession.getMapper(OCsrkMapper.class);
		
		int num=0;
		while(count>0) {			
			Map<String,Object> paramMap=new HashMap<String,Object>();
			paramMap.put("id", id);
			paramMap.put("posi", posi);
			paramMap.put("endid", id+posi);
			List<Csrk> list=oCsrkMapper.selectCsrks(paramMap);//從數據庫查詢數據
			int size=list.size();
			num+=size;
			logger.info("*******此次條數 "+size);
			System.out.println("*******總計條數 "+num);
			if(id>=end) {  //判斷id是否超過發送的範圍
				System.out.println(" id is " +id);
				count=0;
			}
			if(size==0) {
				id=id+posi;
			}
			for(int i=0;i<size;i++) {
				Csrk csrk=list.get(i);
				if(csrk!=null) {
					producer.send(csrk);//封裝的kafka生產者發送數據
				}	
				id=list.get(i).getID();
			}
		}
		producer.close();
		System.out.println("producer closed");

沒有使用char[]這種類型,代碼中String對象也都是正常的,還得從其它方向上尋找問題。

這種類型的對象還有一種可能就是緩存,mybatis分爲一級緩存和二級緩存。

一級緩存的作用域是SqlSession範圍的,當在同一個sqlSession中執行兩次相同的sql語句時,第一次執行完畢會將數據庫中查詢的數據寫到緩存(內存),第二次查詢時會從緩存中獲取數據,不再去底層數據庫查詢,從而提高查詢效率。
需要注意的是,如果SqlSession執行了DML操作(增刪改),並且提交到數據庫,MyBatis則會清空SqlSession中的一級緩存,這樣做的目的是爲了保證緩存中存儲的是最新的信息,避免出現髒讀現象。
當一個SqlSession結束後該SqlSession中的一級緩存也就不存在了。

二級緩存的作用域是mapper的同一個namespace。不同的sqlSession兩次執行相同的namespace下的sql語句,且向sql中傳遞的參數也相同,即最終執行相同的sql語句,則第一次執行完畢會將數據庫中查詢的數據寫到緩存,第二次查詢會從緩存中獲取數據,不再去底層數據庫查詢,從而提高效率。也就是說二級緩存是跨SqlSession進行數據共享的

注意:一級緩存是不能被關閉的

好了,回到代碼中sqlSession只被創建了一次,因此一級緩存被創建了後並未被清楚,加入以下清除一級緩存的代碼進行修改

			sqlSession.clearCache();
			List<Csrk> list=oCsrkMapper.selectCsrks(paramMap);//從數據庫查詢數據

重新使用Jprofiler進行測試,觀察發現char[]和String對象的size仍然在增長,那代開mybatis的xml文件發現開啓了二級緩存

<cache/>

那關閉對應sql的二級緩存

useCache="false"

重新使用Jprofiler執行,觀察,發現char[]和String對象的size穩定在10M以下,連續運行超過2200萬數據用時不到90分鐘,內存使用量穩定在80M以下

 

後續的思考

1.如果xml中沒有使用</cache>標籤會不會不會出現這個問題

  在xml中去掉</cache>標籤,同時去掉useCache="false"然後執行,觀察char[]和String對象的size,會隨着時間的增長而增長。

而在每次查詢前加入sqlSession.clearCache();代碼用來清除之前的一級緩存,觀察觀察char[]和String對象的size,很穩定,不會隨着時間的增長而變化,說明清除一級緩存成功。

 

部分內容參考https://www.jianshu.com/p/c553169c5921

 

 

 

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