Do final vals increase the object size?
Asked Answered
P

2

7
class Foo {
  final val pi = 3
}

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

Photoelectrotype answered 12/7, 2013 at 13:21 Comment(0)
K
4

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.

Kennard answered 12/7, 2013 at 13:42 Comment(0)
C
3

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
Creasy answered 12/7, 2013 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.