【问题标题】:Running Apache DefaultHttpClient and redeploying web app in Tomcat means Permgen space在 Tomcat 中运行 Apache DefaultHttpClient 并重新部署 Web 应用程序意味着 Permgen 空间
【发布时间】:2013-06-19 15:45:10
【问题描述】:

几个月以来我一直在解决这个问题,现在我确信 HttpClient 是我永久 PermGen Space 错误的负责人。

我的网络应用程序使用 Servlet。 其中一项功能允许用户使用 REST 服务连接到远程设备并通过该设备的 HTTP 状态请求。

当我重新部署 Tomcat(在开发时,我不在生产中)并出现 PermGen 时出现问题。 由于跟踪,我知道问题与此 HttpClient 有关:

jun 19, 2013 4:51:34 PM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: El Servlet.service() para el servlet [dispatcher] en el contexto con ruta [] lanzó la excepción [Handler processing failed; nested exception is java.lang.OutOfMemoryError: PermGen space] con causa raíz
java.lang.OutOfMemoryError: PermGen space
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at org.apache.catalina.loader.WebappClassLoader.findClassInternal(WebappClassLoader.java:2904)
    at org.apache.catalina.loader.WebappClassLoader.findClass(WebappClassLoader.java:1173)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1681)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559)
    at org.apache.http.impl.cookie.RFC2109Spec.<init>(RFC2109Spec.java:83)
    at org.apache.http.impl.cookie.RFC2965Spec.<init>(RFC2965Spec.java:67)
    at org.apache.http.impl.cookie.BestMatchSpec.getStrict(BestMatchSpec.java:75)
    at org.apache.http.impl.cookie.BestMatchSpec.getVersion(BestMatchSpec.java:209)
    at org.apache.http.client.protocol.RequestAddCookies.process(RequestAddCookies.java:202)
    at org.apache.http.protocol.ImmutableHttpProcessor.process(ImmutableHttpProcessor.java:109)
    at org.apache.http.protocol.HttpRequestExecutor.preProcess(HttpRequestExecutor.java:176)
    at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:519)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
    at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784)
    at com.pe.f.web.PlantController.getStatus(PlantController.java:431)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:100)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:604)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:565)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:923)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:852)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:882)

在哪里

at com.pe.f.web.PlantController.getStatus(PlantController.java:431)

是启动 HTTP 请求的方法。

我有两个主要问题需要解决:

1- 这个问题是与HttpClient 相关还是我做错了什么?

这是我的代码:

for(Gdu g : p.getGduCollection()){

    try {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 30000);
        get = new HttpGet("http://" 
                        + p.getInstallationId().getIprouter() 
                        + ":" 
                        + g.getHttpPort().toString()
                        + "/FSP/status");
        HttpResponse response = httpClient.execute(get);

        if (response.getStatusLine().getStatusCode() != 200) {
            throw new RuntimeException("Failed : HTTP error code : "
                           + response.getStatusLine().getStatusCode());
        }
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader br = new BufferedReader(
                         new InputStreamReader((response.getEntity().getContent())));
        String line;

        while ((line = br.readLine()) != null) {
            if(!line.contains("<?xml")){
                inputStringBuilder.append(line);
            }                    
        }

        httpClient.getConnectionManager().shutdown();
        logger.debug(inputStringBuilder.toString());
        Document xml = loadXMLFromString(inputStringBuilder.toString());

        // sets the date
        status.setDate(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").parse(xml.getElementsByTagName("time").item(0).getTextContent()));

        // add power of each GDU
        Float f = Float.parseFloat(status.getPower().split(" ")[0]);
        f += Float.parseFloat(xml.getElementsByTagName("power").item(0).getTextContent().split(" ")[0]);
        status.setPower(f.toString() + " kW");

        // add the energy of each GDU
        Float f2 = Float.parseFloat(status.getEnergy().split(" ")[0].replace(",", "."));
        f2 += Float.parseFloat(xml.getElementsByTagName("energy").item(0).getTextContent().split(" ")[0]);
        status.setEnergy(String.format("%.3f", f2 - 0.005).replace(",", ".") + " MWh");

        NodeList nl = xml.getElementsByTagName("status");
        stat = 0;
        for(int j=0; j < nl.getLength(); j++){
            if(nl.item(j).getNodeType() == Node.ELEMENT_NODE){ // Node
                Element e = (Element) nl.item(j);
                stat += Integer.valueOf(e.getElementsByTagName("value").item(0).getTextContent());
            }
        }

        status.setPlantStatus(String.valueOf(stat + Integer.parseInt(status.getPlantStatus())));

        } catch (ConnectTimeoutException cte ) {
            status.setConnError("Unnable to communicate with plant. Reason: Connection Timeout.");
        } catch (SocketTimeoutException ste) {
            status.setConnError("Unnable to communicate with plant. Reason: Socket Timeout.");
        } catch (IOException ex) {
            status.setConnError("Unnable to communicate with plant. Reason: I/O Exception.");

        } finally {
            if(get != null){
                get.releaseConnection();
            }                
        }        
    }

我的代码打开一个 HTTP 连接,请求状态并以字符串形式接收 XML。 获得 XML 后,我会使用此信息创建一个 Status 对象。

2- 你推荐其他更精简的 HTTP 客户端吗? HttpURLConnection 可能吗?

我知道你们中的大多数人会建议增加永久内存堆。 我已经阅读了很多关于这个主题的内容。 我看不出增加堆大小的意义,因为最后,如果问题应该出现,它就会出现。

提前致谢

更新:SO post之后,我已经避免了HttpClient中的自动重试请求。 在接受任何答案之前,我将继续在开发服务器和生产服务器中进行测试。

参考文献:

【问题讨论】:

    标签: java tomcat httpclient permgen


    【解决方案1】:

    我怀疑问题出在 HttpClient 上。应用程序重新部署往往会占用 perm gen 空间,因为每次重新部署时都会重新加载类(并放置到 perm gen 空间中)。

    如果问题仅在开发过程中由于应用程序重新部署而出现,那么增加 perm gen 大小是可行的方法。您也可以尝试例如JRebel 以避免完全重新部署。时不时重启 Tomcat 也会有所帮助。

    【讨论】:

    • 这是否意味着如果我不在生产环境中重新部署应用程序就不会出现这个问题?
    • 可能。但是你需要测试它。最终,您还需要在生产环境中重新部署。就个人而言,我通过在 LB 后面运行多个 Tomcat 节点来处理生产重新部署(更新),这允许我重新启动 Tomcat(从而避免 perm gen 问题)。
    • 那么我应该在生产中测试它。您真的不认为这与 HttpClient 有关吗?那为什么要追踪呢?
    • 就像我说的,我对此表示怀疑。当抛出异常时,堆栈跟踪始终指向您正在运行的代码。您的代码恰好使用了 HttpClient,因此它在堆栈跟踪中可见。
    【解决方案2】:

    您可以使用此命令行参数允许您的开发 tomcat 卸载所有 perm gen 类:

     -XX:+CMSClassUnloadingEnabled
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-12
    • 2011-10-20
    • 2016-03-26
    • 1970-01-01
    • 2020-12-21
    • 1970-01-01
    • 1970-01-01
    • 2015-12-25
    相关资源
    最近更新 更多