RFR: 8060130: Simplify the synchronization of defining and getting java.lang.Package

David Holmes david.holmes at oracle.com
Mon Oct 13 02:18:29 UTC 2014

Hi Claes,

Looking at version three ...

You seemed to have changed the caching strategy for Packages in the 
classloader. Previously a Package defined by a parent or the system 
would be updated in the current loader's Package map; but now you leave 
them separate so future lookups will always have to traverse the 
hierarchy. This doesn't seem like a performance gain.

Looking at definePackage it seems both old and new code have serious 
race conditions due to a lack of atomicity when checking the 
parent/system packages. A package of the same name could be defined in 
the parent/system after definePackage has called getPackage - and we 
then end up with two same named Packages in different loaders.

No comment on the manifest caching aspect - I'm not familiar enough with 
the existing code.

On 12/10/2014 12:09 PM, Claes Redestad wrote:
> On 2014-10-11 02:31, Mandy Chung wrote:
>> On 10/10/2014 8:10 AM, Claes Redestad wrote:
>>> Hi all,
>>> please review this patch which attempts to clean up synchronization
>>> and improve scalability when
>>> defining and getting java.lang.Package objects.
>> I agree with David that getting Package objects are not performance
>> critical. On the other hand, the code defining/getting Packages is
>> old and deserves some cleanup especially the synchronization part.
>> If you run helloworld program, how does that change the list of loaded
>> classes besides the new CachedManifest class?
>>> webrev: http://cr.openjdk.java.net/~redestad/8060130/webrev.02/
>> ClassLoader.java
>> line 272: can you change the declared type as Map.
> Map misses the atomicity requirement of putIfAbsent, ConcurrentMap is OK
> but leaves some
> related questions open (why we can't add a null value, specifically).
> I'm glad it was brought up
> and discussed and will use ConcurrentHashMap for private fields unless
> there's a strong
> preference otherwise.
>> definePackage throws IAE if there exists an existing package either
>> in this class loader or one of its ancestors.
>>   - this change would not catch if two definePackage calls to define
>>   a package of the same name but with different spec version, title, etc
>>   concurrently.
>>   You may not be able to avoid synchronizing on packages for this case.
> Right, I was I think synchronization can still be avoided by throwing
> IAE if
> putIfAbsent doesn't return null:
>          if (packages.putIfAbsent(name, pkg) != null) {
>              throw new IllegalArgumentException(name);
>          }
>          return pkg;
>> move line 1623 to 1630 so that the declaration of map is closer to
>> the assignment.
> Ok
>> Package.java
>> line 557 there is a possibility that new Package[pkgs.size()] is not
>> big enough and a new array would be created.  As this method is not
>> popularly used, it's okay if another array is created.
> Yes, an unlikely race.
>> line 563 and 565 can be merged
>> line 570-575: do you think you can modify the private
>>   Package(String name, Manifest man, URL url, ClassLoader loader)
>> constructor
>> to take null Manifest and null url so that these lines can be folded into
>> pkg = new Package(name,  cachedManifest.getManifest(),
>> cachedManifest.getURL(), null);
> I think I'll take your suggestion below and ensure cachedManifest and
> it's getManifest()
> never evaluate to null, which makes for a cleaner patch. There is some
> code duplication
> here with URLClassLoader#definePackage. Future cleanup?
> It would seem the ClassLoader argument in this ctor is always called
> with null. Remove?
>> I think CachedManifest class and the createCachedManifest method need
>> some work.  Perhaps we can have the CachedManifest constructor to
>> obtain the URL.
>> Each invalid fn will have one instance instead of NO_MANIFEST singleton
>> but that should not happen as fn is the filename where the classes
>> loaded from the bootclasspath.  CachedManifest.url can then become final.
>> line 587-601 would not be needed. Can we avoid line 606 and write
>> the createCachedManifest method this way:
>>    if (!manifests.containsKey(fn)) {
>>        manifests.putIfAbsent(fn, new CachedManifest(fn));
>>    }
>>    return manifests.get(fn);
> Yes. Looked a bit dangerous, but it seems we still maintain the
> necessary guarantees.
>> You may be able to further simplify CachedManifest and remove the
>> resolved
>> field by storing an empty Manifest when loadManifest returns null.
>> That may help the private Package constructor not require any change
>> to merge line 570-575 as my comment noted above.
> Sure! Taking in all these suggestions as well as realizing a race could
> cause different Package
> to return from subsequent calls to Package.defineSystemPackage brings me
> to this:
> http://cr.openjdk.java.net/~redestad/8060130/webrev.03/
> Now... How about a slightly alternative approach: instead of caching
> Manifests we could create
> and cache a Package - call it a prototype - then add a private
> constructor taking the
> package name and the "prototype" Package. The Package objects should
> come with a
> smaller footprint and have the added benefit of being effectively
> immutable. Does that
> sound like an improvement?
>> You will need to check there is any test to verify Package created with
>> and without manifest.  Do you mind making this change and tests (I
>> realize
>> it might be out of scope of this performance improvement you initially
>> anticipated)?
> I'll take a look at the current test coverage and give it some thought.
> Thanks!
> /Claes
>> Mandy

More information about the core-libs-dev mailing list