private static ClassMetadata getClassMetadata (final Class cls,
                                                   final Map /* Class->ClassMetadata */ metadataMap,
                                                   final ClassAccessPrivilegedAction caAction,
                                                   final FieldAccessPrivilegedAction faAction)
    {
        if (cls == null) return null; 
        
        ClassMetadata result;
        synchronized (metadataMap)
        {
            result = (ClassMetadata) metadataMap.get (cls);
        }
        if (result != null) return result;

        int primitiveFieldCount = 0;
        int shellSize = OBJECT_SHELL_SIZE; // java.lang.Object shell
        final List /* Field */ refFields = new LinkedList ();
        
        final Field [] declaredFields;
        try
        {
            caAction.setContext (cls);
            declaredFields = (Field []) AccessController.doPrivileged (caAction);
        }
        catch (PrivilegedActionException pae)
        {
            throw new RuntimeException ("could not access declared fields of class " + cls.getName () + ": " + pae.getException ());
        }
    
        for (int f = 0; f < declaredFields.length; ++ f)
        {
            final Field field = declaredFields [f];
            if ((Modifier.STATIC & field.getModifiers ()) != 0) continue;

            final Class fieldType = field.getType ();
            if (fieldType.isPrimitive ())
            {
                // memory alignment ignored:
                shellSize += sizeofPrimitiveType (fieldType);
                ++ primitiveFieldCount;
            }
            else
            {
                // prepare for graph traversal later:
                if (! field.isAccessible ())
                {
                    try
                    {
                        faAction.setContext (field);
                        AccessController.doPrivileged (faAction);
                    }
                    catch (PrivilegedActionException pae)
                    {
                        throw new RuntimeException ("could not make field " + field + " accessible: " + pae.getException ());
                    }
                }
                
                // memory alignment ignored:
                shellSize += OBJREF_SIZE;
                refFields.add (field);
            }
        }
        
        // recurse into superclass:
        final ClassMetadata superMetadata = getClassMetadata (cls.getSuperclass (), metadataMap, caAction, faAction);
        if (superMetadata != null)
        {
            primitiveFieldCount += superMetadata.m_primitiveFieldCount;
            shellSize += superMetadata.m_shellSize - OBJECT_SHELL_SIZE;
            refFields.addAll (Arrays.asList (superMetadata.m_refFields));
        }
        
        final Field [] _refFields = new Field [refFields.size ()];
        refFields.toArray (_refFields);
        
        result = new ClassMetadata (primitiveFieldCount, shellSize, _refFields);
        synchronized (metadataMap)
        {
            metadataMap.put (cls, result);
        }
        
        return result;        
    }