提交 baa27a20 编写于 作者: M michaelm

4167874: URL-downloaded jar files can consume all available file descriptors

Summary: added close method to URLClassLoader
Reviewed-by: alanb
上级 7c4de060
......@@ -100,6 +100,13 @@ import java.util.StringTokenizer;
* </tr>
*
* <tr>
* <td>closeClassLoader</td>
* <td>Closing of a ClassLoader</td>
* <td>Granting this permission allows code to close any URLClassLoader
* that it has a reference to.</td>
* </tr>
*
* <tr>
* <td>setSecurityManager</td>
* <td>Setting of the security manager (possibly replacing an existing one)
* </td>
......
......@@ -31,10 +31,12 @@ import java.io.File;
import java.io.FilePermission;
import java.io.InputStream;
import java.io.IOException;
import java.io.Closeable;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandlerFactory;
import java.util.Enumeration;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import java.util.jar.Manifest;
......@@ -70,7 +72,7 @@ import sun.security.util.SecurityConstants;
* @author David Connelly
* @since 1.2
*/
public class URLClassLoader extends SecureClassLoader {
public class URLClassLoader extends SecureClassLoader implements Closeable {
/* The search path for classes and resources */
URLClassPath ucp;
......@@ -85,13 +87,13 @@ public class URLClassLoader extends SecureClassLoader {
* to refer to a JAR file which will be downloaded and opened as needed.
*
* <p>If there is a security manager, this method first
* calls the security manager's <code>checkCreateClassLoader</code> method
* calls the security manager's {@code checkCreateClassLoader} method
* to ensure creation of a class loader is allowed.
*
* @param urls the URLs from which to load classes and resources
* @param parent the parent class loader for delegation
* @exception SecurityException if a security manager exists and its
* <code>checkCreateClassLoader</code> method doesn't allow
* {@code checkCreateClassLoader} method doesn't allow
* creation of a class loader.
* @see SecurityManager#checkCreateClassLoader
*/
......@@ -169,12 +171,65 @@ public class URLClassLoader extends SecureClassLoader {
acc = AccessController.getContext();
}
/**
* Closes this URLClassLoader, so that it can no longer be used to load
* new classes or resources that are defined by this loader.
* Classes and resources defined by any of this loader's parents in the
* delegation hierarchy are still accessible. Also, any classes or resources
* that are already loaded, are still accessible.
* <p>
* In the case of jar: and file: URLs, it also closes any class files,
* or JAR files that were opened by it. If another thread is loading a
* class when the {@code close} method is invoked, then the result of
* that load is undefined.
* <p>
* The method makes a best effort attempt to close all opened files,
* by catching {@link IOException}s internally. Unchecked exceptions
* and errors are not caught. Calling close on an already closed
* loader has no effect.
* <p>
* @throws IOException if closing any file opened by this class loader
* resulted in an IOException. Any such exceptions are caught, and a
* single IOException is thrown after the last file has been closed.
* If only one exception was thrown, it will be set as the <i>cause</i>
* of this IOException.
*
* @throws SecurityException if a security manager is set, and it denies
* {@link RuntimePermission}<tt>("closeClassLoader")</tt>
*
* @since 1.7
*/
public void close() throws IOException {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(new RuntimePermission("closeClassLoader"));
}
List<IOException> errors = ucp.closeLoaders();
if (errors.isEmpty()) {
return;
}
if (errors.size() == 1) {
throw new IOException (
"Error closing URLClassLoader resource",
errors.get(0)
);
}
// Several exceptions. So, just combine the error messages
String errormsg = "Error closing resources: ";
for (IOException error: errors) {
errormsg = errormsg + "[" + error.toString() + "] ";
}
throw new IOException (errormsg);
}
/**
* Appends the specified URL to the list of URLs to search for
* classes and resources.
* <p>
* If the URL specified is <code>null</code> or is already in the
* list of URLs, then invoking this method has no effect.
* list of URLs, or if this loader is closed, then invoking this
* method has no effect.
*
* @param url the URL to be added to the search path of URLs
*/
......@@ -199,7 +254,8 @@ public class URLClassLoader extends SecureClassLoader {
*
* @param name the name of the class
* @return the resulting class
* @exception ClassNotFoundException if the class could not be found
* @exception ClassNotFoundException if the class could not be found,
* or if the loader is closed.
*/
protected Class<?> findClass(final String name)
throws ClassNotFoundException
......@@ -370,7 +426,7 @@ public class URLClassLoader extends SecureClassLoader {
*
* @param name the name of the resource
* @return a <code>URL</code> for the resource, or <code>null</code>
* if the resource could not be found.
* if the resource could not be found, or if the loader is closed.
*/
public URL findResource(final String name) {
/*
......@@ -393,6 +449,7 @@ public class URLClassLoader extends SecureClassLoader {
* @param name the resource name
* @exception IOException if an I/O exception occurs
* @return an <code>Enumeration</code> of <code>URL</code>s
* If the loader is closed, the Enumeration will be empty.
*/
public Enumeration<URL> findResources(final String name)
throws IOException
......
......@@ -25,17 +25,7 @@
package sun.misc;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.Set;
import java.util.HashSet;
import java.util.StringTokenizer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.*;
import java.util.jar.JarFile;
import sun.misc.JarIndex;
import sun.misc.InvalidJarIndexException;
......@@ -52,12 +42,7 @@ import java.net.URLConnection;
import java.net.HttpURLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.*;
import java.security.AccessController;
import java.security.AccessControlException;
import java.security.CodeSigner;
......@@ -100,6 +85,9 @@ public class URLClassPath {
/* The jar protocol handler to use when creating new URLs */
private URLStreamHandler jarHandler;
/* Whether this URLClassLoader has been closed yet */
private boolean closed = false;
/**
* Creates a new URLClassPath for the given URLs. The URLs will be
* searched in the order specified for classes and resources. A URL
......@@ -124,6 +112,22 @@ public class URLClassPath {
this(urls, null);
}
public synchronized List<IOException> closeLoaders() {
if (closed) {
return Collections.emptyList();
}
List<IOException> result = new LinkedList<IOException>();
for (Loader loader : loaders) {
try {
loader.close();
} catch (IOException e) {
result.add (e);
}
}
closed = true;
return result;
}
/**
* Appends the specified URL to the search path of directory and JAR
* file URLs from which to load classes and resources.
......@@ -293,6 +297,9 @@ public class URLClassPath {
* if the specified index is out of range.
*/
private synchronized Loader getLoader(int index) {
if (closed) {
return null;
}
// Expand URL search path until the request can be satisfied
// or the URL stack is empty.
while (loaders.size() < index + 1) {
......@@ -453,7 +460,7 @@ public class URLClassPath {
* Inner class used to represent a loader of resources and classes
* from a base URL.
*/
private static class Loader {
private static class Loader implements Closeable {
private final URL base;
/*
......@@ -544,6 +551,12 @@ public class URLClassPath {
return getResource(name, true);
}
/*
* close this loader and release all resources
* method overridden in sub-classes
*/
public void close () throws IOException {}
/*
* Returns the local class path for this loader, or null if none.
*/
......@@ -562,6 +575,7 @@ public class URLClassPath {
private MetaIndex metaIndex;
private URLStreamHandler handler;
private HashMap<URL, Loader> lmap;
private boolean closed = false;
/*
* Creates a new JarLoader for the specified URL referring to
......@@ -604,6 +618,17 @@ public class URLClassPath {
}
}
@Override
public void close () throws IOException {
// closing is synchronized at higher level
if (!closed) {
closed = true;
// in case not already open.
ensureOpen();
jar.close();
}
}
JarFile getJarFile () {
return jar;
}
......
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
/**
* @test
* @bug 4167874
* @library ../../../../com/sun/net/httpserver
* @build FileServerHandler
* @run shell build.sh
* @run main/othervm CloseTest
* @summary URL-downloaded jar files can consume all available file descriptors
*/
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import java.util.concurrent.*;
import com.sun.net.httpserver.*;
public class CloseTest {
static void copyFile (String src, String dst) {
copyFile (new File(src), new File(dst));
}
static void copyDir (String src, String dst) {
copyDir (new File(src), new File(dst));
}
static void copyFile (File src, File dst) {
try {
if (!src.isFile()) {
throw new RuntimeException ("File not found: " + src.toString());
}
dst.delete();
dst.createNewFile();
FileInputStream i = new FileInputStream (src);
FileOutputStream o = new FileOutputStream (dst);
byte[] buf = new byte [1024];
int count;
while ((count=i.read(buf)) >= 0) {
o.write (buf, 0, count);
}
i.close();
o.close();
} catch (IOException e) {
throw new RuntimeException (e);
}
}
static void rm_minus_rf (File path) {
if (!path.exists()) {
return;
}
if (path.isFile()) {
if (!path.delete()) {
throw new RuntimeException ("Could not delete " + path);
}
} else if (path.isDirectory ()) {
String[] names = path.list();
File[] files = path.listFiles();
for (int i=0; i<files.length; i++) {
rm_minus_rf (new File(path, names[i]));
}
if (!path.delete()) {
throw new RuntimeException ("Could not delete " + path);
}
} else {
throw new RuntimeException ("Trying to delete something that isn't a file or a directory");
}
}
static void copyDir (File src, File dst) {
if (!src.isDirectory()) {
throw new RuntimeException ("Dir not found: " + src.toString());
}
if (dst.exists()) {
throw new RuntimeException ("Dir exists: " + dst.toString());
}
dst.mkdir();
String[] names = src.list();
File[] files = src.listFiles();
for (int i=0; i<files.length; i++) {
String f = names[i];
if (files[i].isDirectory()) {
copyDir (files[i], new File (dst, f));
} else {
copyFile (new File (src, f), new File (dst, f));
}
assert false;
}
}
/* expect is true if you expect to find it, false if you expect not to */
static Class loadClass (String name, URLClassLoader loader, boolean expect){
try {
Class clazz = Class.forName (name, true, loader);
if (!expect) {
throw new RuntimeException ("loadClass: "+name+" unexpected");
}
return clazz;
} catch (ClassNotFoundException e) {
if (expect) {
throw new RuntimeException ("loadClass: " +name + " not found");
}
}
return null;
}
//
// needs two jar files test1.jar and test2.jar with following structure
//
// com/foo/TestClass
// com/foo/TestClass1
// com/foo/Resource1
// com/foo/Resource2
//
// and a directory hierarchy with the same structure/contents
public static void main (String args[]) throws Exception {
String workdir = System.getProperty("test.classes");
if (workdir == null) {
workdir = args[0];
}
if (!workdir.endsWith("/")) {
workdir = workdir+"/";
}
startHttpServer (workdir+"serverRoot/");
String testjar = workdir + "test.jar";
copyFile (workdir+"test1.jar", testjar);
test (testjar, 1);
// repeat test with different implementation
// of test.jar (whose TestClass.getValue() returns 2
copyFile (workdir+"test2.jar", testjar);
test (testjar, 2);
// repeat test using a directory of files
String testdir=workdir+"testdir/";
rm_minus_rf (new File(testdir));
copyDir (workdir+"test1/", testdir);
test (testdir, 1);
testdir=workdir+"testdir/";
rm_minus_rf (new File(testdir));
copyDir (workdir+"test2/", testdir);
test (testdir, 2);
getHttpServer().stop (3);
}
// create a loader on jarfile (or directory), plus a http loader
// load a class , then look for a resource
// also load a class from http loader
// then close the loader
// check further new classes/resources cannot be loaded
// check jar (or dir) can be deleted
// check existing classes can be loaded
// check boot classes can be loaded
static void test (String name, int expectedValue) throws Exception {
URL url = new URL ("file", null, name);
URL url2 = getServerURL();
System.out.println ("Doing tests with URL: " + url + " and " + url2);
URL[] urls = new URL[2];
urls[0] = url;
urls[1] = url2;
URLClassLoader loader = new URLClassLoader (urls);
Class testclass = loadClass ("com.foo.TestClass", loader, true);
Class class2 = loadClass ("Test", loader, true); // from http
class2.newInstance();
Object test = testclass.newInstance();
Method method = testclass.getDeclaredMethods()[0]; // int getValue();
int res = (Integer) method.invoke (test);
if (res != expectedValue) {
throw new RuntimeException ("wrong value from getValue() ["+res+
"/"+expectedValue+"]");
}
// should find /resource1
URL u1 = loader.findResource ("com/foo/Resource1");
if (u1 == null) {
throw new RuntimeException ("can't find com/foo/Resource1 in test1.jar");
}
loader.close ();
// should NOT find /resource2 even though it is in jar
URL u2 = loader.findResource ("com/foo/Resource2");
if (u2 != null) {
throw new RuntimeException ("com/foo/Resource2 unexpected in test1.jar");
}
// load tests
loadClass ("com.foo.TestClass1", loader, false);
loadClass ("com.foo.TestClass", loader, true);
loadClass ("java.awt.Button", loader, true);
// now check we can delete the path
rm_minus_rf (new File(name));
System.out.println (" ... OK");
}
static HttpServer httpServer;
static HttpServer getHttpServer() {
return httpServer;
}
static URL getServerURL () throws Exception {
int port = httpServer.getAddress().getPort();
String s = "http://127.0.0.1:"+port+"/";
return new URL(s);
}
static void startHttpServer (String docroot) throws Exception {
httpServer = HttpServer.create (new InetSocketAddress(0), 10);
HttpContext ctx = httpServer.createContext (
"/", new FileServerHandler(docroot)
);
httpServer.start();
}
}
test1 and test2 contain two different implementations of the same
classes. They are compiled and placed into two different target directories
and two jar files test1.jar and test2.jar.
The same class is in both jars/directories, but returns a different result
from the TestClass.getValue() method. The test does the following
1. copy test1.jar to a working directory and call it test.jar
2. load class and invoke method (checking result)
3. close the loader
4. delete test.jar (check delete succeeds)
5. copy test2.jar to same dir and repeat the test
6. The two tests are then repeated by copying the directories
test1 and test2.
The loader also includes a http:// URL in its search path and a http
server is used to serve the required class.
serverRoot is used as the root directory for the http server.
#!/bin/sh
#
# Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
# CA 95054 USA or visit www.sun.com if you need additional information or
# have any questions.
#
#
#
# This script builds the test files for the test
# but not the actual test sources themselves.
#
if [ "${TESTSRC}" = "" ]
then
echo "TESTSRC not set. Test cannot execute. Failed."
exit 1
fi
echo "TESTSRC=${TESTSRC}"
if [ "${TESTJAVA}" = "" ]
then
echo "TESTJAVA not set. Test cannot execute. Failed."
exit 1
fi
echo "TESTJAVA=${TESTJAVA}"
if [ "${TESTCLASSES}" = "" ]
then
echo "TESTCLASSES not set. Test cannot execute. Failed."
exit 1
fi
JAVAC="${TESTJAVA}/bin/javac"
JAR="${TESTJAVA}/bin/jar"
rm -rf ${TESTCLASSES}/test1
rm -rf ${TESTCLASSES}/test2
rm -rf ${TESTCLASSES}/serverRoot
mkdir -p ${TESTCLASSES}/test1/com/foo
mkdir -p ${TESTCLASSES}/test2/com/foo
mkdir -p ${TESTCLASSES}/serverRoot
cd ${TESTSRC}/test1/com/foo
cp * ${TESTCLASSES}/test1/com/foo
cd ${TESTCLASSES}/test1
${JAVAC} com/foo/*.java
${JAR} cvf ../test1.jar com/foo/*.class com/foo/Resource*
cd ${TESTSRC}/test2/com/foo
cp * ${TESTCLASSES}/test2/com/foo
cd ${TESTCLASSES}/test2
${JAVAC} com/foo/*.java
${JAR} cvf ../test2.jar com/foo/*.class com/foo/Resource*
cp ${TESTSRC}/serverRoot/Test.java ${TESTCLASSES}/serverRoot
cd ${TESTCLASSES}/serverRoot
${JAVAC} Test.java
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
public class Test {
public Test () {
System.out.println ("Test created");
}
}
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.foo;
public class TestClass {
public int getValue () {
return 1;
}
}
/*
public class TestClass {
public int getValue () {
return 2;
}
}
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.foo;
public class TestClass1 {}
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.foo;
/*
public class TestClass {
public int getValue () {
return 1;
}
}
*/
public class TestClass {
public int getValue () {
return 2;
}
}
/*
* Copyright 2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package com.foo;
public class TestClass1 {}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册