成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

解決Out Of Memory問題實戰

企業動態
最近用solr進行了一個做索引的測試,在長時間運行做索引的程序之后,會出現堆內存溢出的錯誤。本文Po出簡單代碼,并對該問題進行分析和解決。

最近用solr進行了一個做索引的測試,在長時間運行做索引的程序之后,會出現堆內存溢出的錯誤。本文Po出簡單代碼,并對該問題進行分析和解決。

數據

solr版本為5.5.0,使用三臺服務器配置solr集群,solr以cloud方式啟動,使用自己配置的zookeeper。在solr上新建一個數據集,并分為3片,每片配置兩個replica,交叉備份。

要做索引的數據量是2600+萬,存儲在MySql數據庫表中,數據一直在更新。一次從數據庫表中查詢5000條數據。solr搜索主要針對標題和內容,因此需要將表中的標題和內容做到solr中。其中內容占用空間非常大,在數據庫中使用mediumtext進行存儲。

數據集的配置如下:

  1. <field name="id" type="string" indexed="true" stored="true" required="true" />  
  2. <field name="title" type="text_ik" indexed="true" stored="true" /> 
  3. <field name="url" type="string" indexed="false" stored="true" /> 
  4. <field name="intime" type="string" indexed="true" stored="true"/> 
  5. <field name="content" type="text_ik" indexed="true" stored="false"/> 
  6. <!-- for title and content --> 
  7. <field name="allcontent" type="text_ik" indexed="true" stored="false" multiValued="true"/> 
  8. <copyField source="title" dest="allcontent" />       
  9. <copyField source="content" dest="allcontent" /> 

搜索模式分為標題檢索和全文檢索,因此配置了allcontent復合字段,將標題和內容都放到這里。

做索引的程序使用Java實現,具體思路如下:

  1. 由于數據一直在更新,因此使用while(true)循環進行處理,一次循環查詢5000條數據;
  2. 數據量很大,如果程序出現異常停止運行,要保證下次重新啟動時從上次停的“點”繼續做索引,因此要將這個“點”存儲在文件中,防止丟失,本程序使用數據插入時間作為這個“點”;
  3. 一次查詢5000條數據做處理,統一插入到solr中。

介紹了這么多,終于把前提說完了,下面上類圖和具體代碼,說明問題。

做索引的程序使用Java實現圖

  1. public abstract class SolrAbstract{ 
  2.   
  3.     public static final Logger log = Logger.getLogger(SolrAbstract.class); 
  4.       
  5.     public HttpSolrClient server; 
  6.     public List data; // 數據庫中需要處理的數據 
  7.     public Collection docs = new CopyOnWriteArrayList(); 
  8.       
  9.     public  SolrAbstract(HttpSolrClient server) throws IOException, SolrServerException { 
  10.         log.info("開始做索引");   
  11.         if(server==null) 
  12.             throw new SolrServerException("server不能為空"); 
  13.         this.server = new HttpSolrClient(getUrl()); 
  14.     } 
  15.       
  16.     public SolrAbstract()throws SolrServerException,IOException{ 
  17.         log.info("開始做索引"); 
  18.         this.server = new HttpSolrClient(getUrl()); 
  19.     } 
  20.   
  21.     public SolrAbstract(List data) throws IOException, SolrServerException { 
  22.         if(data == null || data.isEmpty()) { 
  23.             try { 
  24.                 throw new InvalidParameterException("List不能為空"); 
  25.             } catch (InvalidParameterException e) { 
  26.                 e.printStackTrace(); 
  27.             } 
  28.         } 
  29.         this.data = data; 
  30.     } 
  31.   
  32.     public String getUrl() { 
  33.         return "http://192.168.20.10:8983/solr/test/"; // test為數據集名稱 
  34.     } 
  35.   
  36. public class DoIndex extends SolrAbstract { 
  37.       
  38.     public DoIndex(String url) throws SolrServerException, IOException { 
  39.         super(); 
  40.     } 
  41.       
  42.     public void process() throws Exception { 
  43.         for (int i = 0; i < this.data.size(); i++) { 
  44.             Product p = (Product) this.data.get(i); 
  45.             SolrInputDocument doc = new SolrInputDocument(); 
  46.             doc.addField("id", p.getId()); 
  47.             doc.addField("title", p.getTitle()); 
  48.             doc.addField("url", p.getUrl()); 
  49.             doc.addField("intime", p.getIntime()); 
  50.             doc.addField("content", p.getContent()); 
  51.             doc.addField("content", p.getContent()); 
  52.             docs.add(doc); 
  53.         } 
  54.     } 
  55.   
  56.     public synchronized void commitIndex() throws IOException, SolrServerException { 
  57.         long start = System.currentTimeMillis(); 
  58.         if (docs.size() > 0) { 
  59.             server.add(docs); 
  60.         }                
  61.         server.commit(); 
  62.         long endTime = System.currentTimeMillis(); 
  63.         log.info("提交索引花費時間:"+((endTime - start))); 
  64.         docs.clear(); 
  65.         log.info("結束做索引"); 
  66.     } 
  67.   
  68. public class ProcessData { 
  69.       
  70.     DoIndex index ; 
  71.     private JdbcUtil jdbc; 
  72.     private static String RECORD_INTIME ; 
  73.       
  74.     public ProcessData(JdbcUtil jdbc){ 
  75.         try { 
  76.             RECORD_INTIME = "/home/solr/recordIntime.txt"
  77.             this.jdbc = jdbc; 
  78.             index = new DoIndex(); 
  79.         } catch (Exception e) { 
  80.             e.printStackTrace(); 
  81.         } 
  82.     } 
  83.   
  84.     public void processData() throws Exception{ 
  85.         int startTime = Integer.parseInt(FileUtils.readFiles(RECORD_INTIME)); // ***startTime=0,從文件中讀取記錄時間 
  86.         String sql = "select id,title,content,url,intime from testTable where intime>startTime limit 5000; 
  87.         List<HashMap> list = jdbc.queryList(sql); 
  88.         while(list!=null&&list.size()>0){ 
  89.             index.data = new ArrayList<Product>(); 
  90.             for (int i = 0; i < list.size(); i++) { 
  91.                 Map<String,Object> item =  list.get(i); 
  92.                 Product p = new Product(); 
  93.                 p.setId(item.get("id").toString()); 
  94.                 p.setTitle(item.get("title").toString()); 
  95.                 p.setUrl(item.get("url").toString()); 
  96.                 p.setIntime(item.get("intime").toString()); 
  97.                 p.setContent(item.get("content").toString()); 
  98.                 index.data.add(p); 
  99.                 startTime = (int)item.get("intime"); 
  100.             }        
  101.             index.process(); // 組裝索引數據 
  102.             index.commitIndex(); // 提交索引 
  103.             index.data.clear(); 
  104.             list.clear(); 
  105.             FileUtils.writeFiles(startTime, RECORD_INTIME); // 將***的時間寫入到文件中 
  106.         } 
  107.     } 

上述代碼在小數據量短時間內測試沒有問題,但運行幾個小時之后報錯堆內存溢出。

檢查程序,發現SolrAbstract類中定義了兩個成員變量data和docs,這兩個都是“大對象”,雖然在程序中都進行了clear(),但還是懷疑JVM并沒有及時清理這兩個對象引用的對象。還有processData()方法中將從數據庫查詢的數據存入list中,這樣可能也會導致內存不會被及時回收。

抱著試試看的態度對程序進行了修改。修改后的程序如下:

  1. public class ProcessData { 
  2.       
  3.     private JdbcUtil jdbc; 
  4.     private static String RECORD_INTIME ; 
  5.     public ProcessData(JdbcUtil jdbc){ 
  6.         try { 
  7.             RECORD_INTIME = "/home/solr/recordIntime.txt"
  8.             this.jdbc = jdbc; 
  9.         } catch (Exception e) { 
  10.             e.printStackTrace(); 
  11.         } 
  12.     } 
  13.   
  14.     public void processData() throws Exception{ 
  15.         int startTime = Integer.parseInt(FileUtils.readFiles(RECORD_INTIME)); // ***startTime=0,從文件中讀取記錄時間 
  16.         String sql = "select id,title,content,url,intime from testTable where intime>startTime limit 5000; 
  17.         ResultSet rs = null
  18.         try{ 
  19.             rs = jdbc.query(sql); // 直接使用ResultSet獲取數據結果,不再將結果存入list中 
  20.             List list = new ArrayList(); 
  21.             while(rs!=null&&rs.next()){ 
  22.                 SolrInputDocument doc = new SolrInputDocument(); 
  23.                 doc.addField("id", rs.getInt("id")); 
  24.                 doc.addField("title",rs.getString("title")); 
  25.                 doc.addField("url",rs.getString("url")); 
  26.                 doc.addField("intime",rs.getInt("intime")); 
  27.                 doc.addField("content", rs.getString("content")); 
  28.                 list.add(doc); 
  29.             } 
  30.             commitData(list); 
  31.             list.clear(); 
  32.             list.removeAll(list); 
  33.             list = null
  34.               
  35.         }catch(Exception e) { 
  36.             e.printStackTrace(); 
  37.         }finally { 
  38.             try{ 
  39.                 if(rs!=null) { 
  40.                     rs.close(); 
  41.                     rs = null
  42.                 } 
  43.             }catch(Exception e) { 
  44.                 e.prepareStatement(); 
  45.             } 
  46.         } 
  47.     } 
  48.       
  49.     public void commitData(Collection docs) { 
  50.         try { 
  51.             long start = System.currentTimeMillis(); 
  52.             if (docs.size() > 0) { 
  53.                 server.add(docs); 
  54.             } 
  55.             log.info("當前占用內存: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())); 
  56.             server.commit(); 
  57.             long endTime = System.currentTimeMillis(); 
  58.             log.info("提交索引時間:"+((endTime - start))); 
  59.             docs.clear(); 
  60.             docs = null
  61.             log.info("提交索引結束"); 
  62.         } catch (SolrServerException e) { 
  63.             e.printStackTrace(); 
  64.         } catch (IOException e) { 
  65.             e.printStackTrace(); 
  66.         } 
  67.     } 

代碼進行上述修改后,運行了幾個小時,不再報堆內存溢出的錯誤了。

現在假設業務需求修改了,要求在查詢5000條數據時,對每條數據進行處理:需要根據id去其他表中查詢修改的標題并寫入索引中。

我在上述代碼中直接進行了修改,在while(rs!=null&&rs.next())循環中加入了查詢另外一張表的代碼。運行程序發現當前占用的內存越來越多。于是我在服務器上使用了jstat查詢當前虛擬機內存占用情況,命令如下:

  1. jstat -gcutil pid 10000 

10秒輸出一次內存占用及垃圾回收情況,發現Young GC和Full GC非常頻繁,并且Full GC之后,老年代內存回收情況并不好,監控如下:

10秒輸出一次內存占用及垃圾回收情況

這里可以看到第四列老年到剛開始只占用了28.64%,運行一段時間后內存占用量到81.22%,進行Full GC之后,仍然占用52.87%。

檢查代碼,發現是在while(rs!=null&&rs.next())里查詢另外一張表的代碼出現的問題。開發匆忙,我從網上隨便找了一個數據庫工具類進行的開發,發現里面的query方法是這樣的:

  1. public ResultSet query(String sql){ 
  2.     ResultSet rs = null
  3.     PreparedStatement ps = null
  4.     try { 
  5.         ps = conn.prepareStatement(sql); 
  6.         rs = ps.executeQuery(); 
  7.     } catch (SQLException e) { 
  8.         e.printStackTrace(); 
  9.     } 
  10.     return rs; 

這段程序并沒有及時釋放ps,因為查詢頻繁,ps引用的對象一直得不到回收,導致這些對象進入了老年代,并且虛擬機檢查這些對象仍然與GC Root有關聯,因此導致老年代垃圾回收效果不好。也是這個原因導致的Young GC和Full GC非常頻繁。

大致找到了問題原因,修改代碼如下:

  1. public void processData() throws Exception{ 
  2.     int startTime = Integer.parseInt(FileUtils.readFiles(RECORD_INTIME)); // ***startTime=0,從文件中讀取記錄時間 
  3.     String sql = "select id,title,content,url,intime from testTable where intime>startTime limit 5000; 
  4.     ResultSet rs = null
  5.     try{ 
  6.         rs = jdbc.query(sql); // 直接使用ResultSet獲取數據結果,不再將結果存入list中 
  7.         List list = new ArrayList(); 
  8.         while(rs!=null&&rs.next()){ 
  9.             SolrInputDocument doc = new SolrInputDocument(); 
  10.             doc.addField("id", rs.getInt("id")); 
  11.             doc.addField("title",rs.getString("title")); 
  12.             doc.addField("url",rs.getString("url")); 
  13.             doc.addField("intime",rs.getInt("intime")); 
  14.             doc.addField("content", rs.getString("content")); 
  15.             PreparedStatement ps1 = jdbc.getConn().prepareStatement("select newtitle from testTable2 where id=?"); 
  16.             ps1.setInt(1, rs.getInt("id")); 
  17.             ResultSet rs1 = ps1.executeQuery(); 
  18.             String newtitle = ""
  19.             while(rs1!=null&&rs1.next()) { 
  20.                 newtitle = rs1.getString("newtitle"); 
  21.             } 
  22.             if(rs1!=null) { 
  23.                 rs1.close(); 
  24.                 rs1 = null
  25.             } 
  26.             if(ps1!=null) { 
  27.                 ps1.close(); 
  28.                 ps1 = null
  29.             } 
  30.             doc.addField("newtitle",newtitle); // 當然solr數據集的配置文件也需要修改,這里不再贅述 
  31.             list.add(doc); 
  32.         } 
  33.         commitData(list); 
  34.         list.clear(); 
  35.         list.removeAll(list); 
  36.         list = null
  37.           
  38.     }catch(Exception e) { 
  39.         e.printStackTrace(); 
  40.     }finally { 
  41.         try{ 
  42.             if(rs!=null) { 
  43.                 rs.close(); 
  44.                 rs = null
  45.             } 
  46.         }catch(Exception e) { 
  47.             e.prepareStatement(); 
  48.         } 
  49.     } 

經過上面的修改,再次運行程序,不再發生內存溢出了,用jstat監控如下:

用jstat監控

可以看到Young GC和Full GC正常了。Full GC在開始階段基本沒有被觸發,Young GC也少了很多。而第四列的老年代回收情況也變的正常了。

上面的例子很簡單,導致堆內存溢出的問題也比較常見。我想說的是看完一本書可能能被記住的內容并不多,但隨著經驗的積累和實踐的增多,你會慢慢有一種感覺,能夠大致定位到問題在哪里,這樣就夠了。

參考:《深入理解Java虛擬機:JVM高級特性與***實踐(第2版)》

【本文為51CTO專欄作者“王森豐”的原創稿件,轉載請注明出處】

責任編輯:趙寧寧 來源: 神算子
相關推薦

2012-10-08 09:50:45

2017-02-24 15:28:33

Android內存溢出方法總結

2021-09-26 06:43:07

MySQL深分頁優化

2021-11-09 10:20:15

MySQL深分頁數據庫

2021-09-27 13:33:03

MySQL深分頁數據庫

2023-07-26 15:46:52

Docker管理容器

2025-02-07 08:14:15

Java容器應用

2022-09-02 16:07:02

團隊問題

2009-08-21 17:48:28

.NET框架DLL Hell問題

2013-03-20 09:54:07

2009-12-08 16:30:29

WCF程序

2010-03-10 10:24:16

Linux ssh后門

2012-09-05 11:09:15

SELinux操作系統

2009-08-06 10:35:27

C# lock thi

2013-12-05 09:45:04

HadoopHadoop架構圖

2009-12-29 11:40:50

2010-07-15 14:40:42

AIX TELNET

2010-02-06 16:13:49

Ubuntu Auda

2009-09-22 17:32:38

Hibernate A

2012-01-13 13:05:41

Scale Out網絡
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91影片| 视频一区二区三区四区五区 | 黄色在线网站 | 在线免费观看a级片 | 国产精品国产三级国产aⅴ原创 | 精品国产乱码久久久久久蜜柚 | 精品久久香蕉国产线看观看亚洲 | 亚洲精品视 | 成人黄视频在线观看 | 免费观看视频www | 天天干天天爱天天操 | 国产精品99久久久精品免费观看 | 成人国产在线观看 | 久久久无码精品亚洲日韩按摩 | 99热视 | www.亚洲精品| 999久久久国产精品 欧美成人h版在线观看 | 伊人超碰在线 | 四虎影院久久 | 草久在线视频 | 天天影视网天天综合色在线播放 | 91国产在线视频在线 | 狠狠操电影 | 亚洲欧美少妇 | 午夜丰满寂寞少妇精品 | 免费观看一区二区三区毛片 | 亚洲国产精品美女 | 久久久久久久91 | 国产精品美女久久久久aⅴ国产馆 | 久久亚洲天堂 | 国产午夜精品一区二区三区嫩草 | 中文字幕免费在线观看 | 亚洲精品久久久久久下一站 | 99亚洲精品 | 密室大逃脱第六季大神版在线观看 | 91精品久久久久久久久久 | 日韩国产在线观看 | 91色综合 | 91久久精品一区二区二区 | 久久久久国产一区二区三区四区 | 国产精品自产拍 |