Skip to content

Memory Optimization through Sharing

Share common state across huge numbers of objects

Builds on Strings and immutability and Design Pattern: Simple Factory (Creational)

Stage 3advanced3 min read

The Flyweight Pattern Solution

The new code uses the Flyweight Pattern, a structural design pattern that saves memory by sharing data between similar objects. It splits an object's data into Intrinsic (shared) and Extrinsic (unique) parts.


1. How Flyweight Pattern Solves the Issue

TreeType Class (The Flyweight)

This class is the flyweight object. It stores the data that all trees of one type have in common: name, color, and texture. We do not copy this data into every tree. We create only one instance for each different combination.

Tree Class (The Context)

This class is now light. It no longer holds the strings. It stores only:

  • Extrinsic data: $x, y$ coordinates (unique to each specific tree's location).
  • Reference to shared data: One reference to a TreeType instance.

TreeFactory Class (The Cache)

This is the central factory that makes sure TreeType instances are reused. Before it creates a new type, it checks its map (treeTypeMap) for the same name, color, and texture. If the type is there, it returns it. If not, it creates it once and stores it.


2. Key Improvements

Dramatic Memory Efficiency

Even with 1 million trees, if they all share the same properties ("Oak", "Green", "Rough"), only one TreeType object is created. So the repeated strings, which took most of the memory, shrink to one shared object. Each Tree still costs some memory (see the table below).

Reduced Object Overhead

The heavy data now lives in a shared object, so each Tree is small. The demo still creates 1 million Tree objects, but each holds only two ints and a reference. The same memory holds far more trees than before, when the program could fail with an OutOfMemoryError.

Optimized Performance

  • Garbage Collection: There are fewer heavy objects to track, so the Java Virtual Machine (JVM) spends less time on garbage collection and the program runs more smoothly.
  • Rendering: Modern graphics engines can use "instanced rendering" (drawing many copies of one model in one go) when objects share the same mesh or texture data. Flyweight makes that sharing possible.

3. Structural Breakdown

ComponentResponsibility
Flyweight (TreeType)Contains the Intrinsic State (shared data). It should be immutable (final fields, never changed after creation), so one user cannot affect the others. The demo's fields are not final, but it has no setters.
Context (Tree)Contains the Extrinsic State (unique data) and a reference to the Flyweight.
Flyweight Factory (TreeFactory)Manages the creation and caching of Flyweight objects.
Client (Forest)Works out or stores the extrinsic state and passes it to the Flyweight methods.

4. Comparison of "Before" vs "After"

These numbers are illustrative, not measured. Before: the repo code assumes 1KB per tree, so about 1GB. After: each Tree is about 32 bytes (header, two ints, one reference), so about 30MB.

FeatureBefore (Heavyweight)After (Flyweight)
Memory for 1M TreesVery High (~1GB)Low (~30MB)
Redundant Strings1,000,000 copies of "Oak"1 copy of "Oak"
ScalabilityLimited by RAMHigh; limited only by coordinates
State ManagementTangled; all data in one classSeparated into shared vs. unique

5. Summary of "Sharing"

The Flyweight Pattern is like a Library. Students do not each buy a copy of a textbook, which costs money and space. The library keeps one copy that everyone shares. Each student only keeps their own bookmark (the extrinsic state like $x, y$).

In this topic

After code1 file

After/Main.java122 linesGitHub
package DesignPatterns.StructuralPatterns.FlyweightDesignPattern.Good;import java.util.*;/** * 1. THE FLYWEIGHT (Intrinsic State) * This class stores the data that is constant across many objects. * We only want one instance of 'Oak' in memory, regardless of how many * 'Oak' trees exist in our forest. */class TreeType {    private String name;    private String color;    private String texture;    public TreeType(String name, String color, String texture) {        this.name = name;        this.color = color;        this.texture = texture;    }    // The draw method accepts extrinsic state (x, y) as parameters    // rather than storing them, keeping this object stateless-ish.    public void draw(int x, int y) {        System.out.println(            "Drawing " + name + " tree at (" + x + ", " + y + ")"        );    }}/** * 2. THE CONTEXT (Extrinsic State) * This is a lightweight object that contains the unique data (coordinates). * It maintains a reference to the shared Flyweight (TreeType). */class Tree {    private int x;    private int y;    private TreeType treeType; // Reference to shared data    public Tree(int x, int y, TreeType treeType) {        this.x = x;        this.y = y;        this.treeType = treeType;    }    public void draw() {        // Delegate rendering to the Flyweight, passing in the unique coordinates        treeType.draw(x, y);    }}/** * 3. THE FLYWEIGHT FACTORY * This manages the cache of Flyweights. It ensures that we don't * create a new TreeType if one already exists. */class TreeFactory {    static Map<String, TreeType> treeTypeMap = new HashMap<>();    public static TreeType getTreeType(        String name,        String color,        String texture    ) {        String key = name + " - " + color + " - " + texture;        // If the type doesn't exist, create it once.        if (!treeTypeMap.containsKey(key)) {            System.out.println("Creating new TreeType for: " + name);            treeTypeMap.put(key, new TreeType(name, color, texture));        }        // Otherwise, return the existing shared instance.        return treeTypeMap.get(key);    }}class Forest {    private List<Tree> trees = new ArrayList<>();    public void plantTree(        int x,        int y,        String name,        String color,        String texture    ) {        // Key logic: We fetch a shared type from the factory        TreeType type = TreeFactory.getTreeType(name, color, texture);        Tree tree = new Tree(x, y, type);        trees.add(tree);    }    public void draw() {        for (Tree tree : trees) {            tree.draw();        }    }}class Main {    public static void main(String[] args) {        Forest forest = new Forest();        // 1 Million Trees, but only ONE 'Oak' TreeType object is created.        for (int i = 0; i < 1000000; i++) {            forest.plantTree(i, i, "Oak", "Green", "Rough");        }        System.out.println("Planted 1 million trees.");        System.out.println(            "Number of TreeType objects in memory: " +                TreeFactory.treeTypeMap.size()        );    }}

Done with this topic?

How well do you know it?

PreviousThe Surrogate for Controlled Access