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
aandbreference the same array object. - Changing
b[0]changesa[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 ] |
+-----+ +-------------------+matrixis 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:
aandbhave the same identity hash code β they reference the same array object.chas a different identity hash code β itβs a different array object.- Changing
balso changesa, but notc.
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:
Shallow clone of a 2D array:
- Outer array is new.
- Inner arrays are shared references.
- Changing an inner array in one affects the other.
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
nulland must be assigned later.
How it works
Step-by-step:
new int[2][]- Creates an array of length
2where each element is of typeint[](array of integers). - At this point, both elements are
nullbecause the second dimension is not initialized.
- Creates an array of length
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.lengthis2.arr[0].lengthis3.arr[1].lengthis5.
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]andarr[1]arenull.
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 5Now:
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, 3hereβ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: 3go 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: 356573597Different hash codes = different objects in heap.
Confirms that each row is its own object.
go all the way down into how the JVM actually storesnew 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 ] 02. 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 ] 0Key Observations
- Outer array is always an array of references β even for
int[][]. - Inner arrays are separate objects with their own headers and lengths.
- Jagged arrays can have different inner array lengths and even
nullrows. - Rectangular arrays allocate all rows immediately with the same length.
- 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 fornew 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:. YourClass2. 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 bytes4. 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 bytes3. 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]) @ 0x00000007c006c060Pointer 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]) @ 0x00000007c006c0d0Pointer 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
- Run the JOL program from the previous step.
- Copy the addresses from the
GraphLayout.parseInstance(...).toPrintable()output. - Draw arrows from the outer array address to each inner array address.
- 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
nullrows if not initialized, rectangular arrays cannot.