Bug 537505 - Support OpenJ9 CDS directly in the framework

Change-Id: Ib93dd9ab8306b4ba89f9de1b93ea48b654e5bff8
Signed-off-by: Thomas Watson <tjwatson@us.ibm.com>
diff --git a/bundles/org.eclipse.osgi/build.properties b/bundles/org.eclipse.osgi/build.properties
index 9ddb3ad..a3031af 100644
--- a/bundles/org.eclipse.osgi/build.properties
+++ b/bundles/org.eclipse.osgi/build.properties
@@ -33,5 +33,6 @@
 
 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 0000000..9e92911
--- /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 0000000..3376953
--- /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 0000000..98f6711
--- /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 0000000..6e7bcb0
--- /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<N> -> 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<ClasspathEntry> 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 f9d881d..7bd34c9 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 @@
 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 @@
 			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 0000000..597a2d4
--- /dev/null
+++ b/bundles/org.eclipse.osgi/osgi/j9stubs.jar
Binary files differ