When building Java apps that scale, efficient memory usage and performance become critical. Today, we’ll look at two powerful (but often underused) tools:

  • 🔓 WeakHashMap / WeakHashSet – let the GC clean up what you don’t need
  • 🧮 Predefining collection sizes – minimize resizing overhead

Let’s break these down with examples.


🔁 WeakHashMap and WeakHashSet: Smart Memory-Friendly Containers

🔑 WeakHashMap in Action

import java.util.Map;
import java.util.WeakHashMap;

public class WeakMapExample {
    public static void main(String[] args) {
        Map<Object, String> map = new WeakHashMap<>();
        Object key = new Object();

        map.put(key, "Some value");

        System.out.println("Before GC: " + map.size()); // 1

        key = null; // Remove strong reference
        System.gc(); // Hint the GC to collect

        try { Thread.sleep(100); } catch (InterruptedException ignored) {}

        System.out.println("After GC: " + map.size()); // Likely 0
    }
}

💡 The key is garbage collected because the map only holds a weak reference to it.


🧺 Creating a WeakHashSet

import java.util.*;

public class WeakSetExample {
    public static void main(String[] args) {
        Set<Object> weakSet = Collections.newSetFromMap(new WeakHashMap<>());
        Object item = new Object();

        weakSet.add(item);
        System.out.println("Before GC: " + weakSet.size()); // 1

        item = null;
        System.gc();
        try { Thread.sleep(100); } catch (InterruptedException ignored) {}

        System.out.println("After GC: " + weakSet.size()); // Likely 0
    }
}

📌 Use case: A cache of objects you don’t want to prevent from being collected.


⚙️ Predefining Collection Sizes for Performance

When you know (or can estimate) the number of elements, setting an initial capacity saves time and memory.

🆚 Default vs Pre-Sized ArrayList

List<String> dynamicList = new ArrayList<>();
List<String> preSizedList = new ArrayList<>(1000);

for (int i = 0; i < 1000; i++) {
    dynamicList.add("Item " + i);
    preSizedList.add("Item " + i);
}

✅ The preSizedList avoids multiple internal array resizes as it grows, leading to:

  • Less heap churn
  • Fewer GC cycles
  • More predictable memory use

Predefining HashMap Size

Map<Integer, String> map = new HashMap<>(1000);
for (int i = 0; i < 1000; i++) {
    map.put(i, "Value " + i);
}

🔍 Internally, every resize in HashMap involves rehashing all entries, which is expensive.


🧪 Benchmark It (Optional but Smart)

If you want to confirm performance gains in your context, try using JMH (Java Microbenchmark Harness). It’s a great way to test memory usage and execution time differences.


🧵 Recap

Concept Use It For Benefits Watch Out
WeakHashMap Caches, listeners, soft associations Auto-cleans unused keys Don’t use for core state
WeakHashSet Temporary or optional references Prevents memory leaks Created via Collections.newSetFromMap()
Predefined Sizes Known/estimated data volume Faster, fewer allocations Overestimating wastes memory

💬 Your Turn

Have you used weak references or optimized collection sizing in your projects? What kind of impact did you see? Share in the comments 👇