tianhongdehao 2008-12-28 22:46
浏览 417
已采纳

请教一个JVM内存优化的问题

自己写的一个WEB爬虫程序,程序方面的优化我自己感觉做的不错了,各种对象的使用都想办法不让内存浪费。
但还是吃内存吃的很厉害
我想主要的消耗在于,对于每一个爬下来的网页,都要变成String在内存中分析超链接,这个估计很大。
另外对于每一个分析到的URL,都需要保存起来。


我当前使用的内存优化参数如下
-Xms1024M -Xmx1024M -Xmn512M -Xss128K -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC -XX:MaxPermSize=128M -XX:NewSize=128M -XX:MaxNewSize=128M -XX:SurvivorRatio=8

机器内存是1G。。上面那堆参数,除了最大最小堆内存我也是不求甚解,不是非常明白我设置了什么。网上搜答案也是五花八门,希望大家能推荐一个内存优化参数的文章
另外我上面设置的参数有哪些方面可以优化,或者哪些地方写的不对。

核心的代码我都贴到下面:(我是刚入门的菜鸟,大家别笑话我写的差哦)

这个是主类,负责控制,那个monitor大家就当做System.out.println,其实就是

package com.wood.core;

import java.util.concurrent.*;
import java.util.*;

import com.wood.core.monitor.*;


public class CrawlerController implements Runnable {
    //一个类,用来管理URL,由各抓取线程共享
    private URLMap urlmap;
    //已成功处理URL总数
    private int count;
    //目标完成任务总数
    private int total;
    //管理所有的抓取线程,必要时进行关闭,由于抓取线程存在I/O读取,而I/0读取中的线程是不可中断的,需调用线程的cancel函数
    private Crawler[] crawlers;
    //线程工厂,可以监视产生的线程工作状态
    private CrawlerFactory factory;
    //线程数目
    private int threadMax;
    //当前活动线程
    private int threadActive;
    //线程执行器
    private ExecutorService exec;
    
    //当系统关闭时取消
    private boolean canceled;
    
    public CrawlerController(String seed)
    {   
        this(seed,20,10000);        
    }
    public CrawlerController(List<string> seeds)
    {
        this(seeds,20,10000);
        
    }
    public CrawlerController(String seed,int threadNum)
    {
        this(seed,threadNum,10000);
        
    }
    public CrawlerController(List<string> seeds,int threadNum)
    {
        this(seeds,threadNum,10000);
        
    }   

    public CrawlerController(String seed,int threadNum,int total)
    {
        //初始化URLMap,保存抓取启始位置
        urlmap=new URLMap(seed);
        init(threadNum, total);
    }
    public CrawlerController(List<string> seeds,int threadNum,int total)
    {
        urlmap=new URLMap(seeds);
        init(threadNum, total);
    }
    //构造
    private void init(int threadNum,int total)
    {
        //初始完成URL总数为0
        count=0;
        //设置目标完成总数
        this.total=total;
        
        threadMax=threadNum;
        threadActive=threadNum;
        //初始化工作线程
        crawlers=new Crawler[threadMax];
        String id="Crawler ";       
        for(int i=0;i<threadmax;i++) crawlers[i]="new" crawler(id+(i+1),this,urlmap);="" factory="new" crawlerfactory();="" exec="Executors.newCachedThreadPool(factory);" canceled="false;" }="" 检查当前工作线程状态,并打印系统状态="" private="" boolean="" check()="" {="" if(canceled)="" return="" false;="" int="" count="getCount();" if(count="">total)
        {
            MonitorHolder.getMonitor().print("已抓取"+COUNT+"页面,完成目标任务"+total+"页面\n");
            cancel();
            return false;
        }
        threadActive=factory.getActive();
        /*if(threadActive<=0)
        {
            MonitorHolder.getMonitor().print("无活动工作线程,抓取任务提前结束\n");
            cancel();
            return false;           
        }*/
        MonitorHolder.getMonitor().print_status("统计信息:成功抓取"+COUNT+"页面,当前活动线程为"+threadActive+"个\n");
        return true;
    }
    //结束抓取
    public void cancel()
    {
        //调用每个抓取线程的离开方法
        for(Crawler cw:crawlers)
            cw.cancel();
        //销毁工厂
        factory.destory();
        exec.shutdownNow();
        MonitorHolder.getMonitor().print("成功结束抓取工作,共抓取"+getCount()+"页面\n");
        this.canceled=true;
    }
    public synchronized void count()
    {
        count++;
    }
    public synchronized int getCount()
    {
        return count;
    }
    public int getTotal()
    {
        return total;
    }
    public URLMap getMap()
    {
        return urlmap;
    }
    @Override
    public void run() {
        while(!Thread.currentThread().isInterrupted() && !canceled)
        {
            try {
                MonitorHolder.getMonitor().print("初始化完毕\n");
                MonitorHolder.getMonitor().print("开始抓取工作\n");
                for(Crawler cw:crawlers)
                    exec.execute(cw);
                int check_count=0;
                
                while (check()){            
                        TimeUnit.SECONDS.sleep(5);
                        check_count++;
                        if(check_count==24)
                        {
                            //每2分钟把待处理URL队列打乱一次                            
                            urlmap.shuffle();
                            MonitorHolder.getMonitor().print("控制信息!!!待抓取URL顺序将打乱\n");
                            check_count=0;
                            System.gc();
                        }                       
                }
                            
            } catch (InterruptedException e) {
                MonitorHolder.getMonitor().print("抓取工作被中断\n");
                cancel();
            }
        }
    }
    public static void main(String[] args)
    {
        List<string> seeds=new ArrayList<string>(15);
        //问问的各个分类
        seeds.add("http://wenwen.soso.com/");
        seeds.add("http://wenwen.soso.com/z/c1879048192.htm");
        seeds.add("http://wenwen.soso.com/z/c1090519040.htm");
        seeds.add("http://wenwen.soso.com/z/c1627389952.htm");
        seeds.add("http://wenwen.soso.com/z/c855638016.htm");
        seeds.add("http://wenwen.soso.com/z/c1191182336.htm");
        seeds.add("http://wenwen.soso.com/z/c1191182336.htm");
        seeds.add("http://wenwen.soso.com/z/c620756992.htm");
        seeds.add("http://wenwen.soso.com/z/c553648128.htm");
        seeds.add("http://wenwen.soso.com/z/c385875968.htm");
        seeds.add("http://wenwen.soso.com/z/c687865856.htm");
        seeds.add("http://wenwen.soso.com/z/c16777216.htm");
        seeds.add("http://wenwen.soso.com/z/c318767104.htm");
        seeds.add("http://wenwen.soso.com/z/c150994944.htm");
        seeds.add("http://wenwen.soso.com/z/c922746880.htm");
        seeds.add("http://wenwen.soso.com/z/c83886080.htm");
        CrawlerController controller=new CrawlerController(seeds,5,2000000);
        
        List<string> format=new ArrayList<string>(10);
        //问问回答问题的格式
        format.add("http://wenwen.soso.com/z/q");   
        controller.getMap().addLimit(format);
        
        Thread thread=new Thread(controller);
        thread.start(); 
    }

}


package com.wood.core;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.*;
import java.io.*;
import java.net.*;





import com.wood.core.monitor.*;
public class Crawler implements Runnable {
    //抓取线程ID
    private String id;
    //控制器
    private CrawlerController controller;
    //URL,供各线程共享
    private URLMap urlmap;  
    private boolean canceled;

    //保存抓取的网页内容,用于分析超链接
    private String content;
    //字符集
    private String charset;
    //匹配超链接的正则表达式
    private String link_reg="<a\\s+(.*?)href\\s*=\\s*\"?(.*?)[\"(\\s*?)](.*?)>";
    private Pattern link_pattern=Pattern.compile(link_reg,Pattern.CASE_INSENSITIVE);
    //匹配字符集的正则表达式
    private String charset_reg="<meta\\s+http-equiv=\"content-type\"\\s+content=\"text html;="" charset="(.*?)\&quot;(.*?)"> ";
    private Pattern charset_pattern=Pattern.compile(charset_reg, Pattern.CASE_INSENSITIVE);

    private Pattern dir_path_pattern=Pattern.compile("^\\w+$", Pattern.CASE_INSENSITIVE);
    //保存当前抓取的URL的主机
    private String host;
    //生成待建立的文件夹名
    private String host_path;
    //抓取网页的根目录
    private String root_path="E:\\web";
    private File root_dir=new File(root_path);
    //输入来自URL,输出到文件
    private InputStreamReader in=null;
    private OutputStreamWriter out=null;
    
    //输入输出缓冲区
    private char[] buf=new char[10240];
    //网页内容缓冲区
    private StringBuilder contentbuilder=new StringBuilder(1024*1024);
    //通过一个URL,下载网页内容
    private void download(URL url)
    {
         in=null;
         out=null;
         //建立抓取文件
         String urlfile=url.getFile();

         if(urlfile.endsWith("/"))
             urlfile+="indexindex";
         File file=new File(host_path,urlfile);
         File file_dir=new File(file.getParent());
         if(!file_dir.exists())
             file_dir.mkdirs();
         MonitorHolder.getMonitor().print(id+"开始准备下载"+url.toString()+"\n");
         try {
             //打开链接
             
            URLConnection conn=url.openConnection();
            
            //超时30秒
            conn.setConnectTimeout(30000);
            conn.setDoOutput(true);
            
            
            //不是HTML不下载
            /*String type=conn.getContentType();
            if( (type==null)|| (!type.equals("text/html")) )
            return;*/
            //将网页内容的缓冲区清空
            
            contentbuilder.setLength(0);            
            in=new InputStreamReader(conn.getInputStream(),charset);
            out=new OutputStreamWriter(new FileOutputStream(file),charset);
        
            int len;
            //读取网页内容,并写入文件,保存到网页内容缓冲里面
            while((len=in.read(buf, 0, 10240)) >0)
            {
                out.write(buf, 0, len);
                //append可以减小系统损耗
                contentbuilder.append(buf, 0, len);
            }       
            out.flush();
            //将网页内容缓冲区的内容读到content中,用于分析
            content=null;
            content=contentbuilder.toString();
        } catch (IOException e) {
            MonitorHolder.getMonitor().print("错误!!!"+id+"下载页面"+url.toString()+"错误\n");    
            
        }
        finally{
            try {
                if(in!=null)
                in.close();
                if(out!=null)
                out.close();
            } catch (IOException e) {

            }
            in=null;
            out=null;
        }
    }

    public Crawler(String id,CrawlerController controller,URLMap urlmap)
    {
        this.id=id;
        this.controller=controller;
        this.urlmap=urlmap;     
        this.charset="utf-8";
        canceled=false;
        MonitorHolder.getMonitor().print(id+"就绪\n");
    }
    //设置字符集,通过正则表达式获取字符集格式
    //为了性能,该功能并未使用
    private void setCharset()
    {
        Matcher matcher=charset_pattern.matcher(content);
        String CHARSET="GB2312";
        if(matcher.find())
            CHARSET=matcher.group(1).trim();
        this.charset=CHARSET;
        try {
            content=new String(content.getBytes("GB2312"),CHARSET);
        } catch (UnsupportedEncodingException e) {          
            e.printStackTrace();
        }
    }
    //从网页内容中分析超链接
    private List<string> retrieveLinks(URL url)
    {
        List<string> urls=new LinkedList<string>();
        if(content==null)
            return urls;
        Matcher matcher=link_pattern.matcher(content);
        String link;
        String host_reg="http://"+host;     
        String host_nowww;
        if(host.contains("www"))
            host_nowww=host.substring(host.lastIndexOf("w")+2);
        else {
            host_nowww=host;
        }
        while(matcher.find())
        {
            //通过抓取第1组的内容
            link=matcher.group(2).trim().toLowerCase();
            if (link.length() < 1) {
                  continue;
               }
            //网页内部链接,忽略
            if (link.charAt(0) == '#') {
                  continue;
              }
            //发送邮件链接,忽略
            if (link.indexOf("mailto:") != -1) {
                  continue;
               }
           if (link.toLowerCase().indexOf("javascript") != -1) {
                 continue;
              }
         

            //分析绝对地址或相对地址
            if (link.indexOf("://") == -1){
                  if (link.charAt(0) == '/') //处理绝对地址
                    link = "http://" + host+ link;
                  
                  else   if(link.startsWith("./"))
                      link="http://" + host+ link.substring(1);
                  
                  
                  else {         
                    String file = url.getFile();
                    String file_path=file.substring(0, file.lastIndexOf('/'));
                    while(link.startsWith("../"))
                    {
                        link=link.substring(link.indexOf("/")+1);
                        file_path=file_path.substring(0, file_path.lastIndexOf("/"));
                    }
                    link="http://"+host+file_path+"/"+link;
                  
                  }
             }
            
          
            

            
              int index = link.indexOf('#');
              if (index != -1) {
                link = link.substring(0, index);
              }
             if(!urlmap.testHost(link))
                 continue;          
   
              if(!urlmap.testLimit(link))
                  continue;
    
            urls.add(link);     

        }
        return urls;
    }
//设置主机并建立目录
    private void setHost(String host)
    {
        this.host=host;
        this.host_path=root_path+"\\"+host;
        File host_dir=new File(host_path);
        if(!host_dir.exists())
            host_dir.mkdirs();
    }
    @Override
    public void run() {
        if(!root_dir.exists())
            root_dir.mkdirs();
        while(!Thread.currentThread().isInterrupted() && !canceled && (controller.getCount()<controller.gettotal()) )="" {="" try="" {="" 获得一个待抓取的url,如果没有可用url,则进入阻塞状态,该方法调用是线程安全的="" string="" urlstring="urlmap.getURL();" monitorholder.getmonitor().print(id+"开始抓取"+urlstring+"\n");="" 建立url="" url="" url="new" url(urlstring);="" 设置主机="" sethost(url.gethost());="" 清空网页内容,并下贼="" content="null;" download(url);="" 未下载到内容="" if(content="=null)" {="" continue;="" }="" monitorholder.getmonitor().print(id+"抓取"+urlstring+"完毕,进行解析\n");="" setcharset();="" 分析超链接="" list<string=""> urls=retrieveLinks(url);
                //将分析到的超连接加入到待抓取的URL队列中,并将成功抓取数+1
                urlmap.addAll(urls);
                controller.count();
                MonitorHolder.getMonitor().print(id+"解析"+urlstring+"完毕,共计"+urls.size()+"个超链接,开始新任务\n");
                content=null;
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                MonitorHolder.getMonitor().print(id+"被中断\n");
                canceled=true;
            } catch (MalformedURLException e) {
                MonitorHolder.getMonitor().print(id+"报告:URL格式错误\n");                
            }catch (Exception e) {
                e.printStackTrace();
            }
            
        }       

    }
    //退出,由控制器调用,关闭所有底层I/O资源
    public void cancel()
    {
        
          try {
              if(in!=null)
                in.close(); 
            } catch (IOException e) {
                
            }
               
         try {
             if(out!=null)
             out.close();
            } catch (IOException e) {
                    
                    e.printStackTrace();
                }
        
           MonitorHolder.getMonitor().print(id+"停止工作\n");
          canceled=true;
    }

}
package com.wood.core;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.*;
import java.util.*;


public class URLMap {
    //待抓取的URL队列
    private LinkedList<string> URLQueue;
    //缓存解析出来的URL,使用Hash方便快速查找,<k,false>表示未抓取,<k,true>表示抓取结束
    private HashMap<string,boolean> cachedURL;
    private HashSet<string> hosts;
     
    private HashSet<string> format_limit;
    //默认构造器,初始化队列,集合
    private URLMap()
    {   
        URLQueue=new LinkedList<string>();
        cachedURL=new HashMap<string,boolean>(50000);
        hosts=new HashSet<string>(20);
        format_limit=new HashSet<string>(20);
    }   
    public URLMap(List<string> seeds)
    {
        this();
        for(String s:seeds)
        cachedURL.put(s, false);
        URLQueue.addAll(seeds);
        for (String string : seeds) {
            try {
                URL test=new URL(string);
                String host=test.getHost();
                hosts.add(host);
            } catch (MalformedURLException e) {
                
                e.printStackTrace();
            }           
        }
    }
    public URLMap(String seed)
    {
        this(); 
        cachedURL.put(seed, false);
        URLQueue.add(seed);
        try {
            URL test=new URL(seed);
            hosts.add(test.getHost());
        } catch (MalformedURLException e) {
            
            e.printStackTrace();
        }   
    }

    //将解析出来的URL添加到URLMap中,如果有重复则忽略,URL有效性由外部保证,该方法是线程安全的
    public synchronized void addURL(String url) throws InterruptedException
    {
        //如果该URL已存在,忽略该URL
        if(cachedURL.keySet().contains(url))
            return;

        cachedURL.put(url, false);
        URLQueue.add(url);
        //有可用URL,唤醒所有阻塞线程
        notifyAll();
    }
    //将解析出来的URL添加到URLMap中,如果有重复则忽略,URL有效性由外部保证,该方法是同步的
    public synchronized void addAll(List<string> urls) throws InterruptedException
    {
        for (String url : urls) {
            if(cachedURL.keySet().contains(url))
                continue;
            cachedURL.put(url,false);
            URLQueue.add(url);          
        }
        notifyAll();
    }
    //从当前URL队列中获取一个URL,如果当前队列无可用URL,则该线程进入阻塞状态
    public synchronized String getURL() throws InterruptedException
    {
        //该处进入阻塞
        while(URLQueue.size()==0)
            wait();
        //将其从队列中删除
        String url=URLQueue.remove();       
        cachedURL.put(url, true);
        return url;
    }
    public boolean testHost(String host)
    {
        for(String host_allow:hosts)
        {
            if(host.contains(host_allow))
                return true;
        }
        return false;
    }
    public void addHost(List<string> l)
    {
        hosts.addAll(l);
    }
    public void addLimit(List<string> l)
    {
        format_limit.addAll(l);
    }
    public boolean testLimit(String url)
    {
        if(format_limit.size()==0)
            return true;
        else
        {
            for(String s:format_limit)
                if(url.contains(s))
                    return true;
        }
        return false;
    }
    //将待抓取队列打乱
    private int swap_check=0;
    public synchronized void shuffle()
    {
        swap_check++;
        int size=URLQueue.size();
        if(size<1000)
        {
        java.util.Collections.shuffle(URLQueue);
        swap_check=0;
        }
        else {
            if(size>=1000 && size<5000)
            {
                if(swap_check==5)
                {
                    java.util.Collections.shuffle(URLQueue);
                    swap_check=0;
                }
            }
            else {
                if(swap_check==15)
                {
                    java.util.Collections.shuffle(URLQueue);
                    swap_check=0;
                }               
            }
        }
    }

}



附件里是内存使用情况
问题补充
主要占用内存的是char[]
我的策略是所有分析到的有效URL都存储在HashMap中
但是几万个URL不至于占用大多数内存吧?
我的工作线程只有5个,设置的多了,一会就崩了

哪位高人路过,帮我看眼吧
祝你新年好运~

 

附件里是内存占用,缓存的URL总数是20000个,一个大概占用80B,程序内存总数是150MB

我不知道到底是什么东西不停的吃内存,回收不掉,程序运行的时间越长,线程数越多,内存吃的越快

  • 写回答

2条回答 默认 最新

  • 不良校长 2008-12-29 21:29
    关注

    你打个能运行的代码包上来,我帮你看看。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 请问有人知道怎么用工艺库里面的sdb文件通过virtuoso导出来library里面每个cell的symbol吗?
  • ¥20 海思 nnie 编译 报错
  • ¥50 决策面并仿真,要求有仿真结果图
  • ¥15 springboot接入微信支付SDK
  • ¥50 大区域的遥感影像匹配 怎么做啊
  • ¥15 求解答:pytorch跑yolov8神经网络受挫
  • ¥20 Js代码报错问题不知道怎么解决
  • ¥15 gojs 点击按钮node的position位置进行改变,再次点击回到原来的位置
  • ¥20 halcon 图像拼接
  • ¥15 webstorm上开发的vue3+vite5+typeScript打包时报错