How Singleton Save Me From Out of Memory Error

Hi there, long time I don’t talk about something more “techies” (not that Techies from Dota 2). I’ve been working on an Android Project and I want to talk about one library I’ve used extensively: Google GSON. The particular usage of GSON is to simplify the painful method of processing JSON object in Java.

Introduction to GSON

Consider this JSON as string:

{ 'id': 'EdgarDrake',
  'name': 'Edgar',
  'games': [
    { 'id': 'bethsw-bethgs-esv-skyrim',
      'name': 'The Elder Scrolls V: Skyrim',
      'price': 60,
      'meta': {
         'publisher': 'Bethesda Softworks',
         'developer': 'Bethesda Game Studio',
         'avg_rating': 4.8
       }
    },
    { 'id': 'ncsoft-arenanet-gw2',
      'name' : 'Guild Wars 2',
      'price': 45,
      'meta': {
         'publisher': 'NCSoft',
         'developer': 'ArenaNet',
         'avg_rating': 4.3
       }
     }
   ]
}

In Python, you can feed that JSON to variable and directly deserialize it to dictionary. Easy. Dead simple. Say, assign a variable called user with that JSON.

Q1: How to get user’s second game publisher name?

Q2: How to generate JSON string a new user Regulus who has game Destiny?

In Python, it’s as easy as:

// Q1
user.games[1].meta.publisher
// Q2
usr2 = {name:"Regulus",
        id:"0284R1R2L1L2",
        games:[
          {id:"actvsn-bungie-destiny",
           name:"Destiny",
           price:45,
           meta: {publisher:"Activision",
                  developer:"Bungie",
                  avg_rating:3.0}
        ]
       }
usr2.toString()

How do you do that in Java? 

// Q1
String json = that_json_above;
JSONObject user = new JSONObject(json);
user.optJSONArray("games").get(1)
  .optJSONObject("meta")
  .optString("publisher");

// Q2
JSONObject usr2 = new JSONObject();
usr2.put("name", "Regulus")
  .put("id", "0284R1R2L1L2")
  .put("games", new JSONArray()
    .put(new JSONObject()
      .put("id", "actvsn-bungie-destiny")
      .put("name", "Destiny")
      .put("price", 45)
      .put("meta", new JSONObject()
        .put("publisher", "Activision")
        .put("developer", "Bungie")
        .put("avg_rating", 3.0)
      )
    )
  );
// Output JSON Object usr2 as string
usr2.toString();

Wow. Simply wow. Look how convoluted is that to serialize or deserialize a JSON object in Java. Imagine what would you do if you want to process many user data with more fields with more “depth”. GSON is here to help simplify the matter with more familiar approach: treat JSON object as instance of class. What does it mean? It means that we can get any associated field from the variable using direct approach.

// Q1
String json = still_that_json_above;
User user = new Gson().fromJSON(json, User.class);
user.games[1].meta.publisher;

// Q2
Game destiny = new Game("actvsn-bungie-destiny", "Destiny", 45, "Activision", "Bungie", 3.0);
Game[] games = new Game[] { destiny };
User usr2 = new User("Regulus", "0284R1R2L1L2", games);
// Output Object usr2 as JSON-formatted string
new Gson.toJSON(usr2);

/**
 * Define all the classes below and begone with it
 */
public class User {
  public String id;
  public String name;
  public Game[] games;
  // Constructor
  public User(String id, String name, Game[] game) {
    this.id = id;
    this.name = name;
    this.game = game;
  }
}

public class Game {
  public String id;
  public String name;
  public int price;
  public Metadata meta;
  // Constructor
  public Game(String id,
              String name,
              int price,
              String publisher,
              String developer,
              float avg) {
    this.id = id;
    this.name = name;
    this.meta = new Metadata();
    this.meta.publisher = publisher;
    this.meta.developer = developer;
    this.meta.avg_rating = avg;
  }

  class Metadata {
    public String publisher;
    public String developer;
    public float avg_rating;
  }
}

See, GSON makes handling JSON object simpler and easier to understand. Simpler doesn’t necessarily means shorter. As long as you have predefined the required classes, you’re good to go with GSON. However, please note that Java class is not like JavaScript or Python which can access a property from the hash key (in Javascript/Python: user.name == user["name"]), therefore for the sake of consistency, it is ill-advised to use -(dash) as property name (although, GSON can handle -(dash) with simple annotation).

The Problem

I use GSON extensively to convert JSON from socket.io to Notification class. My app contains list of ABUNDANT number of notification JSON, where each notification is a quite complex object. To enable ubiquity of notification, I made the notification in a singleton, a static variable of a class which can be accessed anywhere. Here’s the algorithm for notification:

  1. Fetch all notification until app start time with GET notification API. If connection unavailable, read notification from local storage (SharedPreferences).
  2. Store obtained list of notifications from API to local storage (SharedPreferences) to ensure data integrity
  3. Append any incoming notification from socket.io to the list. New notification may update existing notification, therefore append only happen new unique notification. Update notification should move the respective notification to the top of the list.

So far, the code is like this:

public class NotificationUtility {
  public static List<Notification> getList() {
    String storedNotifs = readFromStorage(location);
    return new Gson().fromJson(storedNotifs, new TypeToken<List<Notification>>(){}.getType());
  }

  public static void store(String apiResponse) {
    writeToStorage(apiResponse);
  }

  public static void append(String socketResponse) {
    Gson gson = new Gson();
    Notification incoming = gson.fromJSON(socketResponse, Notification.class);
    List<Notification> notifications = getList();
    if (notifications.contains(incoming) {
      update(notifications);
    } else {
      // insert new notif to first position
      notifications.add(0, incoming);
    }
    String updatedNotifs = gson.toJSON(notifications);
    writeToStorage(updatedNotifs);
  }
}

Any problem? The code makes sense, so I left it as is. I run the code above in the background process, so that the app can handle any incoming socket message anytime.

We use Crashlytics — a library from Twitter’s Fabric SDK. Whenever crash occured, Crashlytics will record the crash and device stacktrace, then send the data to our dashboard. Therefore, we know when error occured, at what lines, and what type of error. We, however, do not track/record the processed data that cause the crash, because it is the same with privacy breach. Out of blue, Crashlytics in production shown that a lot of our customer experience the crash with a single, unknown exception: Java OutOfMemoryError Exception. In development mode, we rarely encountered that error; if occured, it occured on low-end devices.

…..

Damn.

The stacktraces were useless, I didn’t know why. Other errors that Crashlytics recorded were useful, at least the record which Android Activity the app crashed, at what line. Not this one. I had no clue why this happened. Sometimes the error is quite weird: Dalvik Scheduler stop? How am I supposed to care about something as low as OS runtime scheduler from my app.

I always think that maybe the RAM is unable to handle our app. Maybe the image is too big. Maybe other resource is too much. Or maybe the request is too big and too complex to handle. If it is the latter, then which API is the problem?

……

Then, one day, it occured to my device. It crash in NotificationUtility.getList() with OutOfMemoryError. Hmm, weird.

The Realization

I am a stupid, naive programmer. I couldn’t see a HUGE problem lies in the NotificationUtility code. But then it made me realize that the problem is two-fold actually.

  1. I naively read from storage anytime I need the notification, then convert it to the list of notifications using GSON. I can reduce the overhead of write by using the list in RAM, and whenever notification is modified, just write. No more than once read.
  2. The string from local storage is freed, of course, because the string is already unused beyond GSON conversion. The GSON object, however, is not freed, because I keep the reference in background process. Whenever I need to append new notifications, I use NotificationUtility.getList() which generate new GSON object to handle the list of notification’s data structure. Apparently, new GSON object keep generated whenever getList() invoked, without being able to be freed the data structure (perhaps because the method is static, therefore there’s no guarantee that the GC (garbage collector) will freed the allocated heap.

Solution: Make the List of notifications and GSON object as singleton.

List of notifications is obvious solution, but what’s wrong with GSON? With a single GSON across whole app, the sole GSON data structure will be repeatedly changed (because I use GSON to process any API response). Thus, it is guaranteed that only one GSON ever generated in this app. There will be no more “zombie” GSON object anymore. The logic should be

  1. Fetch all notification until app start time with GET notification API. If connection unavailable, read notification from local storage (SharedPreferences) once.
  2. Store obtained list of notifications from API stored to both List of Notification in RAM and local storage (SharedPreferences) to ensure data integrity.
  3. Append any incoming notification from socket.io to the list. New notification may update existing notification, therefore append only happen new unique notification. Update notification should move the respective notification to the top of the list. Check to RAM only. Write to if changed. No more read from local storage.

The updated code:

public static NotificationUtility {
  // singleton GSON object, to be used by any other class (dirty)
  public static Gson gson = new Gson();
  // private singleton list of notifications, to be accessed from BackgroundService
  private static List<Notification> notifications;

  public static List<Notification> getList() {
    // Ensure read only occured if no existing list exists
    if (notifications == null) {
      String storedNotifs = readFromStorage(location);
      notifications = gson.fromJson(storedNotifs, new TypeToken<List<Notification>>(){}.getType());
    }
    return notifications
  }

  public static void store(String apiResponse) {
    NotificationUtility.notifications = gson.fromJson(apiResponse, new TypeToken<List<Notification>>(){}.getType());
    writeToStorage(apiResponse);
  }

  public static void append(String socketResponse) {
    Notification incoming = gson.fromJSON(socketResponse, Notification.class);
    List<Notification> tempList = getList();
    if (tempList.contains(incoming) {
      update(tempList);
    } else {
      // insert new notif to first position
      tempList.add(0, incoming);
    }
    String updatedNotifs = gson.toJSON(tempList);
    writeToStorage(updatedNotifs);
    // Override existing global list of notifications
    notifications = tempList;
  }
}

Hmm.. Yep. One OutOfMemoryError done. Still some more causes to go. Perhaps a little bit of refactoring or changing image downloader library might be a viable choice. Any discussion to improve the code performance is welcomed.

Advertisements

8 thoughts on “How Singleton Save Me From Out of Memory Error

  1. Ridwan says:

    This is not a singleton. And this code is probably one of many example why some people discourage the uses of static in Java. It’s not evil, but easy to abuse, and once you did it wrong you’ll end up with a lot of undesired behavior that is so hard to trace.

    Is there any good reason not to use a normal instance object?
    NotificationUtility.getList() is probably faster than (new NotificationUtility()).getList(), and it looks cleaner, but the difference in performance is negligible in most cases. The list of notification is the only static variable needed, that is somewhat justifiable because it is used as a simple memory cache.

    On a completely different note. You are not expecting the notification to contains big amount of data, right? because if you are, you might find trouble later by storing it in shared preference, and by using strong reference as a cache.

    • Static object, why? Because the notification needed to be accessed from anywhere, like any activity or adapter. Rather than holding the reference to NotificationUtility instance in each activity/adapter like normally instatiated data ( NotificationUtility util; util.getList() ), it is much cleaner to treat the NotificationUtility as an object ( NotificationUtility.getList() ). Yes, I need the Notification Utility to be as ubiquitous as possible, that’s why I don’t make it normal object.

      Oh for the last paragraph: our v1 notification API is quite stupid, as the response is not paged, and some notifications are duplicated (like: User A like your status X. User B like your status x), thus, the app need to trim-down the duplicated object in phone. So, the answer is yes, the number of object stored in the list is ABUNDANT.

      Our team actually now working to optimize the notification code to make it much cleaner. For the time being, our app must hide the pain of unoptimized API response from user. After the v2 release, the app will handle less response, only the needed one to be stored in Shared Preference.

      • Ridwan says:

        Because you lost a lot of OOP benefits if you put everything into static. No polymorphism, no inheritance, no other OOP goodness, you don’t even have any constructor to pass anything to. And you can’t use unit test on all static class like this. In other words, unless you are really really completely sure that each method will always be stateless now and later in the future, and you don’t need abstraction, then don’t use static.

        Let’s say, for example. You had a requirement change, and for this you somewhat decided that you need to replace Gson with another library which handle serializing, caching, and every bit of nice feature you need.

        Okay, easy, just replace gson variable with the new library, done.
        Well, if things are that easy then you’re lucky. Let’s add a bit more problem here: you need to pass Context for the new library every time you use it.

        What could you do? Create a setContext method? no, that won’t do, that makes it stateful and you’ll find it annoying later that the context is suddenly set as null, or changed to a completely unrelated context and that break things. After all setContext method is accessible from anywhere.

        Okay, setContext won’t do, then the other options is to add Context argument to each of the method. It’s nice, easy to call from anywhere, and pose no problem. The “only” problem is that the existing method is called everywhere in the project and you need to update each one of them. Your code become coupled and your code might break somewhere you don’t know.

        If this happens on a normal instance object. All you need to do is update the constructor and let the compiler solve the problem by itself (assuming you use dependency injection or something similiar, and even if you don’t there is still a lot of options to do).

        This is just one of many problem that could happen on a class that have no abstraction. You might want to keep this class simple by naming it ‘Utility.’ Everything stateless, all happy. I would agree on that if software requirement is frozen, unchanging no matter what happens outside.

        Frankly speaking, I think that this class is likely to change as the requirement changes, and making everything static just limit things.
        And if you insist on static, then it’s better to use proper singleton. Not this.

      • Nice, you actually know your way around. Yes, in reality, my code is actually passed the context as parameter, but for I omit here to simplify it.

        On the next part, what makes static class a proper singleton?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s