Interface SharedClassURLClasspathHelper

All Superinterfaces:
SharedClassHelper, SharedHelper

public interface SharedClassURLClasspathHelper extends SharedClassHelper

SharedClassHelper API that stores and finds classes by using URL classpaths.

Description

A SharedClassURLClasspathHelper is obtained by calling getURLClasspathHelper(ClassLoader, URL[]) on a SharedClassHelperFactory.

The SharedClassURLClasspathHelper is designed for ClassLoaders that load classes using a classpath comprised of a sequence of URLs. It is assumed that classpaths are searched left-most to right-most and can only be modified in predictable ways as described in Usage.

Usage

The ClassLoader should call findSharedClass after asking its parent (if one exists). If findSharedClass does not return null, the ClassLoader calls defineClass on the byte[] that is returned.

The ClassLoader calls storeSharedClass immediately after a class is defined, unless the class that is being defined was loaded from the shared cache.

The ClassLoader must keep the helper up to date with regard to classpath changes. If the ClassLoader classpath is appended to, the ClassLoader must call addClasspathEntry(URL) on the helper. If the ClassLoader classpath is modified in any other way, the ClassLoader must call setClasspath(URL[]).

If partitions are required, the ClassLoader is responsible for coordinating the creation and use of partition Strings (see Using classpaths).

Classes can be stored only by using URLs that have file or jar protocols, and that refer to existing resources. The presence of any other protocol in a URL prevents SharedClassURLClasspathHelper from locating and storing classes in the shared cache.

Using classpaths

When a findSharedClass request is made from a SharedClassURLClasspathHelper, the shared cache determines whether to return a class by comparing the caller's classpath to the classpath or URL that the class was stored against. Classpaths do not have to explicitly match.

For example, a class c1 loaded from c.jar and stored using classpath a.jar;b.jar;c.jar;d.jar may be found by using the following classpaths:

  • a.jar;c.jar ... (It is known that a.jar does not contain a c1)
  • b.jar;c.jar ... (It is known that b.jar does not contain a c1)
  • b.jar;a.jar;c.jar ... (likewise in whatever order)
  • c.jar ...

However, it will not be found by using the following classpath:

  • d.jar;a.jar;b.jar;c.jar ... (It is not known whether a different c1 exists in d.jar or not)

To ensure that findSharedClass returns correct results, it returns only classes that are found for entries up to and including the right-most confirmed entry. The definition of a confirmed entry is that it has been opened and read by the ClassLoader. Entries are confirmed by calling storeSharedClass (every entry up to and including the index used is confirmed) or by calling confirmAllEntries(). The latter should only be used if the classpath is guaranteed not to change (but can still be appended to).

Note that if findSharedClass returns null for whatever reason (even though the cache has the required class), calling storeSharedClass(...) on the class loaded from disk is the correct thing to do. This confirms the classpath entry, and the cache does not store a duplicate class.

In this way, classes can be shared as effectively as possible between class loaders using different classpaths or URL helper types.

Note that two identical classes are never stored twice in the class cache, but many entries may exist for the same class. For example, if class X is stored from a.jar and also from b.jar, the class will exist once in the cache, but will have two entries.

Modifying classpaths

It is possible that the classpath of a ClassLoader may change after it is initially set. There is a jar extension mechanism, for example, that allows a jar to define a lookup order of other jars within its manifest. If this causes the classpath to change, then the helper must be informed of the update.

After an initial classpath has been set in SharedClassURLClasspathHelper, classpath entries in it are confirmed as classes are stored in the cache from these entries. For example given classpath a.jar;b.jar;c.jar;d.jar, if a class is stored from b.jar then both a.jar and b.jar are confirmed because the ClassLoader must have opened both of them.

After classpath entries are confirmed, they cannot be changed, so setClasspath may only make changes to parts of the classpath that are not yet confirmed. For example in the given example, a.jar;b.jar;x.jar;c.jar is acceptable, whereas a.jar;x.jar;b.jar;c.jar;d.jar is not. If an attempt is made to make changes to confirmed classpath entries, a CannotSetClasspathException is thrown.

Dynamic cache updates

Because the shared cache persists beyond the lifetime of a JVM, classes in the shared cache can become out of date (stale). Classes in the cache are automatically kept up to date by default.

If findSharedClass is called for a class that exists in the cache but which has been updated on the filesystem since it was stored, then the old version in the cache is automatically marked stale and findSharedClass returns null. The ClassLoader should then store the updated version of the class.

Note that jar/zip updates cause all cache entries that are loaded from that jar/zip to be marked stale. Since a classpath has a specific search order, an update to a jar/zip will also cause all classes that are loaded from URLs to the right-side of that entry to also become stale. This is because the updated zip/jar may now contain classes that hide other classes to the right-side of it in the classpath.

For example, class c1 is loaded from c.jar and is stored in the cache using classpath a.jar;b.jar;c.jar.

a.jar is then updated to include a version of c1 and findSharedClass is called for c1.

It is essential that findSharedClass does not then return the version of c1 in c.jar. It will detect the change, return null and c1 from a.jar should then be stored.

(This behaviour can be disabled by using the correct command-line option, but this is not recommended. See -Xshareclasses:help.)

It is also assumed that after a jar/zip has been opened, the ClassLoader maintains a read lock on that file during its lifetime, preventing its modification. This prevents the cache from having to constantly check for updates. However, it is understood that non-existent jars/zips on a classpath can only be locked if/when they exist.

Partitions

A partition can be used when finding or storing a class, which allows modified versions of the same class to be stored in the cache, effectively creating partitions in the cache.

Partitions are designed for bytecode modification such as the use of Aspects. It is the responsibility of the ClassLoader to create partitions that describe the type of modification performed on the class bytes.

If a class is updated on the filesystem and automatic dynamic updates are enabled, then all versions of the class across all partitions will be marked stale.

Class metadata

A ClassLoader can create metadata when loading and defining classes, such as a jar manifest or security data. None of this metadata can be stored in the cache, so if a ClassLoader is finding classes in the shared cache, it must load any metadata that it needs from disk before defining the classes.

Example:
For static metadata specific to URL classpath entries (such as Manifest/CodeSource information), a suggested solution is to create a local array to cache the metadata when it is loaded from disk. When findSharedClass is called and an index is returned, look to see if metadata is already cached in the local array for that index. If it is, define the class. If not, load the class from disk to obtain the metadata, cache it in the local array, define the class, and then call storeSharedClass on it (it doesn't matter if it is already in the cache). All future results from findSharedClass for that classpath entry can then use the cached metadata.

If findSharedClass returns null, then load the class from disk, cache the metadata from the entry anyway, define the class, and store it.

Security

A SharedClassHelper will only allow classes that were defined by the ClassLoader that owns the SharedClassHelper to be stored in the cache.

If a SecurityManager is installed, SharedClassPermissions must be used to permit read/write access to the shared class cache. Permissions are granted by ClassLoader classname in the java.policy file and are fixed when the SharedClassHelper is created.

Note also that if the createClassLoader RuntimePermission is not granted, ClassLoaders cannot be created, which in turn means that SharedClassHelpers cannot be created.

Efficient use of the SharedClassURLClasspathHelper

Here are some recommendations on using the SharedClassURLClasspathHelper:
  1. It is best not to start with a zero length classpath and gradually grow it. Each classpath change causes a new classpath to be added to the cache and reduces the number of optimizations that can be made when matching classpaths.
  2. If the ClassLoader will never call setClasspath, then use confirmAllEntries immediately after obtaining the helper. This ensures that findSharedClass will never return null due to unconfirmed entries.

Compatibility with other SharedClassHelpers

Classes stored by using the SharedClassURLClasspathHelper can be retrieved by using the SharedClassURLHelper and vice versa. This is also true for partitions that can be used across these two helpers.

See Also:
  • Method Details

    • findSharedClass

      byte[] findSharedClass(String className, SharedClassURLClasspathHelper.IndexHolder indexFoundAt)

      Finds a class in the shared cache by using the class name given (implicitly using the caller's classpath).

      See Using classpaths for rules on when a class will be found.
      Null is returned if the class cannot be found, if it is stale (see Dynamic cache updates) or if it is found for an unconfirmed entry (see Using classpaths).

      Parameters:
      className - String. The name of the class to be found
      indexFoundAt - IndexHolder. The index in the caller ClassLoader's classpath at which the class was found. This parameter can be null if this data is not needed.
      Returns:
      byte[]. A byte array describing the class found, or null.
    • findSharedClass

      byte[] findSharedClass(String partition, String className, SharedClassURLClasspathHelper.IndexHolder indexFoundAt)

      Finds a class in the shared cache by using the class name and partition given (implicitly using the caller's classpath).

      See Using classpaths for rules on when a class will be found.
      Null is returned if the class cannot be found, if it is stale (see Dynamic cache updates) or if it is found for an unconfirmed entry (see Using classpaths).

      Parameters:
      partition - String. User-defined partition if finding modified bytecode (see Partitions). Passing null is equivalent of calling non-partition findSharedClass call.
      className - String. The name of the class to be found
      indexFoundAt - IndexHolder. The index in the caller ClassLoader's classpath at which the class was found. This parameter can be null if this data is not needed.
      Returns:
      byte[]. A byte array describing the class found, or null.
    • storeSharedClass

      boolean storeSharedClass(Class<?> clazz, int foundAtIndex)

      Stores a class in the shared cache by using the caller's URL classpath.

      The class being stored must have been defined by the caller ClassLoader and must exist in the URL location specified.

      Returns true if the class is stored successfully or false otherwise.

      Will return false if the class being stored was not defined by the caller ClassLoader.

      Also returns false if the URL at foundAtIndex is not a file URL or if the resource it refers to does not exist.

      Parameters:
      clazz - Class. The class to store in the shared cache
      foundAtIndex - int. The index in the caller's classpath where the class was loaded from (first entry is 0).
      Returns:
      boolean. True if the class was stored successfully, false otherwise.
    • storeSharedClass

      boolean storeSharedClass(String partition, Class<?> clazz, int foundAtIndex)

      Stores a class in the shared cache by using the caller's URL classpath and with a user-defined partition.

      The class that is being stored must have been defined by the caller ClassLoader and must exist in the URL location specified.

      Returns true if the class is stored successfully or false otherwise.

      Will return false if the class that is being stored was not defined by the caller ClassLoader.

      Also returns false if the URL at foundAtIndex is not a file URL or if the resource it refers to does not exist.

      Parameters:
      partition - String. User-defined partition if storing modified bytecode (see Partitions). Passing null is equivalent of calling non-partition storeSharedClass call.
      clazz - Class. The class to store in the shared cache
      foundAtIndex - int. The index in the caller's classpath where the class was loaded from (first entry is 0).
      Returns:
      boolean. True if the class was stored successfully, false otherwise.
    • addClasspathEntry

      void addClasspathEntry(URL cpe)

      Updates the helper's classpath by appending a URL (see Usage).

      Note: It is essential that the helper's classpath is kept up-to-date with the classloader.

      Parameters:
      cpe - URL. The classpath entry to append to the classpath
    • setClasspath

      void setClasspath(URL[] newClasspath) throws CannotSetClasspathException

      Updates the helper's classpath with a new classpath.

      This function is useful for ClassLoaders that compute their classpath lazily. The initial classpath is passed to the constructor optimistically, but if the classloader discovers a change while reading an entry, it can update the classpath by using this function.

      Note: It is essential that the helper's classpath is kept up-to-date with the classloader.

      The classpath that is passed to this function must be exactly the same as the original classpath up to and including the right-most entry that classes have been loaded from (the right-most confirmed entry).

      Throws a CannotSetClasspathException if this is not the case (see Modifying classpaths).

      After the classpath has been updated, any indexes passed to storeSharedClass and returned from findSharedClass correspond to the new classpath.

      Parameters:
      newClasspath - The new URL classpath array
      Throws:
      CannotSetClasspathException - when the class path cannot be set.
    • confirmAllEntries

      void confirmAllEntries()

      Confirms all entries in the current classpath. Any new entries added will not automatically be confirmed.

      Note that if all entries are confirmed, setClasspath cannot be used to modify the classpath, only to append new entries. (see Efficient use of the SharedClassURLClasspathHelper).