Memory layout: Go, Rust, C and Java
Paste a Go struct, Rust struct or enum, C struct or Java class and see every byte — fields, padding, object headers, enum tags and niches — on 64-bit and 32-bit targets, with Go's fieldalignment reordering, rustc's own field order, HotSpot's field packing, and a gallery of runtime internals (slice and interface headers, Vec, Box, Rc, fat pointers, String, HashMap.Node).
Added
How to use it
Paste type definitions on the left; each type is drawn byte by byte on the right, eight bytes to a row (four on 32-bit targets). Coloured boxes are fields, hatched boxes are padding, grey is an object header. The sizes table shows every type on every target at once, so you can see what changes on a 32-bit build without switching.
The rules are each compiler’s own, not a generic "align everything" approximation, and I checked them against the real compilers (see What it was checked against below).
How to use it
-
Pick the language, then the target. Paste one or more types, or open Examples and internals.
-
Hover a row of the field table to find that field in the drawing. Nested structs are drawn field by field (
inner.x); untick Show fields of nested structs to draw them as one block. -
Share link copies a link with your code in it.
Go
Go never reorders fields: they go in the order you wrote them, each aligned to its own size (8-byte integers to 4 bytes on 32-bit targets). A struct whose last field takes no space gets one byte of padding, so a pointer to that field can’t point past the object.
When a struct could be smaller, the card says so in the words of the fieldalignment analyser
(struct of size 88 could be 80), shows the order it would choose, and Reorder in the editor
moves your own lines (tags and comments included). The analyser also reports pointer bytes: the
length of the prefix of the struct the garbage collector has to scan. Putting pointer fields first
shortens it even when the size doesn’t change.
Known library types: sync.Mutex, RWMutex, WaitGroup, Once, the sync/atomic types,
time.Time, time.Duration, context.Context, error, strings.Builder, bytes.Buffer.
atomic.Int64 and atomic.Uint64 are 8-byte aligned even on 32-bit targets, as the compiler
guarantees for them; a plain int64 there is aligned to 4.
Rust
Without a [repr], rustc may reorder fields, and this follows what it does today: fields are
grouped by alignment, largest first, and a field with a niche (bit patterns it can never hold —
bool, char, references, Box, NonZero*, a Vec’s capacity) is moved towards the start or end
so an enclosing enum can use it. The card marks a reordered struct and gives the size it would have
as `[repr©]. The default layout is not a stable promise; it is what rustc 1.99 does.
Enums are drawn one row per variant:
-
Tagged: a tag says which variant is present. rustc widens the tag to fill the space before the first field, so
enum { A(u32), B(u64) }has a 4-byte tag. -
Niche: no tag at all.
Option<&T>,Option<Box<T>>,Option<NonZeroU32>are the size of the payload, withNonestored as 0.Option<bool>storesNoneas 2,Option<Vec<T>>as a capacity of 263. Variants with data can sit in the bytes around the niche, which is whyenum { Quit, Move { x: i32, y: i32 }, Write(String) }is 24 bytes.
[repr©], [repr(packed)], packed(N), align(N), transparent and #[repr(u8)]-style enums
are honoured. Known std types: Vec, String, Box, Rc, Arc, Cell, RefCell, Mutex
(Linux futex layout; macOS boxes a pthread mutex and it is 24 bytes), RwLock, Option, Result,
Cow, HashMap, HashSet, BTreeMap, VecDeque, Duration, PhantomData, NonNull,
NonZero*, atomics, references, raw pointers, slices, str, dyn Trait and function pointers.
Generic structs are laid out where they are used, for example Pair<u8, u64>.
C
System V layout on Linux (x86-64, i386, 32-bit ARM) and the Microsoft rules for 64-bit Windows,
including bit-fields: GCC/Clang pack a bit-field into the current storage unit if it fits; MSVC
starts a new unit whenever the declared type changes. attributepacked,
attributealigned(N), _Alignas, #pragma pack(push, N), unions, anonymous members,
flexible array members, function pointers and simple #define constants are understood.
long is 8 bytes on 64-bit Linux and 4 on Windows; long double is 16 bytes on x86-64 Linux, 12
on i386 and 8 on ARM and Windows.
Java
Objects are laid out the way HotSpot has done it since JDK 15: an object header, then primitive
fields from largest to smallest, then references, each put in the smallest hole that fits — which is
why an int or a reference often lands at offset 12, in the gap after a 12-byte header. A subclass’s
fields can fill holes left in its superclass. Static fields aren’t in the object at all.
The JVM settings change the numbers:
-
JDK 17–23 default (heap under 32 GB): 12-byte header, 4-byte references (compressed oops and class pointers).
-
-XX:-UseCompressedOops(heap of 32 GB or more): 12-byte header, 8-byte references; the class pointer stays compressed. -
-XX:-UseCompressedClassPointers: 16-byte header, 4-byte references. -
JDK 27 default, compact object headers: 8-byte header, 4-byte references, so
new Object()is 8 bytes. JEP 450 added them as an experiment in JDK 24 and JEP 519 made them a product option in JDK 25.
In JDK 27 one more rule shows up: when the superclass’s last field is a reference, the subclass’s references come first, so all the references sit together. The Java arrays panel gives an array’s size for any element type and length on each setting.
The record, enum and nested-class forms are read too; methods, constructors and initialisers are
skipped.
What it was checked against
Every case in tests/tools/memory-layout.test.js was compared with the real tool, offset by offset:
-
Go 1.23.3:
go/typessizes for amd64 and 386 (the same rules as the compiler) andunsafe.Sizeof/Alignofin a compiled program; the reorder messages againstfieldalignmentv0.28.0. -
Rust 1.99.0:
-Zprint-type-sizes(struct fields and every enum variant’s fields) for aarch64-apple-darwin, aarch64-unknown-linux-gnu, i686-unknown-linux-gnu and armv7-unknown-linux-gnueabihf. x86-64 lays out the same as aarch64. -
C: Clang 21’s record layout dump for x86_64-linux-gnu, i386-linux-gnu, armv7-linux-gnueabihf and x86_64-windows-msvc, bit-field positions included.
-
Java: JOL 0.17 on OpenJDK 21 (default, without compressed oops, without compressed class pointers), Corretto 23 and OpenJDK 27 (default, and with compact headers turned off), including array base offsets.
Not checked: the 32-bit JVM (its x86 port was removed in JDK 25), and Go’s layout of the runtime’s
own structs beyond Go 1.23 (Go 1.24 replaced the map with Swiss tables; the gallery shows the 1.23
hmap).
Related tools
-
Concurrency lab — step through goroutines, async Rust, virtual threads and the JavaScript event loop.
-
Binary formats — decode bytes on the wire (MessagePack, CBOR, protobuf).
Feedback is a gift. Something to improve, a feature to add, or a bug? Tell me.
Runs entirely in your browser; nothing you type leaves this page.