You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by an...@apache.org on 2011/07/14 17:13:15 UTC

svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Author: andygumbrecht
Date: Thu Jul 14 15:13:14 2011
New Revision: 1146747

URL: http://svn.apache.org/viewvc?rev=1146747&view=rev
Log:
Revert breaking change and improve comments on clearSunJarFileFactoryCacheImpl to avoid the same.

Due to several different implementation changes in various JDK releases
the 'item' can be one of the following (so far):
1. A URL that points to a file.
2. An ASCII String URI that points to a file
3. A String that is used as a key to a JarFile

Sun/Oracle may continue to change the implementation.
In JDK 7 there are new features to unload jars and classes.

Modified:
    openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Modified: openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java
URL: http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java?rev=1146747&r1=1146746&r2=1146747&view=diff
==============================================================================
--- openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java (original)
+++ openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java Thu Jul 14 15:13:14 2011
@@ -17,6 +17,12 @@
  */
 package org.apache.openejb;
 
+import org.apache.openejb.core.TempClassLoader;
+import org.apache.openejb.util.LogCategory;
+import org.apache.openejb.util.Logger;
+import org.apache.openejb.util.URLs;
+import org.apache.openejb.util.UrlCache;
+
 import java.beans.Introspector;
 import java.io.File;
 import java.io.ObjectInputStream;
@@ -24,26 +30,14 @@ import java.io.ObjectOutputStream;
 import java.io.ObjectStreamClass;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.net.URI;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.ArrayList;
-import java.util.ConcurrentModificationException;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.Vector;
+import java.util.*;
 import java.util.jar.JarFile;
 
-import org.apache.openejb.core.TempClassLoader;
-import org.apache.openejb.util.LogCategory;
-import org.apache.openejb.util.Logger;
-import org.apache.openejb.util.UrlCache;
-
 /**
  * @version $Revision$ $Date$
  */
@@ -281,8 +275,7 @@ public class ClassLoaderUtil {
 
     public static URLClassLoader createTempClassLoader(String appId, URL[] urls, ClassLoader parent) {
         URLClassLoader classLoader = createClassLoader(appId, urls, parent);
-        TempClassLoader tempClassLoader = new TempClassLoader(classLoader);
-        return tempClassLoader;
+        return new TempClassLoader(classLoader);
     }
 
     /**
@@ -302,41 +295,121 @@ public class ClassLoaderUtil {
         clearSunJarFileFactoryCacheImpl(jarLocation, 5);
     }
 
+    /**
+     * Due to several different implementation changes in various JDK releases the code here is not as
+     * straight forward as reflecting debug items in your current runtime. There have even been breaking changes
+     * between 1.6 runtime builds, let alone 1.5.
+     * <p/>
+     * If you discover a new issue here please be careful to ensure the existing functionality is 'extended' and not
+     * just replaced to match your runtime observations.
+     * <p/>
+     * If you want to look at the mess that leads up to this then follow the source code changes made to
+     * the class sun.net.www.protocol.jar.JarFileFactory over several years.
+     *
+     * @param jarLocation String
+     * @param attempt     int
+     */
     private static void clearSunJarFileFactoryCacheImpl(final String jarLocation, final int attempt) {
         logger.debug("Clearing Sun JarFileFactory cache for directory " + jarLocation);
 
         try {
-            Class<?> jarFileFactory = Class.forName("sun.net.www.protocol.jar.JarFileFactory");
+            final Class jarFileFactory = Class.forName("sun.net.www.protocol.jar.JarFileFactory");
 
-            synchronized (jarFileFactory) {
+            synchronized (jarFileFactory.this) {
 
                 Field fileCacheField = jarFileFactory.getDeclaredField("fileCache");
 
                 fileCacheField.setAccessible(true);
-                Map<Object, JarFile> fileCache = (Map<Object, JarFile>) fileCacheField.get(null);
+                Map fileCache = (Map) fileCacheField.get(null);
 
                 Field urlCacheField = jarFileFactory.getDeclaredField("urlCache");
                 urlCacheField.setAccessible(true);
+                Map ucf = (Map) urlCacheField.get(null);
+
+                List<URL> urls = new ArrayList<URL>();
+                File file;
+                URL url;
+
+                for (final Object item : fileCache.keySet()) {
+
+                    //Due to several different implementation changes in various JDK releases
+                    //the 'item' can be one of the following (so far):
+                    //1. A URL that points to a file.
+                    //2. An ASCII String URI that points to a file
+                    //3. A String that is used as a key to a JarFile
+
+                    url = null;
+
+                    if (item instanceof URL) {
+                        url = (URL) item;
+                    } else if (item instanceof String) {
+
+                        JarFile jf = (JarFile) fileCache.get(item);
+
+                        if (null != jf) {
+                            url = (URL) ucf.get(jf);
+                            jf.close();
+                        } else {
+                            //This may now also be a plain ASCII URI in later JDKs
+                            try {
+                                url = new URI((String) item).toURL();
+                            } catch (Exception e) {
+                                logger.warning("Don't know how to handle object: " + item.toString() + " of type: " + item.getClass().getCanonicalName() + " from Sun JarFileFactory cache, skipping");
+                                continue;
+                            }
+                        }
 
-                Map<JarFile, Object> ucf = (Map<JarFile, Object>) urlCacheField.get(null);
+                    } else {
+                        logger.warning("Don't know how to handle object: " + item.toString() + " of type: " + item.getClass().getCanonicalName() + " in Sun JarFileFactory cache, skipping");
+                        continue;
+                    }
 
-                List<Object> removedKeys = new ArrayList<Object>();
-                for (Map.Entry<Object, JarFile> entry : fileCache.entrySet()) {
-                    if (isParent(jarLocation, new File(entry.getValue().getName()))) {
-                        removedKeys.add(entry.getKey());
+                    file = null;
+                    try {
+                        file = URLs.toFile(url);
+                    } catch (IllegalArgumentException e) {
+                        //unknown kind of url
+                        return;
+                    }
+                    if (null != file && null != url && isParent(jarLocation, file)) {
+                        urls.add(url);
                     }
                 }
 
-                for(Object key : removedKeys) {
-                    JarFile jarFile = fileCache.remove(key);
-                    if(jarFile != null) {
-                        ucf.remove(jarFile);
-                        try {
-                            jarFile.close();
-                        } catch (Throwable e) {
-                            //Ignore
+                JarFile jarFile;
+                String key;
+                for (final URL jar : urls) {
+
+                    //Fudge together a sun.net.www.protocol.jar.JarFileFactory compatible key option...
+                    key = ("file:///" + new File(URI.create(jar.toString())).getAbsolutePath().replace('\\', '/'));
+                    jarFile = (JarFile) fileCache.remove(key);
+
+                    if (jarFile == null) {
+
+                        //Try next known option...
+                        key = jar.toExternalForm();
+                        jarFile = (JarFile) fileCache.remove(key);
+
+                        if (jarFile == null) {
+
+                            //Try next known option...
+                            jarFile = (JarFile) fileCache.remove(jar);
+
+                            if (jarFile == null) {
+                                //To be continued...
+                                //If you find another 'fileCache.remove(?)' option then add it here.
+                                continue;
+                            }
                         }
                     }
+
+                    ucf.remove(jarFile);
+
+                    try {
+                        jarFile.close();
+                    } catch (Throwable e) {
+                        //Ignore
+                    }
                 }
             }
         } catch (ConcurrentModificationException e) {
@@ -374,8 +447,8 @@ public class ClassLoaderUtil {
      * @param clazz     the name of the class containing the cache field
      * @param fieldName the name of the cache field
      */
-    public static void clearSunSoftCache(Class clazz, String fieldName) {
-        synchronized (clazz) {
+    public static void clearSunSoftCache(final Class clazz, String fieldName) {
+        synchronized (clazz.this) {
             try {
                 Field field = clazz.getDeclaredField(fieldName);
                 field.setAccessible(true);



Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by AndyG <an...@orprovision.com>.
The test for this can only really be tested on Win machines. I have a test on
my dev box that does the following:

1. Start
2. Deploy app
3. Undeploy app
4. Check that app files can be or are deleted
5. Repeat steps 2 through 4 several times
6. Stop

I am using a custom deployer (based loosely on the shipped Deployer) and
redeploy 50 times (which exposes memory leaks very well)

--
View this message in context: http://openejb.979440.n4.nabble.com/Re-svn-commit-r1146747-openejb-trunk-openejb3-container-openejb-core-src-main-java-org-apache-openeja-tp3674308p3680132.html
Sent from the OpenEJB Dev mailing list archive at Nabble.com.

Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by AndyG <an...@orprovision.com>.
>  That is actually what I did in the reverted changes. 

Not saying all the changes you made were completely wrong? the revert was
simply back to the way it was before (working) until a more stable solution
was found - which is what we are doing right? If I put your changes back 'as
is' in then then it does not work any more for whatever reason. I.e I cannot
redeploy an application any more.

The ConcurrentModifiedException was preventing the actual JarFile from being
removed or closed even after 5 iterations and files remained locked.

If you want to modify your code taking into account that 'remove' on either
of the maps is likely to throw a Throwable at least once during any
iteration and put it back then would be great :-)

--
View this message in context: http://openejb.979440.n4.nabble.com/Re-svn-commit-r1146747-openejb-trunk-openejb3-container-openejb-core-src-main-java-org-apache-openeja-tp3674308p3680114.html
Sent from the OpenEJB Dev mailing list archive at Nabble.com.

Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by Ivan <xh...@gmail.com>.
Thanks. :-)

2011/7/20 AndyG <an...@orprovision.com>

> I 'almost' had time to work on this today ...but then got dragged off in
> another direction. Brain carried on pondering though so here's my thoughts.
>
> I am thinking that we only need to look at and use the JarFile objects to
> find a match.
>
>
   That is actually what I did in the reverted changes.


> So, take a clone of both maps. Iterate over the keys in urlCache and the
> values in fileCache. Those JarFiles that match our path get removed and
> closed.
>
>
  Create a snapshot should decrease the possibility of
ConcurrentModifiedException, and think that is only what we could do now


> --
> View this message in context:
> http://openejb.979440.n4.nabble.com/Re-svn-commit-r1146747-openejb-trunk-openejb3-container-openejb-core-src-main-java-org-apache-openeja-tp3674308p3678553.html
> Sent from the OpenEJB Dev mailing list archive at Nabble.com.
>



-- 
Ivan

Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by AndyG <an...@orprovision.com>.
I 'almost' had time to work on this today ...but then got dragged off in
another direction. Brain carried on pondering though so here's my thoughts.

I am thinking that we only need to look at and use the JarFile objects to
find a match.

So, take a clone of both maps. Iterate over the keys in urlCache and the
values in fileCache. Those JarFiles that match our path get removed and
closed.

--
View this message in context: http://openejb.979440.n4.nabble.com/Re-svn-commit-r1146747-openejb-trunk-openejb3-container-openejb-core-src-main-java-org-apache-openeja-tp3674308p3678553.html
Sent from the OpenEJB Dev mailing list archive at Nabble.com.

Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by Ivan <xh...@gmail.com>.
Thanks for so detailed explanation. So, we have two maps, one is fileCache,
its key type is unknown (might be string/uri/url etc), and the value type is
JarFile. Also. another map is ucf, its key type is JarFile, and its value
type is URL.
I am not sure why the line JarFile jarFile = fileCache.remove(key) is not
safe, as all those keys are got from the same map fileCache from the codes :
--->
for (Map.Entry<Object, JarFile> entry : fileCache.entrySet()) {
    if (isParent(jarLocation, new File(entry.getValue().getName()))) {
        removedKeys.add(entry.getKey());
    }
}
<---
For the ConcurrentModifiedException , I think that there is no way to avoid
totally, as it looks that no way to lock the two maps. One possible way is
to create a snapshot in the first loop, which I saw you did the similar
thing in one of your comments, while we might still get exception in the
process of creating the snapshot. Thoughts ?
Thanks.


2011/7/19 AndyG <an...@orprovision.com>

> So basically what I am saying is:
>
> for(Object key : removedKeys) {
>    JarFile jarFile = fileCache.remove(key);
>
> ... Is not safe, as Object key may not be the actual key in the other map
> (even though it was intended to be so)
>
> --
> View this message in context:
> http://openejb.979440.n4.nabble.com/Re-svn-commit-r1146747-openejb-trunk-openejb3-container-openejb-core-src-main-java-org-apache-openeja-tp3674308p3677845.html
> Sent from the OpenEJB Dev mailing list archive at Nabble.com.
>



-- 
Ivan

Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by AndyG <an...@orprovision.com>.
So basically what I am saying is:

for(Object key : removedKeys) {
    JarFile jarFile = fileCache.remove(key); 

... Is not safe, as Object key may not be the actual key in the other map
(even though it was intended to be so)

--
View this message in context: http://openejb.979440.n4.nabble.com/Re-svn-commit-r1146747-openejb-trunk-openejb3-container-openejb-core-src-main-java-org-apache-openeja-tp3674308p3677845.html
Sent from the OpenEJB Dev mailing list archive at Nabble.com.

Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by AndyG <an...@orprovision.com>.
The code is far from optimal and does require a clean up and a second look
for sure. I am on it already. The revert was back to something that was
known to be working until a better solution is implemented. The changes you
made broke redeployment on my dev box - Files were back to being locked, and
the ConcurrentModifiedException occurring.

The original issue stemmed from the fact that unloading began to not release
jar files in 'some' circumstances. In fact at one point 'no' jar files were
released by the VM and that led to a whole lot of 'anti jar locking' code
which is imho not required if we get this code really optimised. The problem
was introduced by the VM.

Sun changed sun.net.www.protocol.jar.JarFileFactory several times and access
was not consistent - I think their guys just kept toying with it which led
to the key confusion.

The ConcurrentModifiedException stems from the fact that removing a single
jar may also trigger the removal of other related jars internally. As we can
only do this via reflection there is no 'real' way to synchronize
internally, which is why I added the retry recursion. As a quick fix I now
copy the keys in the last commit, which should at least prevent a
ConcurrentModifiedException (I was sure I had that already?) on the OpenEJB
side.

There are two maps 'fileCache' (the url to file cache) and 'urlCache' (the
file to url cache) which basically do the same (hold a reference to the
physical JarFile file, but in reverse). Another issue is that JarFile
implementation signatures have changed several times, so using it as a key
has not been reliable. 

Using Map&lt;JarFile, URL&gt; and Map&lt;URL, JarFile&gt; is not going to
work on all 1.6 runtimes as these maps have used different keys internally.
The urlCache key is 'now' the current JarFile. The fileCache key is now a
URL. I'll talk about past keys in a mo.

Both fileCache.put() and urlCache.put() have not been consistent, meaning
that there may be mixed key types even within the same runtime (Generics
would have fixed this, but the current runtime still uses an untyped
HashMap!).

This actual file locking issue is purely a Win OS issue. Under Linux/mac
there is no file locking on loaded jars. They can be deleted and the VM will
throw out an internal stacktrace - Putting them back is just fooling the VM
into reloading. I am sure this is bad, but so far everyone seems to get away
with it. 

In the for (final Object item : keys) iteration you are right about not
closing the JarFile, as the code should just be looking for and building
valid URLs - Not sure how the close got in there! The URLs are only added to
the urls list if they match the criteria (null != file && null != url &&
isParent(jarLocation, file)). The scope for change here is that we are
basically blind as to what type is being returned across all runtime/os
versions, and need to try and cater for all reasonable options.

The second iteration for (final URL jar : urls) should do everything
possible to locate and remove the actual references in the maps and close
the JarFile.

So far I have seen the following used as mixed keys internally:

1. A broken file URI String starting with file:/// - Note the three forward
slashes.
2. JarFile.toExternalForm()
3. JarFile object
4. URL object
5. URL String
6. URI object.
7. URI String
8. A File

So it is possible to get the URL object from the urlCache map but it may be
used as a String key in the other map?

The clean up just requires that the remove method is called on the fileCache
using the following keys.

 JarFile
 JarFile.toExternalForm()
 JarFile.toString()
 URL
 URL.toString()
 URL.toExternalForm()
 URI
 URI.toString()
 URI.toASCIIString()
 ("file:///" + new
File(URI.create(jar.toString())).getAbsolutePath().replace('\\', '/'));




--
View this message in context: http://openejb.979440.n4.nabble.com/Re-svn-commit-r1146747-openejb-trunk-openejb3-container-openejb-core-src-main-java-org-apache-openeja-tp3674308p3677829.html
Sent from the OpenEJB Dev mailing list archive at Nabble.com.

Re: svn commit: r1146747 - /openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java

Posted by Ivan <xh...@gmail.com>.
Hi, I have a few questions for the changes. In OPENEJB-1624, I made some
changes below, since they might be different keys among various JDK, while
not just use a list to host all those keys ? And use the url from jar file
to determine whether the jar file is required to remove from the cache.
 Could you please show me what I missed ? I noticed some
ConcurrentModifiedException sometimes, is it the issue you found ?
Also, it looks to me that some thing might be wrong with the current codes :
a. Should not close jar file while iteratoring the fileCache, as from the
design of the methods, only those jar files belong to the specified folder
should be cleared.
b. Since there might be different key types in the fileCache map, it always
use the string key to remove it from the fileCache later, is it correct ?


--->
 Map<Object, JarFile> fileCache = (Map<Object, JarFile>)
fileCacheField.get(null);
 Map<JarFile, Object> ucf = (Map<JarFile, Object>) urlCacheField.get(null);
 List<Object> removedKeys = new ArrayList<Object>();
for (Map.Entry<Object, JarFile> entry : fileCache.entrySet()) {
    if (isParent(jarLocation, new File(entry.getValue().getName()))) {
        removedKeys.add(entry.getKey());
    }
}
for(Object key : removedKeys) {
    JarFile jarFile = fileCache.remove(key);
    if(jarFile != null) {
        ucf.remove(jarFile);
        try {
            jarFile.close();
        } catch (Throwable e) {
         //Ignore
    }
}


2011/7/14 <an...@apache.org>

> Author: andygumbrecht
> Date: Thu Jul 14 15:13:14 2011
> New Revision: 1146747
>
> URL: http://svn.apache.org/viewvc?rev=1146747&view=rev
> Log:
> Revert breaking change and improve comments on
> clearSunJarFileFactoryCacheImpl to avoid the same.
>
> Due to several different implementation changes in various JDK releases
> the 'item' can be one of the following (so far):
> 1. A URL that points to a file.
> 2. An ASCII String URI that points to a file
> 3. A String that is used as a key to a JarFile
>
> Sun/Oracle may continue to change the implementation.
> In JDK 7 there are new features to unload jars and classes.
>
> Modified:
>
>  openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java
>
> Modified:
> openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java
> URL:
> http://svn.apache.org/viewvc/openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java?rev=1146747&r1=1146746&r2=1146747&view=diff
>
> ==============================================================================
> ---
> openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java
> (original)
> +++
> openejb/trunk/openejb3/container/openejb-core/src/main/java/org/apache/openejb/ClassLoaderUtil.java
> Thu Jul 14 15:13:14 2011
> @@ -17,6 +17,12 @@
>  */
>  package org.apache.openejb;
>
> +import org.apache.openejb.core.TempClassLoader;
> +import org.apache.openejb.util.LogCategory;
> +import org.apache.openejb.util.Logger;
> +import org.apache.openejb.util.URLs;
> +import org.apache.openejb.util.UrlCache;
> +
>  import java.beans.Introspector;
>  import java.io.File;
>  import java.io.ObjectInputStream;
> @@ -24,26 +30,14 @@ import java.io.ObjectOutputStream;
>  import java.io.ObjectStreamClass;
>  import java.lang.reflect.Field;
>  import java.lang.reflect.Method;
> +import java.net.URI;
>  import java.net.URL;
>  import java.net.URLClassLoader;
>  import java.security.AccessController;
>  import java.security.PrivilegedAction;
> -import java.util.ArrayList;
> -import java.util.ConcurrentModificationException;
> -import java.util.HashMap;
> -import java.util.Iterator;
> -import java.util.LinkedHashSet;
> -import java.util.List;
> -import java.util.Map;
> -import java.util.Set;
> -import java.util.Vector;
> +import java.util.*;
>  import java.util.jar.JarFile;
>
> -import org.apache.openejb.core.TempClassLoader;
> -import org.apache.openejb.util.LogCategory;
> -import org.apache.openejb.util.Logger;
> -import org.apache.openejb.util.UrlCache;
> -
>  /**
>  * @version $Revision$ $Date$
>  */
> @@ -281,8 +275,7 @@ public class ClassLoaderUtil {
>
>     public static URLClassLoader createTempClassLoader(String appId, URL[]
> urls, ClassLoader parent) {
>         URLClassLoader classLoader = createClassLoader(appId, urls,
> parent);
> -        TempClassLoader tempClassLoader = new
> TempClassLoader(classLoader);
> -        return tempClassLoader;
> +        return new TempClassLoader(classLoader);
>     }
>
>     /**
> @@ -302,41 +295,121 @@ public class ClassLoaderUtil {
>         clearSunJarFileFactoryCacheImpl(jarLocation, 5);
>     }
>
> +    /**
> +     * Due to several different implementation changes in various JDK
> releases the code here is not as
> +     * straight forward as reflecting debug items in your current runtime.
> There have even been breaking changes
> +     * between 1.6 runtime builds, let alone 1.5.
> +     * <p/>
> +     * If you discover a new issue here please be careful to ensure the
> existing functionality is 'extended' and not
> +     * just replaced to match your runtime observations.
> +     * <p/>
> +     * If you want to look at the mess that leads up to this then follow
> the source code changes made to
> +     * the class sun.net.www.protocol.jar.JarFileFactory over several
> years.
> +     *
> +     * @param jarLocation String
> +     * @param attempt     int
> +     */
>     private static void clearSunJarFileFactoryCacheImpl(final String
> jarLocation, final int attempt) {
>         logger.debug("Clearing Sun JarFileFactory cache for directory " +
> jarLocation);
>
>         try {
> -            Class<?> jarFileFactory =
> Class.forName("sun.net.www.protocol.jar.JarFileFactory");
> +            final Class jarFileFactory =
> Class.forName("sun.net.www.protocol.jar.JarFileFactory");
>
> -            synchronized (jarFileFactory) {
> +            synchronized (jarFileFactory.this) {
>
>                 Field fileCacheField =
> jarFileFactory.getDeclaredField("fileCache");
>
>                 fileCacheField.setAccessible(true);
> -                Map<Object, JarFile> fileCache = (Map<Object, JarFile>)
> fileCacheField.get(null);
> +                Map fileCache = (Map) fileCacheField.get(null);
>
>                 Field urlCacheField =
> jarFileFactory.getDeclaredField("urlCache");
>                 urlCacheField.setAccessible(true);
> +                Map ucf = (Map) urlCacheField.get(null);
> +
> +                List<URL> urls = new ArrayList<URL>();
> +                File file;
> +                URL url;
> +
> +                for (final Object item : fileCache.keySet()) {
> +
> +                    //Due to several different implementation changes in
> various JDK releases
> +                    //the 'item' can be one of the following (so far):
> +                    //1. A URL that points to a file.
> +                    //2. An ASCII String URI that points to a file
> +                    //3. A String that is used as a key to a JarFile
> +
> +                    url = null;
> +
> +                    if (item instanceof URL) {
> +                        url = (URL) item;
> +                    } else if (item instanceof String) {
> +
> +                        JarFile jf = (JarFile) fileCache.get(item);
> +
> +                        if (null != jf) {
> +                            url = (URL) ucf.get(jf);
> +                            jf.close();
> +                        } else {
> +                            //This may now also be a plain ASCII URI in
> later JDKs
> +                            try {
> +                                url = new URI((String) item).toURL();
> +                            } catch (Exception e) {
> +                                logger.warning("Don't know how to handle
> object: " + item.toString() + " of type: " +
> item.getClass().getCanonicalName() + " from Sun JarFileFactory cache,
> skipping");
> +                                continue;
> +                            }
> +                        }
>
> -                Map<JarFile, Object> ucf = (Map<JarFile, Object>)
> urlCacheField.get(null);
> +                    } else {
> +                        logger.warning("Don't know how to handle object: "
> + item.toString() + " of type: " + item.getClass().getCanonicalName() + " in
> Sun JarFileFactory cache, skipping");
> +                        continue;
> +                    }
>
> -                List<Object> removedKeys = new ArrayList<Object>();
> -                for (Map.Entry<Object, JarFile> entry :
> fileCache.entrySet()) {
> -                    if (isParent(jarLocation, new
> File(entry.getValue().getName()))) {
> -                        removedKeys.add(entry.getKey());
> +                    file = null;
> +                    try {
> +                        file = URLs.toFile(url);
> +                    } catch (IllegalArgumentException e) {
> +                        //unknown kind of url
> +                        return;
> +                    }
> +                    if (null != file && null != url &&
> isParent(jarLocation, file)) {
> +                        urls.add(url);
>                     }
>                 }
>
> -                for(Object key : removedKeys) {
> -                    JarFile jarFile = fileCache.remove(key);
> -                    if(jarFile != null) {
> -                        ucf.remove(jarFile);
> -                        try {
> -                            jarFile.close();
> -                        } catch (Throwable e) {
> -                            //Ignore
> +                JarFile jarFile;
> +                String key;
> +                for (final URL jar : urls) {
> +
> +                    //Fudge together a
> sun.net.www.protocol.jar.JarFileFactory compatible key option...
> +                    key = ("file:///" + new
> File(URI.create(jar.toString())).getAbsolutePath().replace('\\', '/'));
> +                    jarFile = (JarFile) fileCache.remove(key);
> +
> +                    if (jarFile == null) {
> +
> +                        //Try next known option...
> +                        key = jar.toExternalForm();
> +                        jarFile = (JarFile) fileCache.remove(key);
> +
> +                        if (jarFile == null) {
> +
> +                            //Try next known option...
> +                            jarFile = (JarFile) fileCache.remove(jar);
> +
> +                            if (jarFile == null) {
> +                                //To be continued...
> +                                //If you find another
> 'fileCache.remove(?)' option then add it here.
> +                                continue;
> +                            }
>                         }
>                     }
> +
> +                    ucf.remove(jarFile);
> +
> +                    try {
> +                        jarFile.close();
> +                    } catch (Throwable e) {
> +                        //Ignore
> +                    }
>                 }
>             }
>         } catch (ConcurrentModificationException e) {
> @@ -374,8 +447,8 @@ public class ClassLoaderUtil {
>      * @param clazz     the name of the class containing the cache field
>      * @param fieldName the name of the cache field
>      */
> -    public static void clearSunSoftCache(Class clazz, String fieldName) {
> -        synchronized (clazz) {
> +    public static void clearSunSoftCache(final Class clazz, String
> fieldName) {
> +        synchronized (clazz.this) {
>             try {
>                 Field field = clazz.getDeclaredField(fieldName);
>                 field.setAccessible(true);
>
>
>


-- 
Ivan