自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

解決Out Of Memory問(wèn)題實(shí)戰(zhàn)

企業(yè)動(dòng)態(tài)
最近用solr進(jìn)行了一個(gè)做索引的測(cè)試,在長(zhǎng)時(shí)間運(yùn)行做索引的程序之后,會(huì)出現(xiàn)堆內(nèi)存溢出的錯(cuò)誤。本文Po出簡(jiǎn)單代碼,并對(duì)該問(wèn)題進(jìn)行分析和解決。

最近用solr進(jìn)行了一個(gè)做索引的測(cè)試,在長(zhǎng)時(shí)間運(yùn)行做索引的程序之后,會(huì)出現(xiàn)堆內(nèi)存溢出的錯(cuò)誤。本文Po出簡(jiǎn)單代碼,并對(duì)該問(wèn)題進(jìn)行分析和解決。

數(shù)據(jù)

solr版本為5.5.0,使用三臺(tái)服務(wù)器配置solr集群,solr以cloud方式啟動(dòng),使用自己配置的zookeeper。在solr上新建一個(gè)數(shù)據(jù)集,并分為3片,每片配置兩個(gè)replica,交叉?zhèn)浞荨?/p>

要做索引的數(shù)據(jù)量是2600+萬(wàn),存儲(chǔ)在MySql數(shù)據(jù)庫(kù)表中,數(shù)據(jù)一直在更新。一次從數(shù)據(jù)庫(kù)表中查詢5000條數(shù)據(jù)。solr搜索主要針對(duì)標(biāo)題和內(nèi)容,因此需要將表中的標(biāo)題和內(nèi)容做到solr中。其中內(nèi)容占用空間非常大,在數(shù)據(jù)庫(kù)中使用mediumtext進(jìn)行存儲(chǔ)。

數(shù)據(jù)集的配置如下:

  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" /> 

搜索模式分為標(biāo)題檢索和全文檢索,因此配置了allcontent復(fù)合字段,將標(biāo)題和內(nèi)容都放到這里。

做索引的程序使用Java實(shí)現(xiàn),具體思路如下:

  1. 由于數(shù)據(jù)一直在更新,因此使用while(true)循環(huán)進(jìn)行處理,一次循環(huán)查詢5000條數(shù)據(jù);
  2. 數(shù)據(jù)量很大,如果程序出現(xiàn)異常停止運(yùn)行,要保證下次重新啟動(dòng)時(shí)從上次停的“點(diǎn)”繼續(xù)做索引,因此要將這個(gè)“點(diǎn)”存儲(chǔ)在文件中,防止丟失,本程序使用數(shù)據(jù)插入時(shí)間作為這個(gè)“點(diǎn)”;
  3. 一次查詢5000條數(shù)據(jù)做處理,統(tǒng)一插入到solr中。

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

做索引的程序使用Java實(shí)現(xiàn)圖

  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; // 數(shù)據(jù)庫(kù)中需要處理的數(shù)據(jù) 
  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為數(shù)據(jù)集名稱 
  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("提交索引花費(fèi)時(shí)間:"+((endTime - start))); 
  64.         docs.clear(); 
  65.         log.info("結(jié)束做索引"); 
  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,從文件中讀取記錄時(shí)間 
  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(); // 組裝索引數(shù)據(jù) 
  102.             index.commitIndex(); // 提交索引 
  103.             index.data.clear(); 
  104.             list.clear(); 
  105.             FileUtils.writeFiles(startTime, RECORD_INTIME); // 將***的時(shí)間寫入到文件中 
  106.         } 
  107.     } 

上述代碼在小數(shù)據(jù)量短時(shí)間內(nèi)測(cè)試沒有問(wèn)題,但運(yùn)行幾個(gè)小時(shí)之后報(bào)錯(cuò)堆內(nèi)存溢出。

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

抱著試試看的態(tài)度對(duì)程序進(jìn)行了修改。修改后的程序如下:

  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,從文件中讀取記錄時(shí)間 
  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獲取數(shù)據(jù)結(jié)果,不再將結(jié)果存入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("當(dāng)前占用內(nèi)存: " + (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())); 
  56.             server.commit(); 
  57.             long endTime = System.currentTimeMillis(); 
  58.             log.info("提交索引時(shí)間:"+((endTime - start))); 
  59.             docs.clear(); 
  60.             docs = null
  61.             log.info("提交索引結(jié)束"); 
  62.         } catch (SolrServerException e) { 
  63.             e.printStackTrace(); 
  64.         } catch (IOException e) { 
  65.             e.printStackTrace(); 
  66.         } 
  67.     } 

代碼進(jìn)行上述修改后,運(yùn)行了幾個(gè)小時(shí),不再報(bào)堆內(nèi)存溢出的錯(cuò)誤了。

現(xiàn)在假設(shè)業(yè)務(wù)需求修改了,要求在查詢5000條數(shù)據(jù)時(shí),對(duì)每條數(shù)據(jù)進(jìn)行處理:需要根據(jù)id去其他表中查詢修改的標(biāo)題并寫入索引中。

我在上述代碼中直接進(jìn)行了修改,在while(rs!=null&&rs.next())循環(huán)中加入了查詢另外一張表的代碼。運(yùn)行程序發(fā)現(xiàn)當(dāng)前占用的內(nèi)存越來(lái)越多。于是我在服務(wù)器上使用了jstat查詢當(dāng)前虛擬機(jī)內(nèi)存占用情況,命令如下:

  1. jstat -gcutil pid 10000 

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

10秒輸出一次內(nèi)存占用及垃圾回收情況

這里可以看到第四列老年到剛開始只占用了28.64%,運(yùn)行一段時(shí)間后內(nèi)存占用量到81.22%,進(jìn)行Full GC之后,仍然占用52.87%。

檢查代碼,發(fā)現(xiàn)是在while(rs!=null&&rs.next())里查詢另外一張表的代碼出現(xiàn)的問(wèn)題。開發(fā)匆忙,我從網(wǎng)上隨便找了一個(gè)數(shù)據(jù)庫(kù)工具類進(jìn)行的開發(fā),發(fā)現(xiàn)里面的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; 

這段程序并沒有及時(shí)釋放ps,因?yàn)椴樵冾l繁,ps引用的對(duì)象一直得不到回收,導(dǎo)致這些對(duì)象進(jìn)入了老年代,并且虛擬機(jī)檢查這些對(duì)象仍然與GC Root有關(guān)聯(lián),因此導(dǎo)致老年代垃圾回收效果不好。也是這個(gè)原因?qū)е碌腨oung GC和Full GC非常頻繁。

大致找到了問(wèn)題原因,修改代碼如下:

  1. public void processData() throws Exception{ 
  2.     int startTime = Integer.parseInt(FileUtils.readFiles(RECORD_INTIME)); // ***startTime=0,從文件中讀取記錄時(shí)間 
  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獲取數(shù)據(jù)結(jié)果,不再將結(jié)果存入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); // 當(dāng)然solr數(shù)據(jù)集的配置文件也需要修改,這里不再贅述 
  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.     } 

經(jīng)過(guò)上面的修改,再次運(yùn)行程序,不再發(fā)生內(nèi)存溢出了,用jstat監(jiān)控如下:

用jstat監(jiān)控

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

上面的例子很簡(jiǎn)單,導(dǎo)致堆內(nèi)存溢出的問(wèn)題也比較常見。我想說(shuō)的是看完一本書可能能被記住的內(nèi)容并不多,但隨著經(jīng)驗(yàn)的積累和實(shí)踐的增多,你會(huì)慢慢有一種感覺,能夠大致定位到問(wèn)題在哪里,這樣就夠了。

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

【本文為51CTO專欄作者“王森豐”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)注明出處】

責(zé)任編輯:趙寧寧 來(lái)源: 神算子
相關(guān)推薦

2012-10-08 09:50:45

2017-02-24 15:28:33

Android內(nèi)存溢出方法總結(jié)

2021-09-26 06:43:07

MySQL深分頁(yè)優(yōu)化

2021-11-09 10:20:15

MySQL深分頁(yè)數(shù)據(jù)庫(kù)

2021-09-27 13:33:03

MySQL深分頁(yè)數(shù)據(jù)庫(kù)

2023-07-26 15:46:52

Docker管理容器

2025-02-07 08:14:15

Java容器應(yīng)用

2022-09-02 16:07:02

團(tuán)隊(duì)問(wèn)題

2009-08-21 17:48:28

.NET框架DLL Hell問(wèn)題

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操作系統(tǒng)

2009-08-06 10:35:27

C# lock thi

2013-12-05 09:45:04

HadoopHadoop架構(gòu)圖

2009-09-22 17:32:38

Hibernate A

2010-07-15 14:40:42

AIX TELNET

2010-02-06 16:13:49

Ubuntu Auda

2009-12-29 11:40:50

2012-01-13 13:05:41

Scale Out網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)