-4

I am parsing a JSON String fetched from the internet (Around 40,000 records in a single request). I am call AsyncTask recursively until I have fetched all the records from server (Total records 200k).

In the first two calls app the responds smooth with around 80k records fetched from server, but in third call an OutOfMemoryException occurs. I searched the web and got to know that this error occurs when the heap size overflows.

String response = "";
            try {
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpEntity httpEntity = null;
                HttpResponse httpResponse = null;

                HttpPost httpPost = new HttpPost("http://URL");                                

                httpPost.setEntity(new StringEntity(strings[0]));

                httpPost.setHeader("Content-Type", "application/json");
                httpPost.setHeader("Accept", "application/json");


                httpResponse = httpClient.execute(httpPost);
                httpEntity = httpResponse.getEntity();
                response = EntityUtils.toString(httpEntity);

The error occurs on the below line.

response = EntityUtils.toString(httpEntity);

My code works fine for first 2 calls, so why does it throw OutOfMemoryExcption in the next call? Why is the first two calls working with the same size of data?

I know there are many questions regarding OutOfMemoryException, but I want to know that how Android allocates the size of the heap? Does it depends on the free memory of device? Does the size of heap reduce when the size of app data increase or does it remains same?

My Code:

public void startBackgrounSync(){

            final JSONObject jsonObject = new JSONObject();
    try{
        jsonObject.put("token","dssdf");
        jsonObject.put("api_key","fdsf");
        jsonObject.put("org_id","101");
        jsonObject.put("sync_type","background_sync");
        jsonObject.put("start",Integer.toString(start));
        jsonObject.put("total",Integer.toString(limit));
    }catch (JSONException e){
        e.printStackTrace();
    }

    AsyncTask<String, Integer, String> backGroundSync = new AsyncTask<String, Integer, String>() {

        @Override
        protected void onPreExecute() {
            super.onPreExecute();

        }

        @Override
        protected String doInBackground(String... strings) {
            System.out.println("BackGround Sync Process Start: ");

            String response = "";
            try {
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpEntity httpEntity = null;
                HttpResponse httpResponse = null;
                HttpPost httpPost = new HttpPost("http://URL");

                httpPost.setEntity(new StringEntity(strings[0]));

                httpPost.setHeader("Content-Type", "application/json");
                httpPost.setHeader("Accept", "application/json");


                httpResponse = httpClient.execute(httpPost);


                httpEntity = httpResponse.getEntity();
                response = EntityUtils.toString(httpEntity);


            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            Log.d("Response: ", "> " + response);

            return response;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);

                try{
                    JSONObject jsonObject1 = new JSONObject(s);
                    JSONObject data = jsonObject1.getJSONObject("data");

                    if(data != null){

                        if (data.getBoolean("is_success")){
                            JSONObject alumni_data = data.getJSONObject("alumni_data");

                                JSONArray alumni_profile_data = alumni_data
                                        .getJSONArray("alumni_profile_data");

    ContentValues[] values = new ContentValues[alumni_profile_data.length()];

                            if (alumni_profile_data == null){
                                Toast.makeText(MainActivity.this,"Sync complete.....",Toast.LENGTH_LONG).show();

                            }else{
                                JSONObject alumini_object;
                                for (int i = 0; i < alumni_profile_data.length(); i++) {
                                    alumini_object = alumni_profile_data.getJSONObject(i);

                                    String id = alumini_object.getString("id");
                                    String org_id = alumini_object.optString("org_id");
                                                                      ContentValues  contentValues = new ContentValues();

                                    contentValues.put("id", id);
                                    contentValues.put("org_id", org_id);
                                    contentValues.put("profile_photo", profile_photo);


                                    values[i] = contentValues;

                                }
                            }

                            String uri = "content://com.poras.provider.alumni/data";

                            Uri uri1 = Uri.parse(uri);

                            int length = getApplicationContext().getContentResolver().bulkInsert(uri1,values);
                            System.out.println("Length of rows...... " +length);


                            start = start+50000;
                            values = new ContentValues[0];

                            startBackgrounSync();// Calling Method Recursively

                        }
                    }

                }catch (JSONException e){
                    e.printStackTrace();
                }catch (OutOfMemoryError error){
                    System.out.println("Out of memory exception...... "+ error);
                }

            }

        }
    };
    backGroundSync.execute(jsonObject.toString());

}
James Z
  • 12,209
  • 10
  • 24
  • 44
Poras Bhardwaj
  • 1,073
  • 1
  • 15
  • 33
  • 5
    Doing these types of calls _recursively_ is almost certainly your issue. Can you show the code where you call these `AsyncTask` - Also I would suggest **not** adding `android:largeHeap="true"` to your manifest as it only masks the real issue with your app and doesn't really ensure a fix across all devices – Ed Holloway-George Jan 07 '16 at 10:09
  • OutofMemory exception occurs when your device runs out of memory. – Sharp Edge Jan 07 '16 at 10:12
  • Please post the code in which you call the Async – Ed Holloway-George Jan 07 '16 at 10:23
  • Ed - The code is very lengthy. – Poras Bhardwaj Jan 07 '16 at 10:30
  • You only need to show the key parts, i.e the recursion and how the Async is called – Ed Holloway-George Jan 07 '16 at 10:34
  • The main problem with this code is that you are getting full reponse as string... then you parse it as whole ... using some kind of [streaming json api](http://developer.android.com/intl/es/reference/android/util/JsonReader.html) would help (personaly, i would go for [jackson](http://wiki.fasterxml.com/JacksonStreamingApi)) ... also it would be better if parsing/inserting code would be inside background task ... – Selvin Jan 07 '16 at 10:53
  • @Selvin This should be acceptable – IntelliJ Amiya Jan 07 '16 at 11:08

3 Answers3

3

Your question states you are calling these methods recursively - and it is highly likely that this is causing the OutOfMemoryException.

When you call a method, Java stores the objects for that method in the heap space and those values are only reclaimed by the garbage collector once the method returns. Calling a method recursively means the method will execute multiple times, all the time allocating to the heap, and cause a long thread stack.

The simple solution is to not call the Async recursively, but start the next call within the onPostExecute method (assuming you're using AsyncTask)

I personally would totally avoid the use of android:largeHeap="true" as the docs wisely state:

Never request a large heap simply because you've run out of memory and you need a quick fix—you should use it only when you know exactly where all your memory is being allocated and why it must be retained.

In terms of how Android allocates the heap space for an application, the docs state

The exact heap size limit varies between devices based on how much RAM the device has available overall. If your app has reached the heap capacity and tries to allocate more memory, it will receive an OutOfMemoryError

From an Activity you can query how much heap space the device currently has allocated for the app using getMemoryClass()

Return the approximate per-application memory class of the current device.

Ed Holloway-George
  • 5,092
  • 2
  • 37
  • 66
0

OutOfMemory Exception

Thrown when a request for memory is made that can not be satisfied using the available platform resources. Such a request may be made by both the running application or by an internal function of the VM.

You can set android:largeHeap="true" in your manifest . [Not Permanent Solutions]

Whether your application's processes should be created with a large Dalvik heap. This applies to all processes created for the application. It only applies to the first application loaded into a process; if you're using a shared user ID to allow multiple applications to use a process, they all must use this option consistently or they will have unpredictable results.

So,To maintain a functional multi-tasking environment, Android sets a hard limit on the heap size for each app. The exact heap size limit varies between devices based on how much RAM the device has available overall. If your app has reached the heap capacity and tries to allocate more memory, it will receive an OutOfMemoryError.

You can call getMemoryClass() to get an estimate of your app's available heap in megabytes. If your app tries to allocate more memory than is available here, it will receive an OutOfMemoryError.

Read Check how much memory you should use . Hope this helps .

IntelliJ Amiya
  • 74,896
  • 15
  • 165
  • 198
0

As said here :

memory usage on modern operating systems like Linux is an extremely complicated and difficult to understand area

ActivityManager.getMemoryClass() gives you the estimate memory available per application.

I you get OutOfMemoryException you are probably doing something wrong, you should profile the memory usage, like explained in Investigating Your RAM Usage, or more specifically with Android Studio's Memory and CPU monitor.

In your case I think that processing a lot of data recursively is a very bad idea, for example, it makes easy to maintain references to large objects at each step in the call stack, prohibiting their garbage collection.

Community
  • 1
  • 1
bwt
  • 17,292
  • 1
  • 42
  • 60