RealmError: Realm Out of memory size
Asked Answered
D

1

5

I am using Realm 3.0.0 as the DB of my Android app. It's like a questionnaire application, in which the user navigates inside the app a lot. When I use the app (go back and forth) continuously, I get the following error:

Fatal Exception: io.realm.exceptions.RealmError: Unrecoverable error. mmap() failed: Out of memory size: 1073741824 offset: 0 in /Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_SharedRealm.cpp line 109
       at io.realm.internal.SharedRealm.nativeGetSharedRealm(SharedRealm.java)
       at io.realm.internal.SharedRealm.(SharedRealm.java:187)
       at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:229)
       at io.realm.internal.SharedRealm.getInstance(SharedRealm.java:204)
       at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:124)
       at io.realm.Realm.getDefaultInstance(Realm.java:210)

Now I know the main cause of this is not closing Realm instances. But I've already checked for that multiple times. And I am positive that I close every instance I open.

The app has many activities and fragments that all get a Realm instance on their onCreate and close it on their onDestroy. There are also other background network jobs that run to upload data that get Realm instances. These jobs close their Realm instances when they've finished running or when they cancel.

All of the above get their Realm instance thru injection via Dagger 2:

  @Provides
  @Nullable
  static Realm realm(@Nullable RealmConfiguration configuration) {
    if (configuration != null) {
      Realm.setDefaultConfiguration(configuration);
      return Realm.getDefaultInstance();
    }
    return null;
  }

Configuration is also provided in the same Dagger Module.

To be more specific, a Questionnaire consists of many Question Fragments displayed in a ViewPager. Each Fragment gets injected with a realm. Many interactions in a given Question Fragment write data to the DB (some async, some blocking). These Fragments also query the database on onResume to get their updated Data. Some of this data is also copied out of Realm via realm.copyFromRealm(). Now at any given time of these happening, an upload job is most likely running and reading data from the DB and uploading it to a server. When an upload job finishes, it then writes to the DB.

I think I can have up to 7-12 fragment/activities holding a realm reference on the UI thread at a given moment. And 0-6 other references on 0-3 other threads (Background Jobs).

Moreover, I compact my realm DB via Realm.compactRealm(realmConfiguration) on every app launch (perhaps as a separate problem, this doesn't seem to do it's job consistently).

Above I've tried to describe my Realm usage descriptively without going into details. Now my problem is, when a user excessively uses the app (going back and forth between activities/fragments (realm injection + DB read query), uploading data (realm injection + DB read&write query)), I get the above posted Out of Memory Error.

I am also using Leak Canary, and it hasn't detected any leaks. (Not sure if it can anyway)

Am I using Realm in a way it's not supposed to be used? Should I close Realm instances onPause instead of onDestroy? Should I have only one realm instance in an activity and have all it's fragmetns (up to 5 in my case) use this instance? What kind of changes can I make in my app, and perhaps my app architecture to solve this problem?

I appreciate any help in trying to solve this problem.

EDIT: I'm sharing the realm open-close logic in my background threads.

All my jobs share the same realm usage, which is the following: Realm is injected lazily via:

@Inject protected transient Lazy<Realm> lazyRealm;

The realm object reference is held at the private transient Realm realm; field. I am using Android Priority Job Queue. When the job is added:

@Override
public void onAdded() {
    realm = lazyRealm.get();
    realm.executeTransaction(realm1 -> {
        //write some stuff into realm
    });
    realm.close();
}

And when the job is run realm is retreived once, and every possible ending of this method has a call to realm.close()

@Override public void onRun() throws Throwable {
    synchronized (syncAdapterLock) {
      realm = lazyRealm.get();
      Answer answer = realm.where(Answer.class).equalTo(AnswerQuery.ID, answerId).findFirst();
      if (answer == null) {
        realm.close();
        throw new RealmException("File not found");
      }
        final File photoFile = new File(answer.getFilePath());
        final Response response = answerService.uploadPhotoAnswer(answerId, RequestBody.create(MediaType.parse("multipart/form-data"), photoFile)).execute();
        if (!response.isSuccessful()) {
          realm.close();
          throw new HttpError(statusCode);
        }
        realm.executeTransaction(realm1 -> {
          answer.setSyncStatus(SyncStatus.SYNCED.getCode());
        });
      }
      realm.close();
    }
  }

As you can see, these background threads do close their realm instances properly as far as I'm concerned.

Diwan answered 31/3, 2017 at 15:7 Comment(9)
And 0-6 other references on 0-3 other threads (Background Jobs). I'm pretty sure that the "background jobs" don't close their Realms.Polycotyledon
@Polycotyledon They do close their realms. I've edited my question and added the realm open/close logic of my background jobs.Diwan
@Polycotyledon I'd like to think that im not that stupid. I've had this issue for over 2 weeks, and have been trying to solve it on and off. Making sure all my realm instances get closed is the first thing I did. I must be doing something wrong in a bigger scale.Diwan
You can check with Realm.getGlobalInstanceCount() if there are any open Realms, you should print it when you finish activities and see if there are any open.Polycotyledon
The other possibility is inserting many elements in many transactions instead of one transaction.Polycotyledon
@Polycotyledon I'll look into the two things you've mentioned. I've read your article on How to use Realm for Android like a champ. Could "Opening a Realm instance on a background thread, obtaining the RealmResults, THEN opening the transaction, and then manipulating the results on the outside of the transaction" be the cause of the problem? Or does that only make me not have up-to-date objects? Because I do have code (including the above mentioned background jobs) that get a realm object, then change it in a transaction without a get inside the transactionDiwan
Let us continue this discussion in chat.Diwan
How big is your Realm file?Allergic
Thank you both for your swift helping hands, but especially @EpicPandaForce! I've solved my problem and written how I did as an answer. I truly appreciate the help, great articles man!Diwan
D
8

While it was true that all my background tasks did call realm.close(), one of them called it too late in it's lifecycle. That was my GPSService, which is a background service. The problem was that GPS service is initialized at the launch of the App as an Android Service, which is rarely destroyed. I was injecting a realm instance onCreate and closing it onDestroy. After the comments of @EpicPandaForce and reading his articles about using realm properly. I realized that this was the cause of the leak. A non-looper thread was keeping an open realm reference for an extremely long time, thus, the mmap was bloating every time a write transaction occures. Now that I moved the realm get/close to happen every time the service runs, my problem is fixed.

My take away is that one needs to treat background thread realm access very delicately. Thank you both for your quick responses and help!

Diwan answered 10/4, 2017 at 13:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.