here’s a visual diagram showing how Java array references work in memory, and why changes in one variable can affect another.

1. Reference Assignment (Shared Array)

int[] a = {1, 2, 3};
int[] b = a;
// b points to the same array as a
b[0] = 99;

Memory Diagram:


   +-----+       +-------------------+
a: | * | ---> | [99] [ 2 ] [ 3 ] |
   +-----+ +-------------------+
b: | * | -----^
   +-----+
  • Both a and b reference the same array object.
  • Changing b[0] changes a[0] because they point to the same memory.

2. Cloning (Independent Copy)

int[] a = {1, 2, 3};
int[] b = a.clone();
// Creates a new array with copied values
b[0] = 99;

Memory Diagram:

   +-----+       +-------------------+
a: | * | ---> | [ 1 ] [ 2 ] [ 3 ] |
   +-----+ +-------------------+

   +-----+ +-------------------+
b: | * | ---> | [99] [ 2 ] [ 3 ] |
   +-----+ +-------------------+

  • a and b now point to different arrays.
  • Changing b does not affect a.

3. Multi-Dimensional (Array of Arrays)

int[][] matrix = new int[2][3];
matrix[0][0] = 5;

Memory Diagram:

matrix
|
v
+-----+ +-----+ +-------------------+
| * | ---> | * | ---> | [ 5 ] [ 0 ] [ 0 ] |
+-----+ +-----+ +-------------------+
| * | ---+ | * | ---> | [ 0 ] [ 0 ] [ 0 ] |
+-----+ +-------------------+
  • matrix is a reference to an array of row references.
  • Each row is itself an array object.

πŸ’‘ Key Insight:
In Java, variables for arrays don’t hold the array itself β€” they hold a reference (pointer) to the array object in heap memory.
That’s why:

  • Assigning one array variable to another shares the same object.
  • Cloning or copying creates a new object.


here’s a short, runnable Java program that prints the identity hash codes of arrays so you can see whether two variables point to the same object or not.

Java Program: Array Reference Identity Check

import java.util.Arrays;

public
class ArrayReferenceDemo {
    public static void main(String[] args) {
        // Original array
        int[] a = {1, 2, 3};
        // Reference assignment (shared object)
        int[] b = a;
        // Independent copy (new object)
        int[] c = a.clone();

        // Print arrays
     System.out.println("a: " + Arrays.toString(a));
     System.out.println("b: " + Arrays.toString(b));
     System.out.println("c: " + Arrays.toString(c));
        
        // Print identity hash codes (like memory IDs)
     System.out.println("\nIdentity Hash Codes:");
     System.out.println("a: " + System.identityHashCode(a));
     System.out.println("b: " + System.identityHashCode(b));
     System.out.println("c: " + System.identityHashCode(c));
        
        // Modify b and see effect on a  
     b[0] = 99;
     System.out.println("\nAfter modifying b[0] = 99:");
     System.out.println("a: " + Arrays.toString(a)); // Changed
     System.out.println("b: " + Arrays.toString(b)); // Changed
     System.out.println("c: " + Arrays.toString(c)); // Unchanged
    }
}

Example Output:

a: [1, 2, 3]
b: [1, 2, 3]
c: [1, 2, 3]

Identity Hash Codes:
a: 366712642
b: 366712642
c: 1829164700

After modifying b[0] = 99:
a: [99, 2, 3]
b: [99, 2, 3]
c: [1, 2, 3]

What This Shows:

  • a and b have the same identity hash code β†’ they reference the same array object.
  • c has a different identity hash code β†’ it’s a different array object.
  • Changing b also changes a, but not c.

Java Program: Multi-Dimensional Array Reference Behavior

import java.util.Arrays;

public
class MultiDimArrayReferenceDemo {
    public static void main(String[] args) {
        // Create a 2D array (array of arrays)
        int[][] matrix = {
{1, 2, 3},
{4, 5, 6}
};// Shallow clone: only outer array is cloned

        int[][] shallowClone = matrix.clone();
        
        // Deep copy: clone each inner array
        int[][] deepCopy = new int[matrix.length][];

        for
(int i = 0; i < matrix.length; i++) {
deepCopy[i] = matrix[i].clone();
}
        
        // Print identity hash codes
System.out.println("Outer array identity hash codes:");
System.out.println("matrix: " + System.identityHashCode(matrix));
System.out.println("shallowClone: " + System.identityHashCode(shallowClone));
System.out.println("deepCopy: " + System.identityHashCode(deepCopy));
System.out.println("\nInner array[0] identity hash codes:");
System.out.println("matrix[0]: " + System.identityHashCode(matrix[0]));
System.out.println("shallowClone[0]: " + System.identityHashCode(shallowClone[0]));
System.out.println("deepCopy[0]: " + System.identityHashCode(deepCopy[0]));

        // Modify shallowClone's inner array
shallowClone[0][0] = 99;
System.out.println("\nAfter modifying shallowClone[0][0] = 99:");
System.out.println("matrix: " + Arrays.deepToString(matrix)); // Changed
System.out.println("shallowClone: " + Arrays.deepToString(shallowClone)); // Changed
        System.out.println("deepCopy: " + Arrays.deepToString(deepCopy)); // Unchanged

}
}

Example Output:


Outer array identity hash codes:
matrix: 366712642
shallowClone: 1829164700
deepCopy: 2018699554

Inner array[0] identity hash codes:
matrix[0]: 1239731077
shallowClone[0]: 1239731077
deepCopy[0]: 356573597

After modifying shallowClone[0][0] = 99:
matrix: [[99, 2, 3], [4, 5, 6]]
shallowClone: [[99, 2, 3], [4, 5, 6]]
deepCopy: [[1, 2, 3], [4, 5, 6]]

Key Takeaways:

  1. Shallow clone of a 2D array:

    • Outer array is new.
    • Inner arrays are shared references.
    • Changing an inner array in one affects the other.
  2. Deep copy:

    • Outer array is new.
    • Each inner array is also cloned.
    • No shared references β€” changes don’t leak.

NOTE: How new int[2][] works

new int[2][]

creates a two-dimensional jagged array (an array of arrays) where:

  • The first dimension has a fixed length of 2.
  • The second dimension is not yet allocated β€” each element in the first dimension is initially null and must be assigned later.

How it works

Step-by-step:

  1. new int[2][]

    • Creates an array of length 2 where each element is of type int[] (array of integers).
    • At this point, both elements are null because the second dimension is not initialized.
  2. You can later assign each row individually, possibly with different lengths (jagged array):

int[][] arr = new int[2][]; // First dimension length = 2, second dimension undefined

arr[0] = new int[3]; // First row: length 3
arr[1] = new int[5]; // Second row: length 5

// Assign values
arr[0][0] = 10;
arr[1][4] = 20;

Memory layout

arr β†’ [  int[3]  ,  int[5]  ]
↑ ↑
{10,0,0} {0,0,0,0,20}

  • arr.length is 2.
  • arr[0].length is 3.
  • arr[1].length is 5.

Example runnable program

public class JaggedArrayExample {
public static void main(String[] args) {
// Create first dimension with size 2
        int[][] arr = new int[2][];
// Initialize each row separately

arr[0] = new int[3]; // Row 0: length 3
arr[1] = new int[5]; // Row 1: length 5
        // Assign some values

arr[0][0] = 1;
arr[0][1] = 2;
arr[0][2] = 3;
arr[1][4] = 99;
// Print the jagged array

        for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j] + " ");
}
System.out.println();
}
}
}

Output:

1 2 3 0 0 0 0 99 

βœ… Key points:

  • new int[2][] fixes only the first dimension.
  • The second dimension can have different lengths for each row (jagged array).
  • Until you assign them, arr[0] and arr[1] are null.


compare new int[2][] vs new int[2][3] in Java with diagrams so you can clearly see the difference in memory allocation and initialization.

1. new int[2][] β€” Jagged Array (Partially Initialized)

int[][] arr = new int[2][];

First dimension: length = 2 (array of int[] references).
Second dimension: not allocated yet β€” each element is null until you assign it.

Memory layout right after creation:

arr ↓[ null , null ]

If you try to access arr[0][0] now, you'll get a NullPointerException because arr[0] is null.

You must initialize each row separately:

arr[0] = new int[3]; // Row 0: length 3arr[1] = new int[5]; // Row 1: length 5

Now:

arr ↓[ int[3] , int[5] ]   ↓        ↓[0,0,0]  [0,0,0,0,0]


2.new int[2][3]β€” Fully Initialized Rectangular Array

int[][] arr = new int[2][3];

First dimension: length = 2.
Second dimension: length = 3 for every row.
All elements are automatically initialized to 0 (default for int).

Memory layout right after creation:

arr ↓[ int[3] , int[3] ]   ↓        ↓[0,0,0]  [0,0,0]

Here, arr[0][0] is safe to access immediately β€” no NullPointerException.

Side-by-side comparison

Feature
new int[2][] (Jagged)
new int[2][3] (Rectangular)
First dimension length
2
2
Second dimension length
Unspecified (null rows)
Fixed (3 for all rows)
Memory allocated for rows
No (until assigned)
Yes (all rows allocated)
Can have different row lengths
βœ… Yes
❌ No (all same length)
Risk of NullPointerException
βœ… Yes (if uninitialized)
❌ No

Runnable demo

public class ArrayComparison {
public static void main(String[] args) {
// Jagged array int[][] jagged = new int[2][];
jagged[0] = new int[3];
jagged[1] = new int[5];
// Rectangular array int[][] rectangular = new int[2][3];
System.out.println("Jagged array lengths: " + jagged[0].length + ", " + jagged[1].length);
System.out.println("Rectangular array lengths: " + rectangular[0].length + ", " + rectangular[1].length);
}
}

Output:

Jagged array lengths: 3, 5Rectangular array lengths: 3, 3





here’s the heap memory diagram for both new int[2][] (jagged) and new int[2][3] (rectangular) so you can see exactly how Java stores them internally.

​

1. new int[2][] β€” Jagged Array

​

Java
コードをコピー
int[][] jagged = new int[2][];jagged[0] = new int[3];jagged[1] = new int[5];

Heap layout:

コードをコピー
jagged ──► [ ref0 , ref1 ]   // length = 2            β”‚       β”‚            β–Ό       β–Ό         int[3]   int[5]         [0,0,0]  [0,0,0,0,0]

The outer array (int[][]) holds references to int[] objects.
Each row is a separate object in memory.
Row lengths can differ (3 and 5 here).
If you don’t assign a row, its reference stays null.

2. new int[2][3] β€” Rectangular Array

Java
コードをコピー
int[][] rectangular = new int[2][3];

Heap layout:

コードをコピー
rectangular ──► [ ref0 , ref1 ]   // length = 2                 β”‚       β”‚                 β–Ό       β–Ό              int[3]   int[3]              [0,0,0]  [0,0,0]

The outer array still holds references to int[] objects.
All rows are allocated automatically with the same length (3).
No null rows β€” safe to access immediately.

Key takeaway

  • Jagged (new int[2][]) β†’ flexible row sizes, but you must allocate each row manually.
  • Rectangular (new int[2][3]) β†’ fixed row size, all allocated at once.

Runnable demo with diagrams in output

Java
コードをコピー
public class ArrayMemoryDemo {    public static void main(String[] args) {        // Jagged array int[][] jagged = new int[2][];        jagged[0] = new int[3];        jagged[1] = new int[5];        // Rectangular array int[][] rectangular = new int[2][3];        // Print jagged array structure        System.out.println("Jagged array:");        for (int i = 0; i < jagged.length; i++) {            System.out.println("Row " + i + " length: " + jagged[i].length);        }        // Print rectangular array structure        System.out.println("\nRectangular array:");        for (int i = 0; i < rectangular.length; i++) {            System.out.println("Row " + i + " length: " + rectangular[i].length);        }    }}

Output:

コードをコピー
Jagged array:Row 0 length: 3Row 1 length: 5Rectangular array:Row 0 length: 3Row 1 length: 3



go deep into JVM memory representation and show an ASCII + pointer-style diagram for

new int[2][] (jagged) vs new int[2][3] (rectangular).

This will help you visualize how the heap stores objects and references.

1. Jagged Array β€” new int[2][]

Java
コードをコピー
int[][] jagged = new int[2][];jagged[0] = new int[3];jagged[1] = new int[5];

Heap representation (conceptual):

コードをコピー
Address: 0x100  jagged (int[][]) object----------------------------------------[ ref@0x200 , ref@0x300 ]   // length = 2   ↑           ↑   β”‚           β”‚   β”‚           └──► Address: 0x300  int[5] object   β”‚                -------------------------   β”‚                [ 0 , 0 , 0 , 0 , 0 ]   β”‚   └──► Address: 0x200  int[3] object                    -----------------                    [ 0 , 0 , 0 ]

Outer array at 0x100 holds two references.
Each reference points to a different int[] object in memory.
Row lengths can differ (3 and 5 here).
If you didn’t assign jagged[1], ref@0x300 would be null.

2. Rectangular Array β€” new int[2][3]

Java
コードをコピー
int[][] rectangular = new int[2][3];

Heap representation (conceptual):

コードをコピー
Address: 0x400  rectangular (int[][]) object---------------------------------------------[ ref@0x500 , ref@0x600 ]   // length = 2   ↑           ↑   β”‚           └──► Address: 0x600  int[3] object   β”‚                -----------------   β”‚                [ 0 , 0 , 0 ]   β”‚   └──► Address: 0x500  int[3] object                    -----------------                    [ 0 , 0 , 0 ]

Outer array at 0x400 holds two references.
Both rows are allocated automatically with the same length (3).
No null rows β€” safe to access immediately.

Key JVM facts

  • In Java, multidimensional arrays are arrays of arrays β€” even for int[][].
  • The outer array stores references (pointers) to inner arrays.
  • Each inner array is a separate object in the heap.
  • This is why jagged arrays can have different row lengths.

Runnable verification code

Java
コードをコピー
public class ArrayPointerDemo {    public static void main(String[] args) {        int[][] jagged = new int[2][];        jagged[0] = new int[3];        jagged[1] = new int[5];        int[][] rectangular = new int[2][3];        System.out.println("Jagged outer array identity: " + System.identityHashCode(jagged));        System.out.println("Jagged row 0 identity: " + System.identityHashCode(jagged[0]));        System.out.println("Jagged row 1 identity: " + System.identityHashCode(jagged[1]));        System.out.println("\nRectangular outer array identity: " + System.identityHashCode(rectangular));        System.out.println("Rectangular row 0 identity: " + System.identityHashCode(rectangular[0]));        System.out.println("Rectangular row 1 identity: " + System.identityHashCode(rectangular[1]));    }}

Sample Output (addresses simulated via hash codes):

コードをコピー
Jagged outer array identity: 366712642Jagged row 0 identity: 1829164700Jagged row 1 identity: 2018699554Rectangular outer array identity: 1311053135Rectangular row 0 identity: 109647522Rectangular row 1 identity: 356573597

Different hash codes = different objects in heap.
Confirms that each row is its own object.


go all the way down into how the JVM actually stores
new int[2][] (jagged) and new int[2][3] (rectangular) in heap memory,
including object headers, length fields, and element storage layout.

JVM Array Object Layout Basics

In HotSpot JVM (typical 64-bit, compressed OOPs enabled):

  • Object Header (12 bytes):
    • Mark Word (8 bytes) β†’ stores hash code, GC info, lock state.
    • Class Pointer (4 bytes) β†’ reference to the array’s class metadata.
  • Array Length (4 bytes) β†’ number of elements in the array.
  • Padding β†’ to align to 8-byte boundaries.
  • Elements β†’ stored contiguously after the header.

For primitive arrays (int[]), each element is 4 bytes.
For reference arrays (int[][] outer array), each element is a 4-byte reference (compressed OOP).

1. Jagged Array β€” new int[2][]

Java
コードをコピー
int[][] jagged = new int[2][];jagged[0] = new int[3];jagged[1] = new int[5];

Heap layout (conceptual, with offsets):

コードをコピー
0x100: int[][] object (outer array)-----------------------------------[  8 bytes  ] Mark Word[  4 bytes  ] Class Pointer (to "[[I" class)[  4 bytes  ] Length = 2[  4 bytes  ] ref -> 0x200  (jagged[0])[  4 bytes  ] ref -> 0x300  (jagged[1])[ padding   ] (if needed)0x200: int[3] object (row 0)----------------------------[  8 bytes  ] Mark Word[  4 bytes  ] Class Pointer (to "[I" class)[  4 bytes  ] Length = 3[  4 bytes  ] 0[  4 bytes  ] 0[  4 bytes  ] 00x300: int[5] object (row 1)----------------------------[  8 bytes  ] Mark Word[  4 bytes  ] Class Pointer (to "[I" class)[  4 bytes  ] Length = 5[  4 bytes  ] 0[  4 bytes  ] 0[  4 bytes  ] 0[  4 bytes  ] 0[  4 bytes  ] 0

2. Rectangular Array β€” new int[2][3]

Java
コードをコピー
int[][] rectangular = new int[2][3];

Heap layout:

コードをコピー
0x400: int[][] object (outer array)-----------------------------------[  8 bytes  ] Mark Word[  4 bytes  ] Class Pointer (to "[[I" class)[  4 bytes  ] Length = 2[  4 bytes  ] ref -> 0x500  (rectangular[0])[  4 bytes  ] ref -> 0x600  (rectangular[1])0x500: int[3] object (row 0)----------------------------[  8 bytes  ] Mark Word[  4 bytes  ] Class Pointer (to "[I" class)[  4 bytes  ] Length = 3[  4 bytes  ] 0[  4 bytes  ] 0[  4 bytes  ] 00x600: int[3] object (row 1)----------------------------[  8 bytes  ] Mark Word[  4 bytes  ] Class Pointer (to "[I" class)[  4 bytes  ] Length = 3[  4 bytes  ] 0[  4 bytes  ] 0[  4 bytes  ] 0

Key Observations

  1. Outer array is always an array of references β€” even for int[][].
  2. Inner arrays are separate objects with their own headers and lengths.
  3. Jagged arrays can have different inner array lengths and even null rows.
  4. Rectangular arrays allocate all rows immediately with the same length.
  5. Memory cost: Jagged arrays may save memory if rows are uneven, but cost more if many small arrays are created.

Runnable verification with System.identityHashCode

Java
コードをコピー
public class JVMArrayLayout {    public static void main(String[] args) {        int[][] jagged = new int[2][];        jagged[0] = new int[3];        jagged[1] = new int[5];        int[][] rectangular = new int[2][3];        System.out.println("Jagged outer: " + System.identityHashCode(jagged));        System.out.println("Jagged row0: " + System.identityHashCode(jagged[0]));        System.out.println("Jagged row1: " + System.identityHashCode(jagged[1]));        System.out.println("\nRectangular outer: " + System.identityHashCode(rectangular));        System.out.println("Rectangular row0: " + System.identityHashCode(rectangular[0]));        System.out.println("Rectangular row1: " + System.identityHashCode(rectangular[1]));    }}



let’s use JOL (Java Object Layout) to get the actual JVM memory dump for
new int[2][] (jagged) and new int[2][3] (rectangular) arrays.

This will show real byte offsets, sizes, and headers from your JVM.

1. Add JOL to your project

If you’re using Maven, add:

Xml
コードをコピー
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.17</version></dependency>

If you’re not using Maven, you can download the JAR from Maven Central and run with:

Bash
コードをコピー
java -cp jol-core-0.17.jar:. YourClass

2. Runnable JOL Demo

Java
コードをコピー
import org.openjdk.jol.info.ClassLayout;import org.openjdk.jol.info.GraphLayout;public class JOLArrayDemo {    public static void main(String[] args) {        // Jagged array int[][] jagged = new int[2][];        jagged[0] = new int[3];        jagged[1] = new int[5];        // Rectangular array int[][] rectangular = new int[2][3];        System.out.println("=== Jagged Outer Array Layout ===");        System.out.println(ClassLayout.parseInstance(jagged).toPrintable());        System.out.println("=== Jagged Row 0 Layout ===");        System.out.println(ClassLayout.parseInstance(jagged[0]).toPrintable());        System.out.println("=== Jagged Row 1 Layout ===");        System.out.println(ClassLayout.parseInstance(jagged[1]).toPrintable());        System.out.println("=== Rectangular Outer Array Layout ===");        System.out.println(ClassLayout.parseInstance(rectangular).toPrintable());        System.out.println("=== Rectangular Row 0 Layout ===");        System.out.println(ClassLayout.parseInstance(rectangular[0]).toPrintable());        System.out.println("=== Rectangular Row 1 Layout ===");        System.out.println(ClassLayout.parseInstance(rectangular[1]).toPrintable());        System.out.println("=== Jagged Total Object Graph ===");        System.out.println(GraphLayout.parseInstance(jagged).toPrintable());        System.out.println("=== Rectangular Total Object Graph ===");        System.out.println(GraphLayout.parseInstance(rectangular).toPrintable());    }}

3. Example Output (64-bit JVM, compressed OOPs)

(Your numbers may differ slightly depending on JVM version and settings)

コードをコピー
=== Jagged Outer Array Layout ===[Ljava.lang.Object; object internals: OFFSET  SIZE   TYPE DESCRIPTION      0     8        (object header: mark word)      8     4        (object header: class pointer)     12     4        (array length: 2)     16     4        (element 0: reference to int[3])     20     4        (element 1: reference to int[5])Instance size: 24 bytes...=== Jagged Row 0 Layout ===[I object internals: OFFSET  SIZE   TYPE DESCRIPTION      0     8        (object header: mark word)      8     4        (object header: class pointer)     12     4        (array length: 3)     16     4        (element 0: int)     20     4        (element 1: int)     24     4        (element 2: int)Instance size: 28 bytes...=== Jagged Row 1 Layout ===[I object internals: OFFSET  SIZE   TYPE DESCRIPTION      0     8        (object header: mark word)      8     4        (object header: class pointer)     12     4        (array length: 5)     16     4        (element 0: int)     20     4        (element 1: int)     24     4        (element 2: int)     28     4        (element 3: int)     32     4        (element 4: int)Instance size: 36 bytes...=== Jagged Total Object Graph ===java.lang.Object[]@xxxx footprint:     COUNT       AVG       SUM   DESCRIPTION         1       24        24   [[I         1       28        28   [I (length 3)         1       36        36   [I (length 5)Total: 3 objects, 88 bytes=== Rectangular Total Object Graph ===java.lang.Object[]@yyyy footprint:     COUNT       AVG       SUM   DESCRIPTION         1       24        24   [[I         2       28        56   [I (length 3)Total: 3 objects, 80 bytes

4. What this proves

  • Outer array is always an array of references ([[I).
  • Inner arrays are separate objects ([I), each with its own header and length.
  • Jagged: inner arrays can have different lengths β†’ different sizes in bytes.
  • Rectangular: all inner arrays have the same length β†’ same size in bytes.
  • Memory footprint is slightly smaller for rectangular arrays when all rows are equal length.



let’s finish the deep dive and dump the actual heap addresses of the array objects using JOL’s GraphLayout.
This will let you see exactly where in memory the JVM has placed each object.

1. Updated JOL Demo with Addresses

Java
コードをコピー
import org.openjdk.jol.info.ClassLayout;import org.openjdk.jol.info.GraphLayout;public class JOLArrayAddresses {    public static void main(String[] args) {        // Jagged array int[][] jagged = new int[2][];        jagged[0] = new int[3];        jagged[1] = new int[5];        // Rectangular array int[][] rectangular = new int[2][3];        // Print layouts        System.out.println("=== Jagged Outer Array Layout ===");        System.out.println(ClassLayout.parseInstance(jagged).toPrintable());        System.out.println("=== Jagged Row 0 Layout ===");        System.out.println(ClassLayout.parseInstance(jagged[0]).toPrintable());        System.out.println("=== Jagged Row 1 Layout ===");        System.out.println(ClassLayout.parseInstance(jagged[1]).toPrintable());        System.out.println("=== Rectangular Outer Array Layout ===");        System.out.println(ClassLayout.parseInstance(rectangular).toPrintable());        System.out.println("=== Rectangular Row 0 Layout ===");        System.out.println(ClassLayout.parseInstance(rectangular[0]).toPrintable());        System.out.println("=== Rectangular Row 1 Layout ===");        System.out.println(ClassLayout.parseInstance(rectangular[1]).toPrintable());        // Print object graph with addresses        System.out.println("=== Jagged Object Graph with Addresses ===");        System.out.println(GraphLayout.parseInstance(jagged).toPrintable());        System.out.println("=== Rectangular Object Graph with Addresses ===");        System.out.println(GraphLayout.parseInstance(rectangular).toPrintable());        // Raw footprint summary        System.out.println("=== Jagged Footprint Summary ===");        System.out.println(GraphLayout.parseInstance(jagged).toFootprint());        System.out.println("=== Rectangular Footprint Summary ===");        System.out.println(GraphLayout.parseInstance(rectangular).toFootprint());    }}

2. Example Output (64-bit JVM, compressed OOPs)

(Your addresses will differ β€” these are just an example)

コードをコピー
=== Jagged Object Graph with Addresses ===java.lang.Object[] @ 0x00000007c006c010  [[Ijava.lang.Object[] @ 0x00000007c006c010  size: 24    0x00000007c006c028: [I @ 0x00000007c006c040    0x00000007c006c02c: [I @ 0x00000007c006c060[I @ 0x00000007c006c040  size: 28  length: 3[I @ 0x00000007c006c060  size: 36  length: 5=== Rectangular Object Graph with Addresses ===java.lang.Object[] @ 0x00000007c006c080  [[Ijava.lang.Object[] @ 0x00000007c006c080  size: 24    0x00000007c006c098: [I @ 0x00000007c006c0b0    0x00000007c006c09c: [I @ 0x00000007c006c0d0[I @ 0x00000007c006c0b0  size: 28  length: 3[I @ 0x00000007c006c0d0  size: 28  length: 3=== Jagged Footprint Summary ===COUNT       AVG       SUM   DESCRIPTION    1       24        24   [[I    1       28        28   [I (length 3)    1       36        36   [I (length 5)Total: 3 objects, 88 bytes=== Rectangular Footprint Summary ===COUNT       AVG       SUM   DESCRIPTION    1       24        24   [[I    2       28        56   [I (length 3)Total: 3 objects, 80 bytes

3. What This Shows

  • Real heap addresses (e.g., 0x00000007c006c010) for each object.
  • Outer array points to inner arrays via references stored at specific offsets.
  • Jagged: inner arrays have different sizes and addresses.
  • Rectangular: inner arrays have the same size but still separate addresses.
  • Footprint confirms total memory usage.

If you run this on your machine, you’ll see exact addresses for your JVM session.

These addresses will change between runs because of GC and heap allocation order.

let’s take the real JOL addresses from the dump and turn them into a visual pointer diagram so you can see exactly how the JVM links these objects in memory.

I’ll use the example addresses from the previous run β€” your actual run will have different ones.

1. Jagged Array β€” new int[2][]

Example JOL output addresses:

コードをコピー
Outer array (int[][])   @ 0x00000007c006c010Row 0 (int[3])          @ 0x00000007c006c040Row 1 (int[5])          @ 0x00000007c006c060

Pointer diagram:

コードをコピー
0x00000007c006c010  (int[][] outer array, length=2) β”œβ”€β”€ element[0] β†’ 0x00000007c006c040  (int[3]) β”‚       [0, 0, 0] β”‚ └── element[1] β†’ 0x00000007c006c060  (int[5])         [0, 0, 0, 0, 0]

Outer array holds two 4-byte references.
Each reference points to a different int[] object in the heap.
Row lengths differ (3 and 5).

2. Rectangular Array β€” new int[2][3]

Example JOL output addresses:

コードをコピー
Outer array (int[][])   @ 0x00000007c006c080Row 0 (int[3])          @ 0x00000007c006c0b0Row 1 (int[3])          @ 0x00000007c006c0d0

Pointer diagram:

コードをコピー
0x00000007c006c080  (int[][] outer array, length=2) β”œβ”€β”€ element[0] β†’ 0x00000007c006c0b0  (int[3]) β”‚       [0, 0, 0] β”‚ └── element[1] β†’ 0x00000007c006c0d0  (int[3])         [0, 0, 0]

Outer array holds two references.
Both rows have the same length (3).
Still separate objects in memory with different addresses.

3. Key Takeaways from the Diagram

  • Even in a rectangular array, each row is a separate object with its own address.
  • Jagged arrays can have rows of different lengths and sizes.
  • The outer array is just an array of references β€” it doesn’t store the actual integers.
  • The integers are stored inside the inner arrays in contiguous memory for that row.

4. How to Generate Your Own Diagram

  1. Run the JOL program from the previous step.
  2. Copy the addresses from the GraphLayout.parseInstance(...).toPrintable() output.
  3. Draw arrows from the outer array address to each inner array address.
  4. Annotate each inner array with its length and contents.



let’s put both jagged and rectangular arrays side-by-side in one combined pointer diagram using the example addresses from our earlier JOL run.

This will make it crystal clear how the JVM stores them differently in the heap.

Combined Heap Pointer Diagram

コードをコピー
───────────────────────────────  Jagged Array  (new int[2][])Outer array @ 0x00000007c006c010  (int[][], length=2) β”œβ”€β”€ element[0] β†’ 0x00000007c006c040  (int[3]) β”‚       [0, 0, 0] β”‚ └── element[1] β†’ 0x00000007c006c060  (int[5])         [0, 0, 0, 0, 0]───────────────────────────────  Rectangular Array  (new int[2][3])Outer array @ 0x00000007c006c080  (int[][], length=2) β”œβ”€β”€ element[0] β†’ 0x00000007c006c0b0  (int[3]) β”‚       [0, 0, 0] β”‚ └── element[1] β†’ 0x00000007c006c0d0  (int[3])         [0, 0, 0]

What This Shows

  • Outer arrays (int[][]) are just arrays of references.
  • Jagged:
    • Row lengths can differ (3 and 5 here).
    • Inner arrays have different sizes in memory.
    • Still separate objects with unique addresses.
  • Rectangular:
    • All rows have the same length (3).
    • Still separate objects with unique addresses β€” no shared memory for rows.
  • Memory locality:
    • Even though rectangular arrays are β€œuniform,” the rows are not stored contiguously with the outer array β€” they are separate heap objects.

Why This Matters

  • Performance: Rectangular arrays may have slightly better cache locality if row lengths are equal, but both forms require pointer dereferencing to reach elements.
  • Flexibility: Jagged arrays allow different row lengths, which can save memory if data is uneven.
  • Null safety: Jagged arrays can have null rows if not initialized, rectangular arrays cannot.



← Back to Learning Journey