2016-07-11 40 views
2

在我的一個客戶端日誌中,我看到了Sun類的一個異常。客戶端使用OpenJDK 1.8.0_91。在OpenJDK代碼(內部Sun類)中調試NullPointerException

我試圖重現它沒有任何運氣。

從我們的日誌看來,我似乎在關閉JVM期間(在ShutdownHook中)發生異常。

問題是,這段代碼在程序的生命週期內工作,並按預期發送所有數據,但是在關閉期間,我們不時得到這個NPE。

有關如何解決的任何想法?我試着看看源代碼,但由於某種原因,我無法找到它。

這裏的堆棧跟蹤:

2016-07-08 11:07:58,426 ERROR [Thread-0] [HttpClient] Failed to send 'POST' request to 'https://prod-x-gw.mycompany.co/api/v2/testDoMagic/'. Error: java.lang.NullPointerException 
java.lang.NullPointerException: null 
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1158) ~[na:1.8.0_91] 
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:999) ~[na:1.8.0_91] 
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:177) ~[na:1.8.0_91] 
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1283) ~[na:1.8.0_91] 
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1258) ~[na:1.8.0_91] 
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250) ~[na:1.8.0_91] 
    at com.my.company.HttpClient.writeRequestBodyToOutputStream(HttpClient.java:152) ~[na:na] 
    at com.my.company.HttpClient.sendRequest(HttpClient.java:52) ~[na:na] 
    at com.my.company.JsonClient.sendHttpRequest(JsonClient.java:187) [na:na] 
    at com.my.company.JsonClient.postRequest(JsonClient.java:92) [na:na] 
    at com.my.company.JsonClient.postRequest(JsonClient.java:86) [na:na] 
    at com.my.company.DoMagicServiceProxy.sendRequest(DoMagicServiceProxy.java:59) [na:na] 
    at com.my.company.DoMagicServiceProxy.submitDoMagic(DoMagicServiceProxy.java:48) [na:na] 
    at com.my.company.DoMagicQueueSender$2.process(DoMagicQueueSender.java:108) [na:na] 
    at com.mycompany..commons.ChunksProcessor.processAsChunks(ChunksProcessor.java:35) [na:na] 
    at com.my.company.DoMagicQueueSender$1.execute(DoMagicQueueSender.java:89) [na:na] 
    at com.my.company.DoMagicQueueSender.shutdown(DoMagicQueueSender.java:46) [na:na] 
    at com.mycompany.DoMagicManager.shutDown(DoMagicManager.java:77) [na:na] 
    at com.mycompany.AM.shutdown(AM.java:145) [na:na] 
    at com.mycompany.AM.access$000(AM.java:17) [na:na] 
    at com.mycompany.AM$1.run(AM.java:157) [na:na] 
2016-07-08 11:07:58,437 ERROR [Thread-0] [DoMagicServiceProxy] Failed while trying to submit DoMagic. Error: 
java.lang.RuntimeException: Failed to send 'POST' request to 'https://prod-x-gw.mycompany.co/api/v2/testDoMagic/'. Error: java.lang.NullPointerException 
    at com.my.company.HttpClient.sendRequest(HttpClient.java:70) ~[na:na] 
    at com.my.company.JsonClient.sendHttpRequest(JsonClient.java:187) ~[na:na] 
    at com.my.company.JsonClient.postRequest(JsonClient.java:92) ~[na:na] 
    at com.my.company.JsonClient.postRequest(JsonClient.java:86) ~[na:na] 
    at com.my.company.DoMagicServiceProxy.sendRequest(DoMagicServiceProxy.java:59) ~[na:na] 
    at com.my.company.DoMagicServiceProxy.submitDoMagic(DoMagicServiceProxy.java:48) ~[na:na] 
    at com.my.company.DoMagicQueueSender$2.process(DoMagicQueueSender.java:108) [na:na] 
    at com.mycompany.commons.ChunksProcessor.processAsChunks(ChunksProcessor.java:35) [na:na] 
    at com.my.company.DoMagicQueueSender$1.execute(DoMagicQueueSender.java:89) [na:na] 
    at com.my.company.DoMagicQueueSender.shutdown(DoMagicQueueSender.java:46) [na:na] 
    at com.mycompany.DoMagicManager.shutDown(DoMagicManager.java:77) [na:na] 
    at com.mycompany.AM.shutdown(AM.java:145) [na:na] 
    at com.mycompany.AM.access$000(AM.java:17) [na:na] 
    at com.mycompany.AM$1.run(AM.java:157) [na:na] 
Caused by: java.lang.NullPointerException: null 
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1158) ~[na:1.8.0_91] 
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:999) ~[na:1.8.0_91] 
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:177) ~[na:1.8.0_91] 
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream0(HttpURLConnection.java:1283) ~[na:1.8.0_91] 
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1258) ~[na:1.8.0_91] 
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:250) ~[na:1.8.0_91] 
    at com.my.company.HttpClient.writeRequestBodyToOutputStream(HttpClient.java:152) ~[na:na] 
    at com.my.company.HttpClient.sendRequest(HttpClient.java:52) ~[na:na] 
    ... 13 common frames omitted 

如果是這樣的幫助,我是我的HttpClient實現:

public class HttpClient { 
private int readTimeoutInMS; 
private int connectTimeoutInMS; 
private String charset; 
private final int SECOND = 1000; 
private ILogFactory logFactory; 
private ILogger log; 

public HttpClient(){ 
    //By default, max timeout will be 130 seconds - 10 seconds to connect, and 120 seconds to read the final byte. 
    connectTimeoutInMS = 10 * SECOND; 
    readTimeoutInMS = 120 * SECOND; 
    charset = "utf-8"; 
} 

public void init() { 
    if (log == null) { 
     log = getLogger(); 
    } 
} 

public HttpResponse sendRequest(HttpRequest request) 
{ 
    log.info("sendRequest was called. Remote url:'" + request.getUrl() + "'."); 
    HttpRequestMethods requestMethod = request.getRequestMethod(); 
    HttpURLConnection connection = null; 
    boolean originalFollowRedirects = HttpURLConnection.getFollowRedirects(); 
    try{ 

     URL targetUrl = new URL(request.getUrl()); 
     connection = (HttpURLConnection) targetUrl.openConnection(); 

     setupConnectionObject(request, requestMethod, connection); 

     if (requestMethod == HttpRequestMethods.POST){ 
      writeRequestBodyToOutputStream(request, connection); 
     } 

     HttpResponse response = new HttpResponse(); 
     response.setStatusCode(connection.getResponseCode()); 
     if (response.getStatusCode() >= 400) { 
      response.setResponseStream(connection.getErrorStream()); 
     } 
     else { 
      response.setResponseStream(connection.getInputStream()); 
     } 

     return response; 

    }catch (Exception e) 
    { 
     String msg = String.format("Failed to send '%s' request to '%s'. Error: %s", requestMethod, request.getUrl(), e); 
     log.error(msg,e); 
     throw new RuntimeException(msg, e); 
    } 
    finally { 

     HttpURLConnection.setFollowRedirects(originalFollowRedirects); 
    } 
} 


public int getReadTimeoutInMS() { 
    return readTimeoutInMS; 
} 

public void setReadTimeoutInMS(int readTimeoutInMS) { 
    this.readTimeoutInMS = readTimeoutInMS; 
} 

public int getConnectTimeoutInMS() { 
    return connectTimeoutInMS; 
} 

public void setConnectTimeoutInMS(int connectTimeoutInMS) { 
    this.connectTimeoutInMS = connectTimeoutInMS; 
} 


public String getCharset() { 
    return charset; 
} 

public void setCharset(String charset) { 
    this.charset = charset; 
} 

public ILogFactory getLogFactory() { 
    return logFactory; 
} 

public void setLogFactory(ILogFactory logFactory) { 
    this.logFactory = logFactory; 
} 

private void setupConnectionObject(HttpRequest request, HttpRequestMethods requestMethod, HttpURLConnection connection)throws ProtocolException { 
    connection.setRequestMethod(requestMethod.toString()); 

    if (requestMethod == HttpRequestMethods.POST){ 
     //Mark the HttpMethod as post. 
     connection.setDoOutput(true); // Allows to send message body on the output stream. 
    } else if (requestMethod == HttpRequestMethods.HEAD) 
    { 
     HttpURLConnection.setFollowRedirects(false); 
     connection.setRequestMethod("HEAD"); 
    } 

    int connectTimeout = request.getConnectTimeout() != null? request.getConnectTimeout() : this.connectTimeoutInMS; 
    if (connectTimeout >= 0) 
    { 
     connection.setConnectTimeout(connectTimeout); 
    } 

    int readTimeout = request.getReadTimeout() != null? request.getReadTimeout() : this.readTimeoutInMS; 
    if (connectTimeout >= 0) 
    { 
     connection.setReadTimeout(readTimeout); 
    } 

    //Add HttpHeader 
    if (request.getHttpHeaders() != null) 
    { 
     for (Entry<String, String> httpHeader : request.getHttpHeaders().entrySet()) { 
      connection.setRequestProperty(httpHeader.getKey(), httpHeader.getValue()); 
     } 
    } 
    if (request.getCompressRequestBody() && connection.getDoOutput()){ //only turn on for request with output 
     connection.setRequestProperty("Content-Encoding","gzip"); 
    } 
} 
private void writeRequestBodyToOutputStream(HttpRequest request, 
     HttpURLConnection connection) throws IOException, 
     UnsupportedEncodingException { 
    DataOutputStream dataOutputSteam = null; 
    try{ 
     dataOutputSteam = new DataOutputStream(connection.getOutputStream()); 
     String requestBody = request.getRequestBody() != null ? request.getRequestBody() : ""; 
     String bodyCharset = request.getCharset() != null ? request.getCharset() : this.charset; 
     byte[] requestBodyAsBytes = requestBody.getBytes(bodyCharset); 

     if (request.getCompressRequestBody()) { 
      //http://stackoverflow.com/questions/7153484/gzip-post-request-with-httpclient-in-java 
      ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
      try (GZIPOutputStream gzos = new GZIPOutputStream(baos)) { 
       gzos.write(requestBodyAsBytes); 
      } 

      byte[] gzippedBytes = baos.toByteArray(); 
      requestBodyAsBytes = gzippedBytes; 
     } 
     dataOutputSteam.write(requestBodyAsBytes); 
    } 
    finally{ 
     if (dataOutputSteam != null) 
     { 
      dataOutputSteam.close(); 
     } 
    } 
} 

private ILogger getLogger() { 
    ILogger logger; 
    if (logFactory != null) 
    { 
     logger = logFactory.getLogger("HttpClient"); 
    } 
    else 
    { 
     logger = NullLogger.INSTANCE; 
    } 
    return logger; 
} 

}

回答

2

我的猜測是,你從這個URL斷開時連接在退出之前的最終清理過程中實際上仍在使用中(也許這是一些終結器執行其工作的結果),事實上它只是偶爾發生意味着t您正在同時使用該HttpURLConnection而沒有正確同步。

查看源碼here,該版本的HttpURLConnection.plainConnect0()該行應該是1156而不是1158

嘗試在HttpURLConnection.disconnect()上放置斷點(僅限於該URL?)。

+0

感謝您的來源和提示。我已附加了我的HttpClient。你看到關於同步問題的事情嗎?對我來說那看起來很安全。 – nadavy

+0

writeRequestBodyToOutputStream中的'dataOutputStream'應該連接到它的'HttpURLConnection',關閉一個應該關閉另一個。我不知道您的DoMagic *類和JSONClient中發生了什麼,它似乎是一系列事件(以非確定性順序來共享連接對象的非同步使用),有時會導致這種情況。 –

+0

基本上,DoMagic類中的流只有一個線程,它只將數據推送到數組(DoMagic),JsonClient使用相關的主體和請求類型序列化並創建新的HttpRequest。 – nadavy