Sitemap

🚀 I Put 1.5 Million Entries into a Java HashMap. Here's What I Learned Using VisualVM.

--

“We all know HashMap provides average O(1) operations. But what actually happens inside the JVM when you insert one million or more entries?"

I’ve used HashMap almost every day for years, but I had never actually measured how the JVM stores millions of entries.

Instead of reading another article explaining buckets and hashing, I wanted to answer questions like:

  • How much heap does it really consume?
  • What objects are created?
  • What is HashMap$Node?
  • Why does VisualVM show HashMap$Node[]?
  • Does pre-sizing really help?
  • How gracefully does the JVM handle millions of entries?

So I decided to run a small experiment.

Environment

  • Java 17 (Temurin)
  • Spring Tool Suite
  • VisualVM 2.2.1
  • macOS Intel

The benchmark is intentionally simple.

Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < entries; i++) {
map.put(i, i);
}
Thread.sleep(300000);

The sleep allows attaching VisualVM after insertion.

Experiment 1–100K Entries

Console

Insertion Finished
Time : 43 ms

Heap Dump

📷 Screenshot 1

Press enter or click to view image in full size

The heap dump shows approximately

ObjectCountInteger 200,033

HashMap$Node 104,401

HashMap$Node[] 1,033

My first question was…

Why are there 200K Integer objects for only 100K entries?

The answer is simple.

Every insertion

map.put(i, i);

creates

Integer Key
+Integer Value+HashMap Node

So

100K entries
↓100K Keys+100K Values↓≈200K Integer Objects

Exactly what VisualVM reported.

Understanding HashMap$Node

Every entry creates one internal node.

Conceptually,

Node
hashkeyvaluenext

VisualVM showed approximately one HashMap$Node per map entry.

The Most Confusing Part — HashMap$Node[]

Initially I thought

Isn’t the bucket array just storing nodes?

Not exactly.

The bucket array stores references.

HashMap
↓Bucket Array↓Reference↓Node

Think of it like

Bucket[0] ----> Node
Bucket[1] ----> nullBucket[2] ----> Node

The array and the node objects are completely different heap objects.

That explains why VisualVM reports them separately.

Experiment 2–1 Million Entries

Console

Max Heap : 2048 MB
Heap Used : 85 MBInsertion Time : 318 ms

Heap Dump

📷 Screenshot 2

Press enter or click to view image in full size

VisualVM now reported approximately

Get Rvjh’s stories in your inbox

Join Medium for free to get updates from this writer.

ObjectCountHashMap$Node 1,004,401

Integer 2,000,033

Bucket Arrays Same number of arrays, but much larger

One interesting observation:

The JVM handled one million entries surprisingly well.

No OutOfMemoryError.

No unusual slowdown.

Just predictable heap growth.

Experiment 3 — Does Pre-sizing Help?

Now I compared

new HashMap<>();

with

new HashMap<>(2_097_152);(the capacity is large enough to avoid most resizes for ~1.5M entries).

Without Pre-sizing

1.5 Million Entries
Heap Used : 115 MBInsertion Time : 552 ms

With Pre-sizing

1.5 Million Entries
Heap Used : 105 MBInsertion Time : 335 ms

Heap Dump

📷 Screenshot 3

Press enter or click to view image in full size

Results

ConfigurationHeap UsedInsert TimeDefault

HashMap 115 MB 552 ms

Pre-sized HashMap 105 MB 335 ms

The interesting part wasn’t memory.

It was time.

Pre-sizing reduced insertion time by almost 40%.

Why?

Because the default HashMap resized multiple times.

Each resize involves:

  • allocating a larger bucket array
  • recomputing bucket indexes
  • redistributing existing nodes

Pre-sizing avoids most of that work.

The final memory usage is almost the same because both versions ultimately store the same number of objects.

even after increasing from 100K to 1 million entries.

One thing initially confused me.

VisualVM always showed around

HashMap$Node[]
1033 objects

even after increasing from 100K to 1 million entries.

Why?

Because VisualVM is showing all live HashMaps in the JVM, not just mine.

The important thing wasn’t the number of arrays.

It was their size.

For example, my benchmark HashMap eventually had a bucket array containing over 2 million references, which VisualVM exposed when expanding the object tree.

That was my “aha!” moment.

What I Learned

This small experiment taught me much more than reading documentation.

✅ Every map entry creates one HashMap$Node.

✅ Keys and values are separate objects.

✅ Bucket arrays store references, not nodes.

✅ The JVM grows heap gradually instead of immediately allocating the maximum heap.

✅ Pre-sizing doesn’t significantly reduce final memory usage.

✅ Pre-sizing noticeably improves insertion performance by avoiding repeated resizing and rehashing.

Why I Think This Matters

Most articles explain how HashMap works.

Very few actually measure it.

Running a small benchmark and opening a heap dump in VisualVM completely changed my understanding.

Instead of assuming how the JVM behaves, I could actually see:

  • object counts
  • heap usage
  • bucket arrays
  • internal nodes

That made the implementation much easier to understand.

What’s Next?

I’m now curious to run similar experiments with:

  • ConcurrentHashMap
  • ArrayList
  • PriorityQueue
  • LinkedHashMap
  • Large custom objects
  • Different GC algorithms
  • 10 million+ entries

Final Thoughts

One of the biggest lessons from this exercise wasn’t about HashMap itself—it was about measuring instead of assuming.

As backend engineers, we often discuss performance in terms of Big-O notation. That’s important, but pairing that knowledge with tools like VisualVM gives a much clearer picture of how our code behaves in the JVM.

If you’re preparing for Java interviews or simply curious about JVM internals, I’d encourage you to try a similar experiment. It’s simple to set up, and you’ll come away with a much deeper understanding than theory alone can provide.

Note:
“This experiment isn’t meant to benchmark HashMap exhaustively. The goal was to build intuition by observing how the JVM represents and manages a large in-memory data structure using VisualVM. If it sparks curiosity for someone else to run similar experiments, it has served its purpose."

--

--