Do final vals make object size larger?

class Foo { final val pi = 3 } 

Does every Foo object have a pi member? Should I put pi in a companion object?

+7
scala constants companion-object final compile-time-constant
source share
2 answers

If you are concerned about the amount of memory, you can move this field to a companion object.

Yes, every instance of the Foo class will have a pi value - the Scala compiler will not delete this declaration. JVM reflection allows you to remove the final modifiers for class members, and the Unsafe object even allows you to modify them. Thus, the Scala compiler can get code with unexpected results by deleting this field, so this optimization is not applied.

 ... minor version: 0 major version: 50 flags: ACC_PUBLIC, ACC_SUPER ... { private final int pi; flags: ACC_PRIVATE, ACC_FINAL public final int pi(); flags: ACC_PUBLIC, ACC_FINAL LineNumberTable: line 243: 0 LocalVariableTable: Start Length Slot Name Signature ... 

In fact, some compiler transformations (e.g., specialization) may even remove the final modifiers for members under the hood, so something that feels final in Scala code may not be final at the bytecode level.

It:

 class Foo[@specialized T] { final val pi: T = null.asInstanceOf[T] } 

becomes:

  ... public final T pi; flags: ACC_PUBLIC, ACC_FINAL Signature: #9 // TT; public T pi(); flags: ACC_PUBLIC LineNumberTable: line 243: 0 ... 

Above, the pi access method (i.e., its recipient) is no longer final.

And none of the JITs in the Oracle JVM removes this member from the representation of the object in memory at run time - the size of the runtime of the Foo object on the 32-bit JVM will be 16 bytes (8 bytes of the object header + 4 bytes for an integer field, rounded to the border of 8 bytes). However, the JIT may decide to include a constant value from the final field in the code part, so that some field entries will be eliminated.

+4
source share

Not only does each instance have a pi field, it will have a null value.

pi is a constant value definition. "Accessor" just returns a constant.

This can cause problems with separate compilation and insertion if you try hard enough.

 { private final int pi; flags: ACC_PRIVATE, ACC_FINAL public final int pi(); flags: ACC_PUBLIC, ACC_FINAL Code: stack=1, locals=1, args_size=1 0: iconst_3 1: ireturn LocalVariableTable: Start Length Slot Name Signature 0 2 0 this LFoo; LineNumberTable: line 8: 0 public Foo(); flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #14 // Method java/lang/Object."<init>":()V 4: return LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LFoo; LineNumberTable: line 13: 0 } 

Just to convince myself by thinking:

 scala> res5.tail res16: Iterable[reflect.runtime.universe.Symbol] = List(value pi) scala> res5.last.asTerm.isAccessor res18: Boolean = false scala> res5.head.asTerm.isAccessor res19: Boolean = true scala> res0 reflectField res5.last.asTerm res21: reflect.runtime.universe.FieldMirror = field mirror for Foo.pi (bound to Foo@2907f26d ) scala> res21.get res22: Any = 0 
+3
source share

All Articles