JDK-6379948 : Need mechanism for implementing serializable classes with final fields
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.io:serialization
  • Affected Version: 6
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2006-02-01
  • Updated: 2018-09-11
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
The Java Memory Model, as reworked for Tiger,
gives preferential treatment to final fields.
In particular, immutable objects with final fields do not need synchronization.
However, how does one deserialize such objects, if the final field must be 
assigned to in the deserialization method?

In the JDK, we can work around this thus:
(from CopyOnWriteArrayList.java)

    // Support for resetting lock while deserializing
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long lockOffset;
    static {
        try {
            lockOffset = unsafe.objectFieldOffset
                (CopyOnWriteArrayList.class.getDeclaredField("lock"));
            } catch (Exception ex) { throw new Error(ex); }
    }
    private void resetLock() {
        unsafe.putObjectVolatile(this, lockOffset, new ReentrantLock());
    }

but.... this technique is strange, not very well known, and uses
JDK internal mechanisms.  What are 3rd party class authors to do?

Brian Goetz writes:

"Can we explore ways to fix this for Dolphin, even if it does require 
proposing language changes?  Final got so much more important in Tiger, 
that the requirement to choose between using final and using 
serialization is really an unfair choice to present user with.  Telling 
the user base "we can write immutable serializable classes, but you 
can't" is just mean."

Comments
WORK AROUND - For JDK-internal classes, use sun.misc.Unsafe.put*Volatile to set final instance fields during the execution of serializable classes' readObject methods, as mentioned in this RFE's Description. This is clearly not a general workaround, but I mention it here to reaffirm that it remains the current recommended workaround for JDK-internal classes. - For classes outside the JDK, if the author is certain that they will only be deployed in ways such that they are granted java.security.AllPermission (i.e. that they will be highly trusted), another workaround is to use the standard reflection API. It is possible (since 5.0) to set final instance fields reflectively after invoking setAccessible(true) on the Field object, and if such a reflective setting is done while executing a readObject (or readObjectNoData) method, the deserialized object should be safe from a memory model perspective. Here's a transformation of the example in the Description to use this reflective technique: [Note: this code has not actually been tested or even compiled.] // Support for resetting lock while deserializing private static final Field lockField; static { try { lockField = AccessController.doPrivileged( new PrivilegedExceptionAction<Field>() { public Field run() throws NoSuchFieldException { Field f = CopyOnWriteArrayList.class.getDeclaredField("lock"); f.setAccessible(true); return f; } }); } catch (PrivilegedActionException e) { throw new AssertionError(e.getCause()); } } private void resetLock() { f.set(this, new ReentrantLock()); } The extreme permission requirement of this workaround should not be dismissed lightly; it might be appropriate for certain application classes for which the author is certain that they will always be granted AllPermission (or executed with no security manager), and it might be appropriate for certain library classes that are part of a library that otherwise effectively requires the complete trust of AllPermission anyway-- but it will not work for general classes that might possibly be executed with limited permission grants. - Another alternative applicable to some serializable classes is to declare a readResolve method that constructs a new instance of the class to be the real product of deserialization, using normal constructors that can then initialize final instance fields correctly. Using a readResolve method tends to be something more for consideration when originally designing the serialization behavior/contract of a class, and it involves other considerations too-- including limitations, like that a readResolve method is generally not appropriate for arbitrarily-subclassable classes, or for classes of objects that might legitimately refer to themselves indirectly through a cycle of references in a serialized object graph. (See Item 57 of "Effective Java" by Josh Bloch for a discussion of motivations and considerations for declaring a readResolve method.)
03-02-2006

EVALUATION [I had previously suggested, as part of the evaluation for 6252102, that it be the CR used to represent this problem-- but perhaps that was too much of a contortion of its original issue (confusion about how "transient", "final", and constant initializers affect apparent serialization behavior) and it would in fact be better to use this new RFE for the.final field initialization problem. Borrowing words from the 6252102 evaluation:] It is indeed problematic to initialize some or all of a serializable class's final instance fields upon deserialization: - For serializable classes that do not declare an appropriate readObject method or whose readObject method invokes ObjectInputStream.defaultReadObject: the problem applies to final instance fields other than the class's serializable fields-- i.e., its final instance fields declared transient or, if the class declares an appropriate "serialPersistentFields", final instance fields not mentioned in the serialPersistentFields array. - For serializable classes whose readObject method uses the ObjectInputStream.readFields/GetField API to read and set serializable field values programmatically from the stream values (like to conform to an earlier version's serialized form after the class's private field types and/or names have been changed): the problem applies to all final instance fields. No language-level constructor gets invoked for a serializable (but not Externalizable-- see tangent below) class upon deserialization (and this includes instance field initializers too), so instance fields not set by default serialization need to be set by the class's custom readObject (or readObjectNoData) method which, if present, does get invoked upon deserialization instead of a constructor. A readObject method, however, is not allowed (by the JLS) to initialize blank final fields like a real constructor can: hence the problem. (If a final instance field has a constant initializer, the problem may be largely masked because corresponding field access expressions will be inlined by the compiler to the constant value regardless of the internal state of the deserialized object...) [There is the case of serializable classes that also implement java.io.Externalizable; Externalizable.readExternal implementations cannot set final instance fields either, meaning that fields corresponding to an Externalizable instance's externally-represented state cannot be final-- but this is not considered to be such a problem, because the Externalizable model involves an object's state getting set from an external representation through invocation of a mutating public method after completion of its normal construction. Also, instance fields not corresponding to the external representation can actually be final, because normal constructors are invoked for an Externalizable object.] A solution to this problem must not be limited to supporting deserialization only through the Java platform's java.io.ObjectInputStream implementation, because then it wouldn't support ObjectInputStream subclasses that attempt to implement standard Java serialization semantics with a different "wire format", like CORBA RMI-IIOP implementations use. (A solution to this problem that required serializable classes to give up RMI-IIOP support in order to use it would seem to be a non-starter.) It seems that serialization is the primary motivation for a solution to this problem, but the problem itself has at least slightly broader applicability: as I understand it, the same problem applies to classes that override Object.clone in a way that it delegates to java.lang.Object's magic superclass implementation, which also instantiates a class without normal constructor invocations-- so if a clonable class wants a value for a cloned final instance field to be different from the value provided by the default shallow copy, it also has a problem setting the field. In fact, the workaround for CopyOnWriteArrayList quoted in this RFE's Description is used to implement that class's public clone() method as well as deserialization. For various reasons (including the applicability to clone methods), it seems unlikely that the ultimate solution mechanism would be part of the serialization API proper, and thus that this RFE will ultimately remain in the java/serialization category, but it seems reasonable to leave it here for now for the sake of discussion.
03-02-2006