DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Analyzing “java.lang.OutOfMemoryError: Failed to create a thread” Error
  • Fixing OutOfMemoryErrors in Java Applications
  • Understanding Root Causes of Out of Memory (OOM) Issues in Java Containers
  • Singleton: 6 Ways To Write and Use in Java Programming

Trending

  • Building Resilient Identity Systems: Lessons from Securing Billions of Authentication Requests
  • Mastering Fluent Bit: Installing and Configuring Fluent Bit on Kubernetes (Part 3)
  • Can You Run a MariaDB Cluster on a $150 Kubernetes Lab? I Gave It a Shot
  • Ethical AI in Agile
  1. DZone
  2. Coding
  3. Java
  4. Saving Memory In Java: Making The Smallest Memory Footprint

Saving Memory In Java: Making The Smallest Memory Footprint

This article explains how to reduce memory usage in Java by demonstrating four ways to achieve the best footprint size and a way to calculate it.

By 
Dmitry Egorov user avatar
Dmitry Egorov
DZone Core CORE ·
Jul. 05, 22 · Tutorial
Likes (17)
Comment
Save
Tweet
Share
18.3K Views

Join the DZone community and get the full member experience.

Join For Free

Articles To Read Before We Start

I've already written articles about memory estimation. The first one explains how to calculate memory for objects, arrays, etc. The second one shows tools for how to estimate recursively any object size. Before you begin this article, I strongly recommend you read the first ones. Otherwise, it would be a bit difficult to understand how I calculate the size of objects.

This article will cover the next tricks and topics:

  • Using primitive fields over wrappers (e.g., Boolean -> boolean)
  • Reducing the number of classes making a flat structure (collapsing classes in one or less number of classes)
  • Using narrow data types when it's possible (e.g., short instead of int, long instead of Date, etc.)
  • Using masks to hide one type inside another (e.g., many booleans inside short)

Introducing Classes Structure To Be Reduced

Let's just cut to the chase and review the classes that we will use in our example. We will alter its structure and estimate its size step-by-step four times.

Classes that we will use in our example

Calculating First Snapshot Size

As already mentioned above, I recommend reviewing the previous articles in order to understand the memory calculation process. In our case, we will use calculations based on 64-bit Java. We will verify all our calculations with the JOL-core library developed by Alexey Shipilev. You can find examples of this library in this manual.

Initiating User Object

First, let's create our 3 objects and set all fields. In our example, we will use all unique objects, and even for booleans, we will use new instances (by using new Boolean). In that case, our calculation will be most pessimistic (from size perspective) but perfectly correct:

Initiating UserObject

Now let's calculate the size of each object (again considering that even all boolean instances are unique):

Calculate the size of each object

So after all, the total User size instance is 320 bytes (which includes UserSalary and UserPayment instances together). To verify it, let's use the JOL-core library and print their sizes: 

Verification using JOL-core library and print sizes

The printed results are 56 bytes, 136 bytes, and 320 bytes.

Now let's improve this ridiculous example and initiate Boolean values properly.

In the previous example, I initiated Booleans as unique objects. Here, we will initiate in the way we reuse the same Boolean.true and Boolean.false references (and it's how you often initialize them). With such initialization, our example will be more realistic. (This step is not an optimization, it's just an additional step to explain how memory calculation works.)

How memory calculation works

Now we have to recalculate all objects because we basically changed Boolean initialization:

Recalculate all objects

Print results are as expected: 56 bytes, 136 bytes, and 208 bytes (decrease from 320 bytes).

First Memory Optimization: Replacing All Wrappers by Primitives

Here, we make the first real optimization by using only primitives instead of wrappers objects. In such cases, we lose the null benefit option and all our values will be initialized by default. 

Using primitives instead of wrappers

Now, let's recalculate the snapshot-size considering that all primitive values have no additional reference and stay inside their container object:

Recalculate the snapshot-size considering that all primitive values have no additional reference and stay inside their container object

As you can see, all wrapper objects add 16 additional bytes. Such transformation decreases total size almost by 2 times from 208 to 112 bytes. 

Second Memory Optimization: Collapsing Data in One Class

This optimization is not possible in the majority of situations, or at least makes OOP structure just less readable and maintainable; but in some urgent situations of memory shortage, you have no other options. So, let's move all fields on one class and see improvements:Move all fields on one class and see improvements

Now having only one user object we also lost extra memory spent on references. Yes, the code is much more readable and maintainable, but we improved size almost by 2 times. JOL-Core library also confirms that the total size now is 64 bytes!

Third Memory Optimization: Using Narrow Types

If you check all types we use, you might mention that some of them cover bigger ranges than we need: e.g., salary is int value and covers from -2147483648 to -2,147,483,647, but according to our business requirement, it won't exceed 32,767 USD per month. Therefore, we can use Short data type instead of integer. In the same way, we can replace java.sql.Date and keep only long value (that are thousands of years in future and past already).

So in our case, we will make these next changes:

Using narrow types

Now the total snapshot size is 40 bytes size. Can we improve it even more? Yes!

Fourth Memory Optimization: Using Narrow Types

At this step, we will "hide" smaller data types inside bigger types.  The integer value is covered by 2^32 values. The boolean value is covered by 2^1. So inside one integer, we can "hide" 32 booleans. The same thing can be applied to these examples:

  • 32 booleans (2^1) inside 1 integer (2^32)
  • 4 bytes (2^7) inside 1 integer (2^32)
  • 2 shorts (2^16) inside 1 integer (2^32)
  • 2 integers (2^32) inside one long (2^64)
  • Etc.

Writing Logic To Hide and To Reveal Masked Value for Booleans and Shorts

In our example, we will encapsulate all 9 booleans inside one short. We will use bitwise operations (shift to right, left, etc). You can find more examples in this article. 

Transformation From Boolean To Short:

The transformation from boolean to short consists of the 3 next steps:

  1. Convert boolean to 1 (true) and 0 (false).
  2. Move that value to the left by N steps using the left shift operator (<<).
  3. Merge all values by using OR (|) operator.

So, with defined flags order as below:

And implementation of all steps inside one method:

Implementation of all steps in one method

Now having this function, we can hide boolean values in one single short:

Hide boolean values in one single short

Revealing Hidden Boolean Values From Short Value

In order to identify what flag has what value, we need to make a backward transformation:

  1. Move results value to right by N steps (depending on flag order). 
  2. Make a comparison in order to "cut" the right number.
  3. Compare this number with 1, and if it's 1, => flags value is true.

All steps look like this:

Revealing Hidden Boolean Values From Short Value

Now using all these functions we can try to hide the next values:

Our final value is 229, or can be represented in binary format as 011100101 and represent the next values as below:

Final value represented in binary format

Now having this result value, we can use mask and get a specific encapsulated boolean:

Use mask and get specific encapsulated boolean

The final calculation is that the total size of our User class is 32 bytes. 

Final calculation

Conclusions

After four transformations for our example, we got the next footprint improvements:

Memory footprints

After all transformations, we reduced the size of the class from 208 bytes to 32, or almost 7 times. Our object is much more difficult to read and maintain, but memory consumption dropped dramatically. In the case of saving 10 million users, we would need only 320MB instead of 2.1GB. There are still some tricks on how to reduce memory usage, like string interning, but I hope the examples I show were useful. Here you can find all the used examples. Thanks for reading.

Java (programming language) Memory (storage engine)

Opinions expressed by DZone contributors are their own.

Related

  • Analyzing “java.lang.OutOfMemoryError: Failed to create a thread” Error
  • Fixing OutOfMemoryErrors in Java Applications
  • Understanding Root Causes of Out of Memory (OOM) Issues in Java Containers
  • Singleton: 6 Ways To Write and Use in Java Programming

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!