GC压力场景下:class与struct、List与Array的垃圾回收差异探讨
Great question—let's break this down clearly from a garbage collection (GC) perspective, since these choices can have tangible impacts on how your runtime manages memory and performance.
Class vs Struct: GC Behavior Differences
The core distinction here boils down to value types (structs) vs reference types (classes), which directly affects how memory is allocated and cleaned up:
- Memory location & automatic cleanup: Structs are value types. If they're declared as local variables, they live on the stack and are automatically cleaned up when the method's stack frame is destroyed—no GC involvement needed. Classes are reference types, so all their instances live on the managed heap. Every time you discard a class instance (by losing all references to it), it becomes garbage that the GC has to track, mark, and clean up later, adding to GC pressure.
- Boxing overhead: If you ever cast a struct to
objector an interface type, it gets "boxed"—the runtime creates a wrapper object on the heap to hold the struct's value. This boxed object is now garbage once it's no longer referenced, creating extra work for the GC. Classes never require boxing, so they avoid this overhead entirely. - Collection element replacement: When you replace an element in a collection (like swapping out an old value for a new one):
- For
List<MyStruct>orMyStruct[], you're just overwriting the existing value in memory—no garbage is created. - For
List<MyClass>orMyClass[], you're replacing a reference to a heap instance. If the old instance has no other references, it becomes garbage that the GC will need to collect.
- For
List vs Array: GC Behavior Differences
Both use underlying arrays, but their dynamic vs fixed-size nature creates GC-related tradeoffs:
- Resizing-induced garbage: Lists are dynamic—when you add elements beyond their current capacity, they automatically allocate a new, larger underlying array (usually double the size) and copy existing elements over. The old, smaller array is then abandoned and becomes garbage, forcing the GC to clean it up. Arrays are fixed-size, so no automatic resizing happens—you only generate garbage if you manually create a new array and copy elements over yourself.
- Fragmentation risks: Frequent List resizing can lead to more heap fragmentation. Each new larger array takes up a bigger contiguous block of heap memory, leaving the old smaller blocks unused until GC compacts the heap. Fixed-size arrays, especially if allocated with a known size upfront, are less likely to contribute to fragmentation since they occupy a single, stable block.
- Element handling parity: For both Lists and Arrays, the GC behavior for element cleanup is identical once the collection is sized:
- Value type (struct) elements don't generate garbage when replaced.
- Reference type (class) elements generate garbage when replaced (if the old instance has no other references).
Quick Scenario Tips
- If you're dealing with small, short-lived objects and want to minimize GC pressure, go with structs (avoid boxing!) and fixed-size arrays if you know the element count upfront.
- If you need dynamic sizing, use a List but set its
Capacityupfront to match your maximum N—this avoids repeated resizing and the garbage that comes with it. - For large, long-lived objects that need multiple references, classes are the way to go (structs would be inefficient to copy around, and boxing would negate any GC benefits).
内容的提问来源于stack exchange,提问作者Bogey
相关产品推荐
相关产品推荐

