realm-java: Out of memory on commitTransaction
Here’s the stack trace I’m getting:
Fatal Exception: io.realm.exceptions.RealmError: Unrecoverable error. mmap() failed: Out of memory size: 402653184 in io_realm_internal_SharedGroup.cpp line 188 at io.realm.internal.SharedGroup.nativeCommitAndContinueAsRead(SharedGroup.java) at io.realm.internal.SharedGroup.commitAndContinueAsRead(SharedGroup.java:95) at io.realm.internal.ImplicitTransaction.commitAndContinueAsRead(ImplicitTransaction.java:62) at io.realm.internal.SharedGroupManager.commitAndContinueAsRead(SharedGroupManager.java:118) at io.realm.BaseRealm.commitTransaction(BaseRealm.java:340) at io.realm.BaseRealm.commitTransaction(BaseRealm.java:327) at io.realm.Realm.commitTransaction(Realm.java:120) at myapp.services.ToDoIntentService.getTasks(ToDoIntentService.java:350) at myapp.services.ToDoIntentService.doWork(ToDoIntentService.java:70) at myapp.services.RealmBackgroundIntentService.onHandleIntent(RealmBackgroundIntentService.java:56) at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:135) at android.os.HandlerThread.run(HandlerThread.java:61)
I have a class that I use to monitor what classes have opened a realm, and if they’ve closed it or not. I use this exclusively over Realm.getDefaultInstance()/close(). I’ve had lots of problems with fragments not shutting down in a timely fashion and so not closing their realm instances.
public class RealmMonitor {
private static volatile RealmMonitor _instance;
private final Map<String,Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>());
private final Object mapLock = new Object();
private RealmMonitor() {
}
protected static RealmMonitor getInstance() {
if (_instance == null) {
synchronized (RealmMonitor.class) {
if (_instance == null) {
_instance = new RealmMonitor();
}
}
}
return _instance;
}
public static Realm getDefaultInstance(Object o) throws RealmMigrationNeededException, RealmError, RealmIOException {
//Timber.e("REALM acquired: " + o.getClass().getName());
Realm realm = Realm.getDefaultInstance();
incrementCount(o);
return realm;
}
public static void close(Object o, Realm realm) {
if (realm != null && !realm.isClosed()) {
//Timber.e("REALM released: " + o.getClass().getName());
try {
Looper looper = Looper.myLooper();
if (looper != null) {
realm.removeAllChangeListeners();
}
}
catch (Exception e) {
Timber.e(e, "Realm change listeners could not be removed.");
}
try {
RealmConfiguration configuration = realm.getConfiguration();
realm.close();
decrementCount(o);
}
catch (Exception e) {
Timber.e(e, "Realm could not be closed.");
}
}
}
private static void incrementCount(Object o) {
String caller = o.getClass().getName();
RealmMonitor monitor = getInstance();
synchronized (monitor.mapLock) {
if (!monitor.map.containsKey(caller)) {
monitor.map.put(caller, 1);
} else {
monitor.map.put(caller, monitor.map.get(caller) + 1);
}
}
}
private static void decrementCount(Object o) {
String caller = o.getClass().getName();
RealmMonitor monitor = getInstance();
synchronized (monitor.mapLock) {
if (monitor.map.containsKey(caller)) {
monitor.map.put(caller, monitor.map.get(caller) -1);
}
}
}
private static String getCaller() {
StackTraceElement[] traceElements = Thread.currentThread().getStackTrace();
String caller = null;
for (StackTraceElement traceElement : traceElements) {
Timber.e(traceElement.getClassName());
}
for (StackTraceElement traceElement : traceElements) {
caller = traceElement.getClassName();
if (
!caller.equals(RealmMonitor.class.getName()) &&
!caller.equals(Thread.class.getName()) &&
!caller.startsWith("android") &&
!caller.startsWith("dalvik")
) {
break;
}
}
return caller;
}
public static void log() {
if (BuildConfig.DEBUG) {
synchronized (getInstance().mapLock) {
Timber.d("========================== REALM MONITOR =======================");
for (Map.Entry<String, Integer> entry : getInstance().map.entrySet()) {
Timber.d("| " + entry.getKey() + "\t - " + String.valueOf(entry.getValue()) + " Connection(s)");
}
Timber.d("========================== REALM MONITOR =======================");
}
}
}
}
I have a RealmBackgroundIntentService that looks like this:
public abstract class RealmBackgroundIntentService extends IntentService {
protected volatile Realm realm = null;
protected final Object realmLock = new Object();
public RealmBackgroundIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(Intent intent) {
PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
PowerManager.WakeLock wl = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"lock");
wl.acquire();
try {
android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
if (realm == null) {
synchronized (realmLock) {
if (realm == null) {
try {
realm = RealmMonitor.getDefaultInstance(this);
}
catch (RealmMigrationNeededException rmne) {
Timber.e(rmne, "Realm Migration Needed.");
Toast.makeText(getApplicationContext(), "You need to re-install. Thanks.", Toast.LENGTH_LONG).show();
return;
}
catch (Exception e) {
Timber.e(e,"Couldn't open realm.");
}
}
}
}
doWork(intent);
}
catch (Exception e) {
Timber.e(e, "An error occurred attempting to perform intent.");
}
finally {
synchronized (realmLock) {
if (realm != null) {
RealmMonitor.close(this, realm);
realm = null;
}
}
try {
if (wl.isHeld()) {
wl.release();
}
}
catch (Exception e) {
Timber.e(e,"Couldn't release wakelock");
}
}
}
public abstract void doWork(Intent intent);
}
The service mentioned in the stack trace, looks like this:
public class ToDoIntentService extends RealmBackgroundIntentService {
private static final String TAG = ToDoIntentService.class.getSimpleName();
@Inject
RetrofitService retrofitService;
public ToDoIntentService() {
super(TAG);
}
@Override
public void onCreate() {
super.onCreate();
MyApplication.getComponent(this).inject(this);
}
// implements the doWork from RealmBackgroundIntentService
@Override
public void doWork(Intent intent) {
getTasks(intent);
}
private void getTasks(Intent intent) {
final String id = intent.getStringExtra(Actions._ID);
EventBus.getDefault().postSticky(new CVEvents.Tasks(CVEvents.STARTED));
try {
final Call<List<Task>> getTasksCall = retrofitService.getTasks(villageId);
final Response<List<Task>> getTasksResponse = getTasksCall.execute();
if (getTasksResponse.isSuccessful()) {
final List<Task> tasks = getTasksResponse.body();
realm.beginTransaction();
realm.copyToRealmOrUpdate(tasks);
realm.commitTransaction();
EventBus.getDefault().postSticky(new CVEvents.Tasks(CVEvents.SUCCESS));
}
else {
EventBus.getDefault().postSticky(new CVEvents.Tasks(logException(getTasksResponse)));
}
}
catch (Exception e) {
EventBus.getDefault().postSticky(new CVEvents.Tasks(logException(e)));
}
}
}
This IntentService gets called 1 of 3 ways:
- A push message comes in that says I need to update
- When a user pulls to refresh on the tasks screen.
- During the initial data loading process.
For #3, I have an intent service that makes 4-5 http calls, and inserts data into the realm. Once those 4-5 initial calls are done, there’s about 8 “child” intent services that get called, per “item” from one of those initial calls (generally people have 1-5 items). Something like this:
for (Item item : items) {
String id = item.getId();
Actions.getMembers(this,id);
Actions.getTasks(this,id);
Actions.getEvents(this,id);
Actions.getAnswers(this,id);
Actions.getWeather(this,id);
Actions.getJournals(this,id);
}
Unfortunately with the way IntentServices work, at least with this crash, I don’t have any way to determine where it’s being called.
For most users the app’s data sits at 1-2MB. I’m checking with the user that experienced this crash to find out what their Data size is.
In everything I’ve seen with my RealmMonitor, all my RealmBackgroundIntentServices open and close their realms as expected. All my Activities open and close their realms as expected. All my fragments open their realms as expected, and eventually close them, as expected.
What could be going on here?
Version of Realm and tooling
Realm version(s): 1.0.0 Android Studio version: 2.1 Which Android version and device: 5.1.1, Nexus 7 (2nd gen)
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 1
- Comments: 26 (14 by maintainers)
@kneth The problem is that I can’t even reproduce the error myself. Whatever I try, the realm file keeps its size and does not grow. And on multiple devices running my app at some time it is just over 500mb big. I’m kind of desperate.
My database was 800mb but has less than 50 entries o_O
I have the exact same problem 😕
My initial guesses are:
1.) at least one of your Realm stays open on the background thread 2.) there are too many simultaneous transactions open on background threads 3.) your locking prevents Realms from being closed?
I’m not entirely sure why you need the
RealmMonitor
, Realm already ensures that you can access it only on the thread where you’ve opened it, and is otherwise threadsafe.