我试图优化(内存)我的程序,但 GC 仍然让它滞后

Posted

技术标签:

【中文标题】我试图优化(内存)我的程序,但 GC 仍然让它滞后【英文标题】:I have tried to optimize (memory) my program, but GC is still making it lag 【发布时间】:2018-08-29 08:31:32 【问题描述】:

我用 Java 编写了一个软件,通过使用代理发送 HTTP 请求来检查代理是否正常工作。

从数据库中提取大约 30,000 个代理,然后尝试检查它们是否正常运行。从数据库收到的代理过去以ArrayList<String> 的形式返回,但由于以下原因已更改为Deque<String>

程序的工作方式是有一个ProxyRequest 对象,它将 IP 和端口分别存储为 String 和 int。 ProxyRequest 对象有一个方法 isWorkingProxy() 尝试使用代理发送请求并返回 boolean 是否成功。

这个ProxyRequest 对象被一个RunnableProxyRequest 对象包裹,该对象在覆盖的run() 方法中调用super.isWorkingProxy()。根据super.isWorkingProxy() 的响应,RunnableProxyRequest 对象更新 mysql 数据库。

注意MySQL数据库的更新是synchronized()

它使用 FixedThreadPool(在 VPS 上)在 750 个线程上运行,但面向 最后,它变得非常慢(卡在约 50 个线程上),这显然 意味着垃圾收集器正在工作。这就是问题所在。

我尝试了以下方法来改善延迟,但似乎不起作用:

1) 使用Deque<String> 代理并使用Deque.pop() 获取代理所在的String。这(我相信)不断使Deque<String> 更小,这应该会改善由 GC 引起的延迟。

2) 设置con.setConnectTimeout(this.timeout);,其中this.timeout = 5000; 这样,连接应该在5 秒内返回结果。如果不是,则线程已完成,不应再在线程池中处于活动状态。

除此之外,我不知道还有什么其他方法可以提高性能。

谁能为我推荐一种提高性能的方法,以避免/停止在 GC 线程结束时滞后?我知道有一个关于此的 *** 问题 (Java threads slow down towards the end of processing),但我已经尝试了答案中的所有内容,但它对我没有用。

感谢您的宝贵时间。

代码sn-ps:

FixedThreadPool循环添加线程:

//This code is executed recursively (at the end, main(args) is called again)
//Create the threadpool for requests
//Threads is an argument that is set to 750.
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(threads);
Deque<String> proxies = DB.getProxiesToCheck();

while(proxies.isEmpty() == false) 
    try 
        String[] split = proxies.pop().split(":");

        Runnable[] checks = new Runnable[] 
            //HTTP check
            new RunnableProxyRequest(split[0], split[1], Proxy.Type.HTTP, false),
            //SSL check
            new RunnableProxyRequest(split[0], split[1], Proxy.Type.HTTP, true),
            //SOCKS check
            new RunnableProxyRequest(split[0], split[1], Proxy.Type.SOCKS, false)
            //Add more checks to this list as time goes...
        ;

        for(Runnable check : checks) 
            executor.submit(check);
        

     catch(IndexOutOfBoundsException e) 
        continue;
    

ProxyRequest类:

//Proxy details
private String proxyIp;
private int proxyPort;
private Proxy.Type testingType;

//Request details
private boolean useSsl;

public ProxyRequest(String proxyIp, String proxyPort, Proxy.Type testingType, boolean useSsl) 
    this.proxyIp = proxyIp;
    try 
        this.proxyPort = Integer.parseInt(proxyPort);
     catch(NumberFormatException e) 
        this.proxyPort = -1;
    
    this.testingType = testingType;
    this.useSsl = useSsl;


public boolean isWorkingProxy() 
    //Case of an invalid proxy
    if(proxyPort == -1) 
        return false;
    

    HttpURLConnection con = null;

    //Perform checks on URL
    //IF any exception occurs here, the proxy is obviously bad.
    try 
        URL url = new URL(this.getTestingUrl());
        //Create proxy
        Proxy p = new Proxy(this.testingType, new InetSocketAddress(this.proxyIp, this.proxyPort));
        //No redirect
        HttpURLConnection.setFollowRedirects(false);
        //Open connection with proxy
        con = (HttpURLConnection)url.openConnection(p);
        //Set the request method
        con.setRequestMethod("GET");
        //Set max timeout for a request.
        con.setConnectTimeout(this.timeout);
     catch(MalformedURLException e) 
        System.out.println("The testing URL is bad. Please fix this.");
        return false;
     catch(Exception e) 
        return false;
    

    try(
            BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
            ) 

        String inputLine = null; StringBuilder response = new StringBuilder();
        while((inputLine = in.readLine()) != null) 
            response.append(inputLine);
        

        //A valid proxy!
        return con.getResponseCode() > 0;

     catch(Exception e) 
        return false;
    

RunnableProxyRequest类:

public class RunnableProxyRequest extends ProxyRequest implements Runnable 


    public RunnableProxyRequest(String proxyIp, String proxyPort, Proxy.Type testingType, boolean useSsl) 

        super(proxyIp, proxyPort, testingType, useSsl);

    

    @Override
    public void run() 

        String test = super.getTest();

        if(super.isWorkingProxy()) 

            System.out.println("-- Working proxy: " + super.getProxy() + " | Test: " +  test);

            this.updateDB(true, test);

         else 
            System.out.println("-- Not working: " + super.getProxy() + " | Test: " +  test);

            this.updateDB(false, test);
           


    

    private void updateDB(boolean success, String testingType) 
        switch(testingType) 
            case "SSL":
                DB.updateSsl(super.getProxyIp(), super.getProxyPort(), success);
                break;
            case "HTTP":
                DB.updateHttp(super.getProxyIp(), super.getProxyPort(), success);
                break;
            case "SOCKS":
                DB.updateSocks(super.getProxyIp(), super.getProxyPort(), success);
                break;
            default:
                break;
        
    

DB类:

//Locker for async 
private static Object locker = new Object();

private static void executeUpdateQuery(String query, String proxy, int port, boolean toSet) 
    synchronized(locker) 
        //Some prepared statements here.
    

【问题讨论】:

如果您的大多数线程都很快,但有些很慢,很可能他们试图从他们正在联系的服务器获得答案。当你做的工作最多而不是尾端时,你的 GC 暂停将是最糟糕的。 是的,但就像我提到的,我将超时设置为 5 秒。因此,即使他们试图得到答案,无论他们是否得到答案,他们都会在 5 秒后停止尝试。 你能分享一些代码吗? 绝对。刚刚发布了一些代码。 @ILoveKali 我发现当事情真的出错时,网络库在关闭连接方面不够积极。当连接正常时,超时往往效果最好。 YMMV 【参考方案1】:

感谢Peter Lawrey 指导我找到解决方案! :) 他的评论:

@ILoveKali 我发现网络库在 当事情真的出错时关闭连接。超时趋向 当连接正常时工作得最好。 YMMV

所以我做了一些研究,发现我也必须使用方法setReadTimeout(this.timeout);。之前我只用setConnectTimeout(this.timeout);

感谢这篇解释以下内容的帖子 (HttpURLConnection timeout defaults):

不幸的是,根据我的经验,使用这些默认值似乎可以 导致不稳定的状态,这取决于你的情况 连接到服务器。如果您使用 HttpURLConnection 并且不使用 显式设置(至少读取)超时,您的连接可以进入 一个永久的陈旧状态。默认情况下。所以总是将 setReadTimeout 设置为 “某事”,否则您可能会孤立连接(可能还有线程 取决于您的应用程序的运行方式)。

所以最终的答案是:GC 做得很好,它与滞后无关。由于我没有设置读取超时,线程只是永远停留在一个数字上,因此isWorkingProxy() 方法从未得到结果并继续读取。

【讨论】:

以上是关于我试图优化(内存)我的程序,但 GC 仍然让它滞后的主要内容,如果未能解决你的问题,请参考以下文章

QTableView Horizo​​ntalHeader 让它非常滞后?

GC_CONCURRENT 解决

NSTableView 在滚动时非常滞后

如何旋转图像视图而不滞后?

Java GC调优

C# SQL 查询滞后