Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do final vals increase the object size?

class Foo {
  final val pi = 3
}

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

like image 824
fredoverflow Avatar asked Jul 12 '13 13:07

fredoverflow


2 Answers

If you are concerned about the memory footprint, you might consider moving this field into the companion object.

Yes, every instance of class Foo will have the pi value -- the Scala compiler will not eliminate this declaration. The JVM reflection allows you to remove final modifiers on class members, and the Unsafe object even allows modifying these. So -- the Scala compiler could produce code with surprising results by removing 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) might even remove final modifiers on members under-the-hood, so something that feels final in Scala code might not be final on the bytecode level.

This:

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 accessor method (i.e. its getter) is no longer final.

And neither will the JIT in the Oracle JVM remove this member from the object representation in memory at runtime - the runtime size of the Foo object on a 32-bit JVM will be 16 bytes (8 bytes object header + 4 bytes for an integer field, rounded to an 8 byte boundary). The JIT might, however, decide to inline the constant value from the final field into parts of the code, so that some field writes are eliminated.

like image 114
axel22 Avatar answered Nov 19 '22 01:11

axel22


Not only will every instance have a field pi, it will have value zero.

pi is a constant value definition. The "accessor" just returns the constant.

This can cause problems under conditions of separate compilation and inlining, 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, upon reflection:

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
like image 25
som-snytt Avatar answered Nov 19 '22 00:11

som-snytt