我试图优化(记忆)我的程序,但GC仍然使它滞后

Rag*_*hav 5 java profiling garbage-collection

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

它从数据库中获取大约30,000个代理,然后尝试检查它们是否可以运行.从数据库收到的代理曾经作为a返回ArrayList<String>,但Deque<String>由于下述原因而被更改为.

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

ProxyRequest对象由RunnableProxyRequest调用super.isWorkingProxy()覆盖run()方法的对象包围.根据响应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线程结束?我知道有一个关于这个问题的Stackoverflow问题(Java线程在处理结束时会慢下来),但我已经尝试了答案中的所有内容并且它对我没用.

感谢您的时间.

代码片段:

循环添加线程到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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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.
    }
}
Run Code Online (Sandbox Code Playgroud)

Rag*_*hav 1

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

@ILoveKali 我发现网络库在出现问题时没有足够主动地关闭连接。当连接良好时,超时往往效果最好。青年MMV

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

感谢这篇文章(HttpURLConnection timeout defaults)解释了以下内容:

不幸的是,根据我的经验,使用这些默认值似乎会导致不稳定的状态,具体取决于您与服务器的连接发生的情况。如果您使用 HttpURLConnection 并且没有显式设置(至少读取)超时,您的连接可能会进入永久失效状态。默认情况下。因此,请始终将 setReadTimeout 设置为“某事”,否则您可能会孤立连接(可能还有线程,具体取决于您的应用程序的运行方式)。

所以最终的答案是:GC 做得很好,它不负责延迟。由于我没有设置读取超时,线程只是永远停留在一个数字上,因此该isWorkingProxy()方法永远不会得到结果并继续读取。