From 140cd46e78e904d9cb5489086265b72cb6d5b34c Mon Sep 17 00:00:00 2001 From: hangshao Date: Wed, 30 May 2018 17:07:28 -0400 Subject: [PATCH] Contribute the source code of CDS adapter to OpenJ9 Contribute only the java source files of the CDS adapter project to OpenJ9 [ci skip] Signed-off-by: hangshao --- cdsadapter/.project | 11 + .../src/com/ibm/cds/CDSBundleEntry.java | 114 ++++++++ .../src/com/ibm/cds/CDSBundleFile.java | 135 +++++++++ .../src/com/ibm/cds/CDSHookConfigurator.java | 47 ++++ .../src/com/ibm/cds/CDSHookImpls.java | 256 ++++++++++++++++++ 5 files changed, 563 insertions(+) create mode 100644 cdsadapter/.project create mode 100644 cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleEntry.java create mode 100644 cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleFile.java create mode 100644 cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookConfigurator.java create mode 100644 cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookImpls.java diff --git a/cdsadapter/.project b/cdsadapter/.project new file mode 100644 index 00000000000..2e996dd771e --- /dev/null +++ b/cdsadapter/.project @@ -0,0 +1,11 @@ + + + cdsadapter + + + + + + + + diff --git a/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleEntry.java b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleEntry.java new file mode 100644 index 00000000000..bfa899e45ec --- /dev/null +++ b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleEntry.java @@ -0,0 +1,114 @@ +/******************************************************************************* + * Copyright (c) 2006, 2018 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ + +package com.ibm.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/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleFile.java b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleFile.java new file mode 100644 index 00000000000..99c6e712ce9 --- /dev/null +++ b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSBundleFile.java @@ -0,0 +1,135 @@ +/******************************************************************************* + * Copyright (c) 2006, 2018 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ + +package com.ibm.cds; + + +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; + +import com.ibm.oti.shared.SharedClassURLHelper; + +/** + * 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()); + } 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"; + 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(url, name); + } +} + diff --git a/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookConfigurator.java b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookConfigurator.java new file mode 100644 index 00000000000..e9c8b9c06b0 --- /dev/null +++ b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookConfigurator.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2006, 2018 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ + +package com.ibm.cds; + +import org.eclipse.osgi.internal.hookregistry.HookConfigurator; +import org.eclipse.osgi.internal.hookregistry.HookRegistry; + +public class CDSHookConfigurator implements HookConfigurator { + //////////////// HookConfigurator ////////////////// + static public boolean SUPPRESS_ERROR_REPORTING = "true".equals(System.getProperty("ibm.cds.suppresserrors")); + public void addHooks(HookRegistry hookRegistry) { + try { + Class.forName("com.ibm.oti.shared.SharedClassHelperFactory"); + } catch (ClassNotFoundException e) { + // not running on J9 + if (!SUPPRESS_ERROR_REPORTING) { + System.err.println("The IBM Class Sharing Adaptor will not work in this configuration."); + System.err.println("You are not running on a J9 Java VM."); + } + return; + } + CDSHookImpls hooks = new CDSHookImpls(); + hookRegistry.addClassLoaderHook(hooks); + hookRegistry.addBundleFileWrapperFactoryHook(hooks); + } + +} diff --git a/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookImpls.java b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookImpls.java new file mode 100644 index 00000000000..d8416a40cab --- /dev/null +++ b/cdsadapter/com.ibm.cds/src/com/ibm/cds/CDSHookImpls.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright (c) 2006, 2018 IBM Corp. and others + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which accompanies this + * distribution and is available at https://www.eclipse.org/legal/epl-2.0/ + * or the Apache License, Version 2.0 which accompanies this distribution and + * is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * This Source Code may also be made available under the following + * Secondary Licenses when the conditions for such availability set + * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU + * General Public License, version 2 with the GNU Classpath + * Exception [1] and GNU General Public License, version 2 with the + * OpenJDK Assembly Exception [2]. + * + * [1] https://www.gnu.org/software/classpath/license.html + * [2] http://openjdk.java.net/legal/assembly-exception.html + * + * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 OR LicenseRef-GPL-2.0 WITH Assembly-exception + *******************************************************************************/ + +package com.ibm.cds; + +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.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; + +import com.ibm.oti.shared.HelperAlreadyDefinedException; +import com.ibm.oti.shared.Shared; +import com.ibm.oti.shared.SharedClassHelperFactory; +import com.ibm.oti.shared.SharedClassURLHelper; + +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(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(); + /* Not supported in the Java6 GA, but will be in SR1 + */ + java.lang.reflect.Field isSupported = c.getField("MINIMIZE_ENABLED"); + if (isSupported != null) { /* Field doesn't exist in Java5 */ + if (isSupported.getBoolean(urlHelper)) { + minimizeMethod = c.getMethod("setMinimizeUpdateChecks", null); + hasMinimizeMethod = true; + } + } + } catch (Exception e) { + /* hasMinimizeMethod will be false and we won't try this again */ + } + } + if (minimizeMethod != null) { + try { + minimizeMethod.invoke(urlHelper, null); + 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; + } +}