From dceb34eef2316ee15a3442e6b853af9be06861f7 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Mon, 11 Jun 2018 13:42:35 -0500 Subject: Bug 537505 - Support OpenJ9 CDS directly in the framework Change-Id: Ib93dd9ab8306b4ba89f9de1b93ea48b654e5bff8 Signed-off-by: Thomas Watson --- bundles/org.eclipse.osgi/build.properties | 3 +- .../eclipse/osgi/internal/cds/CDSBundleEntry.java | 102 +++++++++ .../eclipse/osgi/internal/cds/CDSBundleFile.java | 120 ++++++++++ .../osgi/internal/cds/CDSHookConfigurator.java | 52 +++++ .../eclipse/osgi/internal/cds/CDSHookImpls.java | 244 +++++++++++++++++++++ .../osgi/internal/hookregistry/HookRegistry.java | 9 +- bundles/org.eclipse.osgi/osgi/j9stubs.jar | Bin 0 -> 4081 bytes 7 files changed, 528 insertions(+), 2 deletions(-) create mode 100644 bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleEntry.java create mode 100644 bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleFile.java create mode 100644 bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookConfigurator.java create mode 100644 bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookImpls.java create mode 100644 bundles/org.eclipse.osgi/osgi/j9stubs.jar diff --git a/bundles/org.eclipse.osgi/build.properties b/bundles/org.eclipse.osgi/build.properties index 9ddb3ad0b..a3031af88 100644 --- a/bundles/org.eclipse.osgi/build.properties +++ b/bundles/org.eclipse.osgi/build.properties @@ -33,5 +33,6 @@ output.. = bin/ javacWarnings..=-raw,unchecked,hiding,unused,warningToken jars.extra.classpath = osgi/osgi.annotation.jar,\ - osgi/function.interface.jar + osgi/function.interface.jar,\ + osgi/j9stubs.jar jre.compilation.profile = JavaSE-1.7 diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleEntry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleEntry.java new file mode 100644 index 000000000..9e92911ef --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleEntry.java @@ -0,0 +1,102 @@ +/*********************************************************************** + * IBM Confidential + * OCO Source Materials + * + * (C) Copyright IBM Corp. 2006, 2014 + * + * The source code for this program is not published or otherwise divested + * of its trade secrets, irrespective of what has been deposited with the + * U.S. Copyright Office. + ************************************************************************/ + +package org.eclipse.osgi.internal.cds; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import org.eclipse.osgi.storage.bundlefile.BundleEntry; + +/** + * A bundle entry for a class that is found in the shared classes cache + */ +public class CDSBundleEntry extends BundleEntry { + String path; + byte[] classbytes; + BundleEntry wrapped; + + /** + * The constructor + * @param path the path to the class file + * @param classbytes the magic cookie bytes for the class in the shared cache + * @param wrapped the actual bundleEntry where the class comes from + */ + public CDSBundleEntry(String path, byte[] classbytes, BundleEntry wrapped) { + super(); + this.path = path; + this.classbytes = classbytes; + this.wrapped = wrapped; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry#getFileURL() + * uses the wrapped bundle file to get the actual file url to the content of + * the class on disk. + * + * This should is likely never to be called. + */ + public URL getFileURL() { + return wrapped.getFileURL(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry#getInputStream() + * wraps the classbytes into a ByteArrayInputStream. This should not be used + * by classloading. + */ + public InputStream getInputStream() throws IOException { + // someone is trying to get the real bytes of the class file!! + // just return the entry from the wrapped file instead of the magic cookie + return wrapped.getInputStream(); + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry#getBytes() + * if classbytes is not null, it returns the magic cookie for the shared class. This is used to define + * the class during class loading. + * if classbytes is null, it gets the contents from actual BundleEntry and caches it in classbytes. + */ + public byte[] getBytes() throws IOException { + if (classbytes == null) { + classbytes = wrapped.getBytes(); + } + return classbytes; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.baseadaptor.bundlefile.BundleEntry#getLocalURL() + * uses the wrapped bundle file to get the actual local url to the content of + * the class on disk. + * + * This should is likely never to be called. + */ + public URL getLocalURL() { + return wrapped.getLocalURL(); + } + + public String getName() { + return path; + } + + public long getSize() { + return wrapped.getSize(); + } + + public long getTime() { + return wrapped.getTime(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleFile.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleFile.java new file mode 100644 index 000000000..33769533f --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSBundleFile.java @@ -0,0 +1,120 @@ +/*********************************************************************** + * IBM Confidential + * OCO Source Materials + * + * (C) Copyright IBM Corp. 2006, 2014 + * + * The source code for this program is not published or otherwise divested + * of its trade secrets, irrespective of what has been deposited with the + * U.S. Copyright Office. + ************************************************************************/ + +package org.eclipse.osgi.internal.cds; + +import com.ibm.oti.shared.SharedClassURLHelper; +import java.net.MalformedURLException; +import java.net.URL; +import org.eclipse.osgi.storage.bundlefile.BundleEntry; +import org.eclipse.osgi.storage.bundlefile.BundleFile; +import org.eclipse.osgi.storage.bundlefile.BundleFileWrapper; + +/** + * Wraps an actual BundleFile object for purposes of loading classes from the + * shared classes cache. + */ +public class CDSBundleFile extends BundleFileWrapper { + private URL url; // the URL to the content of the real bundle file + private SharedClassURLHelper urlHelper; // the url helper set by the classloader + private boolean primed = false; + + /** + * The constructor + * @param wrapped the real bundle file + */ + public CDSBundleFile(BundleFile wrapped) { + super(wrapped); + // get the url to the content of the real bundle file + try { + this.url = new URL("file", "", wrapped.getBaseFile().getAbsolutePath()); //$NON-NLS-1$ //$NON-NLS-2$ + } catch (MalformedURLException e) { + // do nothing + } + } + + public CDSBundleFile(BundleFile bundleFile, SharedClassURLHelper urlHelper) { + this(bundleFile); + this.urlHelper = urlHelper; + } + + /* + * (non-Javadoc) + * @see org.eclipse.osgi.storage.bundlefile.BundleFile#getEntry(java.lang.String) + * + * If path is not for a class then just use the wrapped bundle file to answer the call. + * If the path is for a class, it returns a CDSBundleEntry object. + * If the path is for a class, it will look for the magic cookie in the + * shared classes cache. If found, the bytes representing the magic cookie are stored in CDSBundleEntry object. + */ + public BundleEntry getEntry(String path) { + String classFileExt = ".class"; //$NON-NLS-1$ + BundleEntry wrappedEntry = super.getEntry(path); + if (wrappedEntry == null) { + return null; + } + if ((false == primed) || (false == path.endsWith(classFileExt))) { + return wrappedEntry; + } + + byte[] classbytes = getClassBytes(path.substring(0, path.length() - classFileExt.length())); + BundleEntry be = new CDSBundleEntry(path, classbytes, wrappedEntry); + return be; + } + + /** + * Returns the file url to the content of the actual bundle file + * @return the file url to the content of the actual bundle file + */ + URL getURL() { + return url; + } + + /** + * Returns the url helper for this bundle file. This is set by the + * class loading hook + * @return the url helper for this bundle file + */ + SharedClassURLHelper getURLHelper() { + return urlHelper; + } + + /** + * Sets the url helper for this bundle file. This is called by the + * class loading hook. + * @param urlHelper the url helper + */ + void setURLHelper(SharedClassURLHelper urlHelper) { + this.urlHelper = urlHelper; + this.primed = false; // always unprime when a new urlHelper is set + } + + /** + * Sets the primed flag for the bundle file. This is called by the + * class loading hook after the first class has been loaded from disk for + * this bundle file. + * @param primed the primed flag + */ + void setPrimed(boolean primed) { + this.primed = primed; + } + + /** + * Searches in the shared classes cache for the specified class name. + * @param name the name of the class + * @return the magic cookie to the shared class or null if the class is not in the cache. + */ + private byte[] getClassBytes(String name) { + if (urlHelper == null || url == null) + return null; + return urlHelper.findSharedClass(null, url, name); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookConfigurator.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookConfigurator.java new file mode 100644 index 000000000..98f671178 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookConfigurator.java @@ -0,0 +1,52 @@ +/*********************************************************************** + * IBM Confidential + * OCO Source Materials + * + * (C) Copyright IBM Corp. 2006 + * + * The source code for this program is not published or otherwise divested + * of its trade secrets, irrespective of what has been deposited with the + * U.S. Copyright Office. + ************************************************************************/ + +package org.eclipse.osgi.internal.cds; + +import org.eclipse.osgi.internal.hookregistry.HookConfigurator; +import org.eclipse.osgi.internal.hookregistry.HookRegistry; + +public class CDSHookConfigurator implements HookConfigurator { + + private static final String SUPPRESS_ERRORS = "j9.cds.suppresserrors"; //$NON-NLS-1$ + private static final String DISABLE_CDS = "j9.cds.disable"; //$NON-NLS-1$ + private static final String OLD_CDS_CONFIGURATOR = "com.ibm.cds.CDSHookConfigurator"; //$NON-NLS-1$ + private static final String J9_SHARED_CLASS_HELPER_CLASS = "com.ibm.oti.shared.SharedClassHelperFactory"; //$NON-NLS-1$ + + public void addHooks(HookRegistry hookRegistry) { + boolean disableCDS = "true".equals(hookRegistry.getConfiguration().getProperty(DISABLE_CDS)); //$NON-NLS-1$ + if (disableCDS) { + return; + } + // check for the external com.ibm.cds system.bundle fragment + try { + Class.forName(OLD_CDS_CONFIGURATOR); + // the old com.ibm.cds fragment is installed; disable build-in one + return; + } catch (ClassNotFoundException e) { + // expected + } + try { + Class.forName(J9_SHARED_CLASS_HELPER_CLASS); + } catch (ClassNotFoundException e) { + boolean reportErrors = "false".equals(hookRegistry.getConfiguration().getProperty(SUPPRESS_ERRORS)); //$NON-NLS-1$ + // not running on J9 + if (reportErrors) { + System.err.println("The J9 Class Sharing Adaptor will not work in this configuration."); //$NON-NLS-1$ + System.err.println("You are not running on a J9 Java VM."); //$NON-NLS-1$ + } + return; + } + + new CDSHookImpls().registerHooks(hookRegistry); + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookImpls.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookImpls.java new file mode 100644 index 000000000..6e7bcb054 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/cds/CDSHookImpls.java @@ -0,0 +1,244 @@ +/*********************************************************************** + * IBM Confidential + * OCO Source Materials + * + * (C) Copyright IBM Corp. 2006 + * + * The source code for this program is not published or otherwise divested + * of its trade secrets, irrespective of what has been deposited with the + * U.S. Copyright Office. + ************************************************************************/ + +package org.eclipse.osgi.internal.cds; + +import com.ibm.oti.shared.HelperAlreadyDefinedException; +import com.ibm.oti.shared.Shared; +import com.ibm.oti.shared.SharedClassHelperFactory; +import com.ibm.oti.shared.SharedClassURLHelper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import org.eclipse.osgi.internal.hookregistry.BundleFileWrapperFactoryHook; +import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook; +import org.eclipse.osgi.internal.hookregistry.HookRegistry; +import org.eclipse.osgi.internal.loader.ModuleClassLoader; +import org.eclipse.osgi.internal.loader.classpath.ClasspathEntry; +import org.eclipse.osgi.internal.loader.classpath.ClasspathManager; +import org.eclipse.osgi.storage.BundleInfo.Generation; +import org.eclipse.osgi.storage.bundlefile.BundleEntry; +import org.eclipse.osgi.storage.bundlefile.BundleFile; +import org.eclipse.osgi.storage.bundlefile.BundleFileWrapper; +import org.eclipse.osgi.storage.bundlefile.BundleFileWrapperChain; + +public class CDSHookImpls extends ClassLoaderHook implements BundleFileWrapperFactoryHook { + private static SharedClassHelperFactory factory = Shared.getSharedClassHelperFactory(); + private static java.lang.reflect.Method minimizeMethod = null; + private static boolean hasMinimizeMethod = true; /* Assume true to begin with */ + + // With Equinox bug 226038 (v3.4), the framework will now pass an instance + // of BundleFileWrapperChain rather than the wrapped BundleFile. This is + // so that multiple wrapping hooks can each wrap the BundleFile and all + // wrappers are accessible. + // + // The Wrapper chain will look like below: + // WrapperChain -> Wrapper -> WrapperChain -> CDSBundleFile -> WrapperChain -> BundleFile + // + private static CDSBundleFile getCDSBundleFile(BundleFile bundleFile) { + CDSBundleFile cdsBundleFile = null; + + if (bundleFile instanceof BundleFileWrapperChain) { + // Equinox > 3.4 + BundleFile wrapped = null; + do { + wrapped = ((BundleFileWrapperChain) bundleFile).getWrapped(); + if (wrapped instanceof CDSBundleFile) { + cdsBundleFile = (CDSBundleFile) wrapped; + break; + } + + //Go to next wrapper chain. + bundleFile = ((BundleFileWrapperChain) bundleFile).getNext(); + } while (wrapped != null); + } + return cdsBundleFile; + } + + public void recordClassDefine(String name, Class clazz, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager manager) { // only attempt to record the class define if: + // 1) the class was found (clazz != null) + // 2) the class has the magic class number CAFEBABE indicating a real class + // 3) the bundle file for the classpath entry is of type CDSBundleFile + // 4) class bytes is same as passed to weaving hook i.e. weaving hook did not modify the class bytes + if ((null == clazz) || (false == hasMagicClassNumber(classbytes)) || (null == getCDSBundleFile(classpathEntry.getBundleFile()))) { + return; + } + try { + // check if weaving hook modified the class bytes + byte originalClassBytes[] = entry.getBytes(); + if (originalClassBytes != classbytes) { + // weaving hook has potentially modified the class bytes + boolean modified = false; + if (originalClassBytes.length == classbytes.length) { + // do a byte-by-byte comparison + modified = !Arrays.equals(classbytes, originalClassBytes); + } else { + modified = true; + } + if (modified) { + // Class bytes have been modified by weaving hooks. + // Such classes need to be stored as Orphans, so skip the call to storeSharedClass() + return; + } + } + } catch (IOException e) { + // this should never happen, but in case it does, its safe to return + return; + } + + CDSBundleFile cdsFile = getCDSBundleFile(classpathEntry.getBundleFile()); + + if (null == cdsFile.getURL()) { + // something went wrong trying to determine the url to the real bundle file + return; + } + + // look for the urlHelper; if it does not exist then we are not sharing for this class loader + SharedClassURLHelper urlHelper = cdsFile.getURLHelper(); + if (urlHelper == null) { + // this should never happen but just in case get the helper from the base host bundle file. + CDSBundleFile hostBundleFile = getCDSBundleFile(manager.getGeneration().getBundleFile()); + if (null != hostBundleFile) { + // try getting the helper from the host base cdsFile + urlHelper = hostBundleFile.getURLHelper(); + } + + if (null != urlHelper) { + cdsFile.setURLHelper(urlHelper); + } + } + if (null != urlHelper) { + // store the class in the cache + urlHelper.storeSharedClass(null, cdsFile.getURL(), clazz); + cdsFile.setPrimed(true); + } + } + + /* Calling setMinimizeUpdateChecks() on the urlHelper tells it to only check the plugin jar for updates + * once on startup. This removes the need to "prime" plugins by always cacheing the first class from the jar. + * + * Java5 does not have a runMinimizeUpdateChecks method, but Java6 does. The text below explains why. + * + * Java6 has an improved jar update detection mechanism which is event-driven and listens for + * real jar open and close events. It will check jar timestamps on every class-load for closed jars (when + * loading cached classes from those jars) and not check them if it knows the jars are open. + * + * Java5 didn't know about jar open/close events so simply assumed that the first class to be stored by + * a plugin implied that its jar was opened indefinitely. This is why it helps to "prime" a plugin when + * running under Java5 - by storing a class, the jar is opened and the JVM stops checking its timestamp + * which results in faster startup. + * + * While the Java6 behaviour is more correct (it will pick up changes if a jar is closed after having been opened), + * if the jars are not opened or "primed", then it will perform constant checks on their timestamps which hurts startup times. + * This is why setMinimizeUpdateChecks was introduced - it's a means of saying to the urlHelper - regardless of + * whether my container(s) is open or closed, I only want you to check it once for updates. + * + * The consequence of this is that less file handles are open on startup in Java6. + * + * This has been written in such a way that this adaptor will continue to work exactly the same with Java5, but + * will adapt its behaviour when used with Java6 to do the right thing. + */ + private boolean runMinimizeMethod(SharedClassURLHelper urlHelper) { + if (hasMinimizeMethod && (urlHelper != null)) { + if (minimizeMethod == null) { + hasMinimizeMethod = false; /* Assume failure - prove success below */ + try { + Class c = urlHelper.getClass(); + minimizeMethod = c.getMethod("setMinimizeUpdateChecks"); //$NON-NLS-1$ + minimizeMethod.setAccessible(true); + hasMinimizeMethod = true; + } catch (Exception e) { + /* hasMinimizeMethod will be false and we won't try this again */ + } + } + if (minimizeMethod != null) { + try { + minimizeMethod.invoke(urlHelper); + return true; + } catch (Exception e) { + hasMinimizeMethod = false; + } + } + } + return false; + } + + private boolean hasMagicClassNumber(byte[] classbytes) { + if (classbytes == null || classbytes.length < 4) + return false; + // TODO maybe there is a better way to do this? I'm not sure why I had to AND each byte with the value I was checking ... + return (classbytes[0] & 0xCA) == 0xCA && (classbytes[1] & 0xFE) == 0xFE && (classbytes[2] & 0xBA) == 0xBA && (classbytes[3] & 0xBE) == 0xBE; + } + + public void classLoaderCreated(ModuleClassLoader classLoader) { + // try to get the url helper for this class loader + if (factory == null) { + return; + } + CDSBundleFile hostFile = null; + try { + SharedClassURLHelper urlHelper = factory.getURLHelper(classLoader); + boolean minimizeSucceeded = runMinimizeMethod(urlHelper); + // set the url helper for the host base CDSBundleFile + hostFile = getCDSBundleFile(classLoader.getClasspathManager().getGeneration().getBundleFile()); + if (hostFile != null) { + hostFile.setURLHelper(urlHelper); + if (minimizeSucceeded) { + /* In Java6, there is no longer a requirement to "prime" plugins */ + hostFile.setPrimed(true); + } + } + } catch (HelperAlreadyDefinedException e) { + // We should never get here. + // If we do, we simply won't share for this ClassLoader + } + } + + public boolean addClassPathEntry(ArrayList cpEntries, String cp, ClasspathManager hostmanager, Generation sourceGeneration) { + CDSBundleFile hostFile = getCDSBundleFile(hostmanager.getGeneration().getBundleFile()); + CDSBundleFile sourceFile = getCDSBundleFile(sourceGeneration.getBundleFile()); + if ((hostFile != sourceFile) && (null != hostFile) && (null != sourceFile)) { + // set the helper that got set on the host base bundle file in initializedClassLoader + SharedClassURLHelper urlHelper = hostFile.getURLHelper(); + sourceFile.setURLHelper(urlHelper); + } + + return false; + } + + //////////////// BundleFileWrapperFactoryHook ////////////// + public BundleFileWrapper wrapBundleFile(BundleFile bundleFile, Generation generation, boolean base) { + // wrap the real bundle file for purposes of loading shared classes. + CDSBundleFile newBundleFile; + if (!base && generation.getBundleInfo().getBundleId() != 0) { + // initialize the urlHelper from the base one. + SharedClassURLHelper urlHelper = null; + BundleFile baseFile = generation.getBundleFile(); + if ((baseFile = getCDSBundleFile(baseFile)) != null) { + urlHelper = ((CDSBundleFile) baseFile).getURLHelper(); + } + newBundleFile = new CDSBundleFile(bundleFile, urlHelper); + } else { + newBundleFile = new CDSBundleFile(bundleFile); + } + + return newBundleFile; + } + + void registerHooks(HookRegistry hookRegistry) { + // only register if sharing is enabled + if (!Shared.isSharingEnabled()) { + return; + } + hookRegistry.addClassLoaderHook(this); + hookRegistry.addBundleFileWrapperFactoryHook(this); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java index f9d881dd4..7bd34c99c 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/hookregistry/HookRegistry.java @@ -14,8 +14,14 @@ package org.eclipse.osgi.internal.hookregistry; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Properties; import org.eclipse.osgi.framework.log.FrameworkLogEntry; +import org.eclipse.osgi.internal.cds.CDSHookConfigurator; import org.eclipse.osgi.internal.framework.EquinoxConfiguration; import org.eclipse.osgi.internal.framework.EquinoxContainer; import org.eclipse.osgi.internal.hooks.DevClassLoadingHook; @@ -103,6 +109,7 @@ public final class HookRegistry { addClassLoaderHook(new EclipseLazyStarter(container)); addClassLoaderHook(new WeavingHookConfigurator(container)); configurators.add(SignedBundleHook.class.getName()); + configurators.add(CDSHookConfigurator.class.getName()); loadConfigurators(configurators, errors); // set to read-only initialized = true; diff --git a/bundles/org.eclipse.osgi/osgi/j9stubs.jar b/bundles/org.eclipse.osgi/osgi/j9stubs.jar new file mode 100644 index 000000000..597a2d4b6 Binary files /dev/null and b/bundles/org.eclipse.osgi/osgi/j9stubs.jar differ -- cgit v1.2.3