【问题标题】:java.lang.OutOfMemoryError: Could not allocate JNI Envjava.lang.OutOfMemoryError:无法分配 JNI 环境
【发布时间】:2017-12-09 22:42:36
【问题描述】:

当我第一次运行异步任务时,它工作正常。实际上,这个错误来得不可预测。我在这个问题上搜索了很多解决方案,但没有什么对我有用。 我得到的常见解决方案是,我们需要关闭 InputStream/ByteArrayInputStream 并且我关闭了所有但仍然应用程序崩溃。

堆栈跟踪:

java.lang.OutOfMemoryError: 无法在 java.lang.Thread.nativeCreate(Native Method) 在 java.lang.Thread.start(Thread.java:730) 在 java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:941) 在 java.util.concurrent.ThreadPoolExecutor.processWorkerExit(ThreadPoolExecutor.java:1009) 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1151) 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 在 java.lang.Thread.run(Thread.java:761)

下面是AsyncTask;

public class AsyncHttpRequest extends AsyncTask<Void, Void, String> {

    private String UrlString = "";
    private boolean _showProgressDialog = false;
    private CustomCircularLoadingDialog Dialog;
    private Context mContext;
    AppCompatActivity mActivity;
    private IHttpRequestCompletedListener listener;
    private boolean isActivity = true;
    private String _messageText = "Please wait..."; 
    private String type = "get";

    private HttpUtility utility;


    public AsyncHttpRequest(String urlString, Context context, IHttpRequestCompletedListener listener, boolean _showProgressDialog) {
        UrlString = urlString;
        this._showProgressDialog = _showProgressDialog;
        this.mContext = context;
        this.listener = listener;
        Dialog = new CustomCircularLoadingDialog(this.mContext);
        this.utility = new HttpUtility(this.UrlString, mContext);
        Utilities.setCurrentHitURL(mContext, UrlString);
    }

    public void setOnCompletedListener(IHttpRequestCompletedListener listener) {
        this.listener = listener;
    }

    public void setWaitMessage(String msgText) {
        if (!msgText.equals(""))
            msgText = "\n" + msgText;
        this._messageText = _messageText + msgText;
    }

    public void addPostItem(NameValuePair nameValuePair) {
        this.utility.addNameValuePairs(nameValuePair);
    }

    public void addGetHeader(String headerData) {
        this.utility.addGetHeader(headerData);
    }

    public void addPostHeader(String headerData) {
        this.utility.addPostHeader(headerData);
    }

    public void setTypePost() {
        this.type = "post";
    }

    @Override
    protected void onPreExecute() {
        if (_showProgressDialog) {
            Dialog.setMessage(this._messageText);
            Dialog.setCancelable(false);
            Dialog.setCanceledOnTouchOutside(false);
            this.Dialog.getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
            Dialog.show();
        }
    }

    @Override
    protected String doInBackground(Void... params) {

        if (!Utilities.isNetworkAvailable(mContext))
            return "No network available";
        try {
            if (this.type.equals("get"))
                return utility.doGetRequest();
            else
                return utility.doPostRequest();
        } catch (MediCorporateException tex) {

            if (listener != null) {
                if (isActivity) {
                    ((Activity) mContext).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            listener.OnHttpRequestError();
                        }
                    });
                } else {
                    ((GcmIntentService) mContext).runOnUIThread(new Runnable() {
                        @Override
                        public void run() {
                            listener.OnHttpRequestError();
                        }
                    });
                }
            }
            Utilities.callCrashReport(mContext, tex);
        }
        Log.i("Exit ", "doInBackground");

        return "";
    }

    @Override
    protected void onPostExecute(String Result) {
        if (_showProgressDialog)
            this.Dialog.dismiss();
        Log.i("Came in", "onPostExecute");
        if (this.listener != null) {
            if (!Utilities.isNullOrEmpty(Result))
                listener.OnHttpRequestCompleted(Result);
            else {
                logoutUser();
                listener.OnHttpRequestError();
            }
        }
        Log.i("Exit ", "onPostExecute");
    }

}

以下是 HttpUtility 类中处理请求和响应的函数;

public String doGetRequest() throws MediCorporateException {

        String resp = "";
        int responseCode = 0;
        try {
            if (header != null) {
                if (header.length() > 0) {
                    httpURLConnection.setRequestMethod("GET");
                    httpURLConnection.setRequestProperty("Authorization", header);
                }
            }
            responseCode = httpURLConnection.getResponseCode();
            InputStream inputStream = new BufferedInputStream(this.httpURLConnection.getInputStream());
            resp = readResponse(inputStream);
            Log.v("Resp", "" + responseCode + " --- " + resp);
            inputStream.close();
        } catch (IOException ioExc) {
            FileLog.e(getClass().getName(), ioExc);
            resp = ioExc.getMessage();
            throw new MediCorporateException("Http IO Exception...");
        } catch (Exception ex) {
            FileLog.e(getClass().getName(), ex);
            throw new MediCorporateException("Http Error...");
        } finally {

            this.httpURLConnection.disconnect();
            if (responseCode == 401)
                return "" + responseCode;
            if (responseCode != 200)
                return null;
        }

        return resp;
    }

以下是 DoPostRequest() :

public String doGetRequest() throws MediCorporateException {

        String resp = "";
        int responseCode = 0;
        try {
            if (header != null) {
                if (header.length() > 0) {
                    httpURLConnection.setRequestMethod("GET");
                    httpURLConnection.setRequestProperty("Authorization", header);
                }
            }
            responseCode = httpURLConnection.getResponseCode();
            InputStream inputStream = new BufferedInputStream(this.httpURLConnection.getInputStream());
            resp = readResponse(inputStream);
            Log.v("Resp", "" + responseCode + " --- " + resp);
            inputStream.close();
        } catch (IOException ioExc) {
            FileLog.e(getClass().getName(), ioExc);
            resp = ioExc.getMessage();
            throw new MediCorporateException("Http IO Exception...");
        } catch (Exception ex) {
            FileLog.e(getClass().getName(), ex);
            throw new MediCorporateException("Http Error...");
        } finally {

            this.httpURLConnection.disconnect();
            if (responseCode == 401)
                return "" + responseCode;
            if (responseCode != 200)
                return null;
        }

        return resp;
    }

以下是读写响应函数;

private void writePostMethod(OutputStream outputStream) throws Exception {
        if (this.nameValuePairs.size() <= 0)
            throw new Exception("Cannot use post method with no values to post");
        String postStr = "";
        for (NameValuePair item : this.nameValuePairs)
            postStr += URLEncoder.encode(item.getName(), "UTF-8") + "=" + URLEncoder.encode(item.getValue(), "UTF-8") + "&";
        postStr = postStr.substring(0, postStr.length() - 1);
        Log.v("Post Values", postStr);
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8"));
        writer.write(postStr);
        writer.flush();
        writer.close();
    }

    private String readResponse(InputStream inputStream) throws IOException {
        int i;
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        while ((i = inputStream.read()) != -1)
            outputStream.write(i);

        byte[] responseBytes = outputStream.toByteArray();
        ByteArrayInputStream bais = new ByteArrayInputStream(responseBytes);
        InputStreamReader reader;
        if (!this._urlString.contains("token")) {
            GZIPInputStream gzis = new GZIPInputStream(bais);
            reader = new InputStreamReader(gzis);
            gzis.close();
        } else
            reader = new InputStreamReader(bais);

        BufferedReader in = new BufferedReader(reader);
        StringBuilder total = new StringBuilder();
        String readed;
        while ((readed = in.readLine()) != null) {
            total.append(readed);
            bais.close();
        }
        in.close();
        reader.close();
        inputStream.close();
        outputStream.close();
        return total.toString();
    }

【问题讨论】:

  • 堆栈跟踪在哪里?
  • 我已经编辑了我的问题。
  • 每个线程在内存消耗方面的成本很高。 OOM 发生在本机方法中,但是当您的应用程序耗尽其堆时,OOM 的实际意义无关紧要。因此我删除了 jni 标签。

标签: android android-asynctask out-of-memory httpurlconnection


【解决方案1】:

每个线程在内存消耗方面的成本很高。

AsyncTask 确实管理它的线程池,但它没有针对网络活动进行优化。实际上,如果您对同一台服务器有许多 HTTP 请求,那么无论从内存消耗还是整体性能方面来看,最好将它们保持在同一个线程上,并尽可能重用持久连接。 AsyncTask 不考虑此类问题。

有很多可靠的 HTTP 客户端提供异步请求,例如OkHttpvolley,或 J.Smith 的 Android Asynchronous Http Client,或 GitHub 上的许多其他项目。

发明自己的 HTTP 客户端是可以的,但至少研究一下其他人在这个领域做了什么以及他们以前犯了什么错误是明智的。

【讨论】:

  • “研究其他人在这个领域做了什么以及他们以前犯了什么错误是明智的。” - 像专业人士一样说话
【解决方案2】:

似乎您在从流中读取数据时没有正确关闭输入/输出流或缓冲区。请尝试以下 sn-p 以读取 InputStream 的响应。

使用 org.apache.commons.io 的 IOUtils。你可以download jar

private String readResponse(InputStream inputStream) throws IOException {
    if (!this._urlString.contains("token")) {
        GZIPInputStream gzipIn = new GZIPInputStream(inputStream);
        return IOUtils.toString(gzipIn);    
    }else {
        if (inputStream != null) {
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();

            String line = null;
            try {
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return sb.toString();
        }
    }
    return null;
}

虽然大部分 OOM 是由于分配给应用程序的堆大小造成的。我们可以通过在清单文件中添加标签来增加堆大小。

 <application
    android:name="ApplicationLoader"
    android:largeHeap="true">

查看您的存储库后,我发现对于每个异常,您都会向 FireBase 发送日志和崩溃报告。这会导致线程池内存不足,从而导致 OOM。请确保您分批发送崩溃和事件。

【讨论】:

  • @user1269737 链接无效。这是网络存档link
  • 警告!永远不要仅仅因为内存用完而需要快速修复就请求大堆 - 您应该使用 Profiler 来查找阻止内存对象被垃圾收集的原因
  • 更大的堆使得垃圾收集的工作更长的时间来处理 => 当 GC 发生在 UI 线程上时,更长的进程 => UI LAGS
猜你喜欢
  • 1970-01-01
  • 2017-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多