001: /**
002: *
003: * Licensed to the Apache Software Foundation (ASF) under one or more
004: * contributor license agreements. See the NOTICE file distributed with
005: * this work for additional information regarding copyright ownership.
006: * The ASF licenses this file to You under the Apache License, Version 2.0
007: * (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */package org.apache.openejb;
018:
019: import java.beans.Introspector;
020: import java.io.File;
021: import java.io.ObjectInputStream;
022: import java.io.ObjectOutputStream;
023: import java.io.ObjectStreamClass;
024: import java.lang.reflect.Field;
025: import java.lang.reflect.Method;
026: import java.net.URL;
027: import java.net.URLClassLoader;
028: import java.security.AccessController;
029: import java.security.PrivilegedAction;
030: import java.util.ArrayList;
031: import java.util.HashMap;
032: import java.util.List;
033: import java.util.Map;
034: import java.util.Collections;
035: import java.util.Set;
036: import java.util.LinkedHashSet;
037: import java.util.jar.JarFile;
038:
039: import org.apache.openejb.core.TempClassLoader;
040: import org.apache.openejb.util.URLs;
041: import org.apache.openejb.util.Logger;
042: import org.apache.openejb.util.LogCategory;
043: import org.apache.openejb.util.UrlCache;
044:
045: /**
046: * @version $Revision: 637617 $ $Date: 2008-03-16 10:42:30 -0700 $
047: */
048: public class ClassLoaderUtil {
049: private static final Logger logger = Logger.getInstance(
050: LogCategory.OPENEJB, ClassLoaderUtil.class);
051: private static final Map<String, List<ClassLoader>> classLoadersByApp = new HashMap<String, List<ClassLoader>>();
052: private static final Map<ClassLoader, Set<String>> appsByClassLoader = new HashMap<ClassLoader, Set<String>>();
053:
054: private static final UrlCache urlCache = new UrlCache();
055:
056: public static ClassLoader getContextClassLoader() {
057: return AccessController
058: .doPrivileged(new PrivilegedAction<ClassLoader>() {
059: public ClassLoader run() {
060: return Thread.currentThread()
061: .getContextClassLoader();
062: }
063: });
064: }
065:
066: public static URLClassLoader createClassLoader(String appId,
067: URL[] urls, ClassLoader parent) {
068: urls = urlCache.cacheUrls(appId, urls);
069: URLClassLoader classLoader = new URLClassLoader(urls, parent);
070:
071: List<ClassLoader> classLoaders = classLoadersByApp.get(appId);
072: if (classLoaders == null) {
073: classLoaders = new ArrayList<ClassLoader>(2);
074: classLoadersByApp.put(appId, classLoaders);
075: }
076: classLoaders.add(classLoader);
077:
078: Set<String> apps = appsByClassLoader.get(classLoader);
079: if (apps == null) {
080: apps = new LinkedHashSet<String>(1);
081: appsByClassLoader.put(classLoader, apps);
082: }
083: apps.add(appId);
084:
085: return classLoader;
086: }
087:
088: public static void destroyClassLoader(ClassLoader classLoader) {
089: logger.debug("Destroying classLoader " + toString(classLoader));
090:
091: // remove from the indexes
092: Set<String> apps = appsByClassLoader.remove(classLoader);
093: if (apps != null) {
094: for (String appId : apps) {
095: List<ClassLoader> classLoaders = classLoadersByApp
096: .get(appId);
097: if (classLoaders != null) {
098: classLoaders.remove(classLoader);
099: // if this is the last class loader in the app, clean up the app
100: if (classLoaders.isEmpty()) {
101: destroyClassLoader(appId);
102: }
103: }
104: }
105: }
106:
107: // clear the lame openjpa caches
108: cleanOpenJPACache(classLoader);
109: }
110:
111: public static void destroyClassLoader(String appId) {
112: logger
113: .debug("Destroying classLoaders for application "
114: + appId);
115:
116: List<ClassLoader> classLoaders = classLoadersByApp
117: .remove(appId);
118: if (classLoaders != null) {
119: for (ClassLoader classLoader : classLoaders) {
120: // get the apps using the class loader
121: Set<String> apps = appsByClassLoader.get(classLoader);
122: if (apps == null)
123: apps = Collections.emptySet();
124:
125: // this app is no longer using the class loader
126: apps.remove(appId);
127:
128: // if no apps are using the class loader, destroy it
129: if (apps.isEmpty()) {
130: appsByClassLoader.remove(classLoader);
131: destroyClassLoader(classLoader);
132: } else {
133: logger.debug("ClassLoader " + toString(classLoader)
134: + " held open by the applications" + apps);
135: }
136: }
137: }
138: urlCache.releaseUrls(appId);
139: clearSunJarFileFactoryCache(appId);
140: }
141:
142: public static URLClassLoader createTempClassLoader(
143: ClassLoader parent) {
144: return new TempClassLoader(parent);
145: }
146:
147: public static URLClassLoader createTempClassLoader(String appId,
148: URL[] urls, ClassLoader parent) {
149: URLClassLoader classLoader = createClassLoader(appId, urls,
150: parent);
151: TempClassLoader tempClassLoader = new TempClassLoader(
152: classLoader);
153: return tempClassLoader;
154: }
155:
156: /**
157: * Cleans well known class loader leaks in VMs and libraries. There is a lot of bad code out there and this method
158: * will clear up the know problems. This method should only be called when the class loader will no longer be used.
159: * It this method is called two often it can have a serious impact on preformance.
160: */
161: public static void clearClassLoaderCaches() {
162: clearSunSoftCache(ObjectInputStream.class, "subclassAudits");
163: clearSunSoftCache(ObjectOutputStream.class, "subclassAudits");
164: clearSunSoftCache(ObjectStreamClass.class, "localDescs");
165: clearSunSoftCache(ObjectStreamClass.class, "reflectors");
166: Introspector.flushCaches();
167: }
168:
169: public static void clearSunJarFileFactoryCache(String jarLocation) {
170: logger.debug("Clearing Sun JarFileFactory cache for directory "
171: + jarLocation);
172:
173: try {
174: Class jarFileFactory = Class
175: .forName("sun.net.www.protocol.jar.JarFileFactory");
176:
177: Field fileCacheField = jarFileFactory
178: .getDeclaredField("fileCache");
179:
180: fileCacheField.setAccessible(true);
181: Map fileCache = (Map) fileCacheField.get(null);
182:
183: Field urlCacheField = jarFileFactory
184: .getDeclaredField("urlCache");
185: urlCacheField.setAccessible(true);
186: Map urlCache = (Map) urlCacheField.get(null);
187:
188: List<URL> urls = new ArrayList<URL>();
189: for (Object item : fileCache.keySet()) {
190: URL url = (URL) item;
191: if (isParent(jarLocation, URLs.toFile(url))) {
192: urls.add(url);
193: }
194: }
195:
196: for (URL url : urls) {
197: JarFile jarFile = (JarFile) fileCache.remove(url);
198: if (jarFile == null)
199: continue;
200:
201: urlCache.remove(jarFile);
202: jarFile.close();
203: }
204: } catch (ClassNotFoundException e) {
205: // not a sun vm
206: } catch (NoSuchFieldException e) {
207: // different version of sun vm?
208: } catch (Throwable e) {
209: logger.error("Unable to clear Sun JarFileFactory cache", e);
210: }
211: }
212:
213: private static boolean isParent(String jarLocation, File file) {
214: File dir = new File(jarLocation);
215: while (file != null) {
216: if (file.equals(dir)) {
217: return true;
218: }
219: file = file.getParentFile();
220: }
221: return false;
222: }
223:
224: /**
225: * Clears the caches maintained by the SunVM object stream implementation. This method uses reflection and
226: * setAccessable to obtain access to the Sun cache. The cache is locked with a synchronize monitor and cleared.
227: * This method completely clears the class loader cache which will impact preformance of object serialization.
228: * @param clazz the name of the class containing the cache field
229: * @param fieldName the name of the cache field
230: */
231: public static void clearSunSoftCache(Class clazz, String fieldName) {
232: Map cache = null;
233: try {
234: Field field = clazz.getDeclaredField(fieldName);
235: field.setAccessible(true);
236: cache = (Map) field.get(null);
237: } catch (Throwable ignored) {
238: // there is nothing a user could do about this anyway
239: }
240:
241: if (cache != null) {
242: synchronized (cache) {
243: cache.clear();
244: }
245: }
246: }
247:
248: public static void cleanOpenJPACache(ClassLoader classLoader) {
249: try {
250: Class<?> pcRegistryClass = ClassLoaderUtil.class
251: .getClassLoader().loadClass(
252: "org.apache.openjpa.enhance.PCRegistry");
253: Method deRegisterMethod = pcRegistryClass.getMethod(
254: "deRegister", ClassLoader.class);
255: deRegisterMethod.invoke(null, classLoader);
256: } catch (Throwable ignored) {
257: // there is nothing a user could do about this anyway
258: }
259: }
260:
261: private static String toString(ClassLoader classLoader) {
262: if (classLoader == null) {
263: return "null";
264: } else {
265: return classLoader.getClass().getSimpleName() + "@"
266: + System.identityHashCode(classLoader);
267: }
268: }
269: }
|