Repository: mjiderhamn/classloader-leak-prevention Branch: master Commit: dbf68c5fc129 Files: 153 Total size: 329.3 KB Directory structure: gitextract_ovazmkyr/ ├── .gitignore ├── .mvn/ │ └── wrapper/ │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── .travis.yml ├── LICENSE.txt ├── README.md ├── classloader-leak-prevention/ │ ├── classloader-leak-prevention-core/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ └── java/ │ │ │ └── se/ │ │ │ └── jiderhamn/ │ │ │ └── classloader/ │ │ │ └── leak/ │ │ │ └── prevention/ │ │ │ ├── ClassLoaderLeakPreventor.java │ │ │ ├── ClassLoaderLeakPreventorFactory.java │ │ │ ├── ClassLoaderPreMortemCleanUp.java │ │ │ ├── JULLogger.java │ │ │ ├── Logger.java │ │ │ ├── MustBeAfter.java │ │ │ ├── PreClassLoaderInitiator.java │ │ │ ├── ReplaceDOMNormalizerSerializerAbortException.java │ │ │ ├── StdLogger.java │ │ │ ├── cleanup/ │ │ │ │ ├── ApacheCommonsLoggingCleanUp.java │ │ │ │ ├── BeanELResolverCleanUp.java │ │ │ │ ├── BeanIntrospectorCleanUp.java │ │ │ │ ├── BeanValidationCleanUp.java │ │ │ │ ├── DefaultAuthenticatorCleanUp.java │ │ │ │ ├── DriverManagerCleanUp.java │ │ │ │ ├── GeoToolsCleanUp.java │ │ │ │ ├── IIOServiceProviderCleanUp.java │ │ │ │ ├── IntrospectionUtilsCleanUp.java │ │ │ │ ├── JDK8151486CleanUp.java │ │ │ │ ├── JacksonCleanUp.java │ │ │ │ ├── JavaServerFaces2746CleanUp.java │ │ │ │ ├── JavaUtilLoggingLevelCleanUp.java │ │ │ │ ├── JavaxSecurityAuthLoginConfigurationCleanUp.java │ │ │ │ ├── JceSecurityCleanUp.java │ │ │ │ ├── KeepAliveTimerCacheCleanUp.java │ │ │ │ ├── MBeanCleanUp.java │ │ │ │ ├── MXBeanNotificationListenersCleanUp.java │ │ │ │ ├── MoxyCleanUp.java │ │ │ │ ├── MultiThreadedHttpConnectionManagerCleanUp.java │ │ │ │ ├── ObjectStreamClassCleanup.java │ │ │ │ ├── PropertyEditorCleanUp.java │ │ │ │ ├── ProxySelectorCleanUp.java │ │ │ │ ├── ReactorNettyHttpResourcesCleanUp.java │ │ │ │ ├── ResourceBundleCleanUp.java │ │ │ │ ├── RmiTargetsCleanUp.java │ │ │ │ ├── SAAJEnvelopeFactoryParserPoolCleanUp.java │ │ │ │ ├── SecurityProviderCleanUp.java │ │ │ │ ├── ShutdownHookCleanUp.java │ │ │ │ ├── StopThreadsCleanUp.java │ │ │ │ ├── ThreadGroupCleanUp.java │ │ │ │ ├── ThreadGroupContextCleanUp.java │ │ │ │ ├── ThreadLocalCleanUp.java │ │ │ │ ├── WarningThreadLocalCleanUp.java │ │ │ │ └── X509TrustManagerImplUnparseableExtensionCleanUp.java │ │ │ └── preinit/ │ │ │ ├── AwtToolkitInitiator.java │ │ │ ├── DatatypeConverterImplInitiator.java │ │ │ ├── DocumentBuilderFactoryInitiator.java │ │ │ ├── JarUrlConnectionInitiator.java │ │ │ ├── Java2dDisposerInitiator.java │ │ │ ├── Java2dRenderQueueInitiator.java │ │ │ ├── JavaxSecurityLoginConfigurationInitiator.java │ │ │ ├── JdbcDriversInitiator.java │ │ │ ├── LdapPoolManagerInitiator.java │ │ │ ├── OracleJdbcThreadInitiator.java │ │ │ ├── SecurityPolicyInitiator.java │ │ │ ├── SecurityProvidersInitiator.java │ │ │ ├── SunAwtAppContextInitiator.java │ │ │ └── SunGCInitiator.java │ │ └── test/ │ │ ├── java/ │ │ │ └── se/ │ │ │ └── jiderhamn/ │ │ │ └── classloader/ │ │ │ └── leak/ │ │ │ └── prevention/ │ │ │ ├── ClassLoaderLeakPreventorFactoryTest.java │ │ │ ├── PreventionsTestBase.java │ │ │ ├── StopThreadsCleanUp_TimerTest.java │ │ │ ├── cleanup/ │ │ │ │ ├── BeanELResolverCleanUpTest.java │ │ │ │ ├── BeanIntrospectorCleanUpTest.java │ │ │ │ ├── BeanValidationCleanUpTest.java │ │ │ │ ├── ClassLoaderPreMortemCleanUpTestBase.java │ │ │ │ ├── DefaultAuthenticatorCleanUpTest.java │ │ │ │ ├── DriverManagerCleanUpTest.java │ │ │ │ ├── GeoToolsCleanUpTest.java │ │ │ │ ├── IIOServiceProviderCleanUpTest.java │ │ │ │ ├── ImageIOMockImageInputStreamSPI.java │ │ │ │ ├── JDK8151486CleanUpTest.java │ │ │ │ ├── JacksonCleanUpTest.java │ │ │ │ ├── JavaServerFaces2746CleanUpTest.java │ │ │ │ ├── JavaUtilLoggingLevelCleanUpTest.java │ │ │ │ ├── JavaxSecurityAuthLoginConfigurationCleanUpTest.java │ │ │ │ ├── JceSecurityCleanUpTest.java │ │ │ │ ├── MBeanCleanUpTest.java │ │ │ │ ├── MXBeanNotificationListenersCleanUpTest.java │ │ │ │ ├── MXBeanNotificationListenersCleanUp_ListenerWrapperTest.java │ │ │ │ ├── MoxyCleanUpTest.java │ │ │ │ ├── MultiThreadedHttpConnectionManagerCleanUpTest.java │ │ │ │ ├── ObjectStreamClassCleanupTest.java │ │ │ │ ├── PropertyEditorCleanUpTest.java │ │ │ │ ├── ProxySelectorCleanUpTest.java │ │ │ │ ├── ReplaceDOMNormalizerSerializerAbortExceptionCleanUpTest.java │ │ │ │ ├── SAAJEnvelopeFactoryParserPoolCleanUpTest.java │ │ │ │ ├── SecurityProviderCleanUpTest.java │ │ │ │ ├── ShutdownHookCleanUpTest.java │ │ │ │ ├── StopThreadsCleanUp_MultiThreadedHttpConnectionManagerTest.java │ │ │ │ ├── StopThreadsCleanUp_PostgresqlJdbcTest.java │ │ │ │ ├── StopThreadsCleanUp_Runnable.java │ │ │ │ ├── StopThreadsClenup_ExecutorTest.java │ │ │ │ ├── ThreadGroupCleanUpTest.java │ │ │ │ ├── ThreadLocalCleanUpTest.java │ │ │ │ ├── ThreadLocalWithNestedRefValueCleanUpTest.java │ │ │ │ ├── ThreadLocalWithRefValueCleanUpTest.java │ │ │ │ ├── X509TrustManagerImplUnparseableExtensionCleanUpTest.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── preinit/ │ │ │ ├── AwtToolkitInitiatorTest.java │ │ │ ├── DatatypeConverterImplInitiatorTest.java │ │ │ ├── DocumentBuilderFactoryInitiatorTest.java │ │ │ ├── Java2dDisposerInitiatorTest.java │ │ │ ├── Java2dRenderQueueInitiatorTest.java │ │ │ ├── JavaxSecurityLoginConfigurationInitiatorTest.java │ │ │ ├── JdbcDriversInitiatorTest.java │ │ │ ├── LdapPoolManagerInitiatorTest.java │ │ │ ├── OracleJdbcThreadInitiatorTest.java │ │ │ ├── PreClassLoaderInitiatorTestBase.java │ │ │ ├── ReplaceDOMNormalizerSerializerAbortExceptionInitiatorTest.java │ │ │ ├── SecurityPolicyInitiatorTest.java │ │ │ ├── SecurityProvidersInitiatorTest.java │ │ │ ├── SunAwtAppContextInitiatorTest.java │ │ │ └── SunGCInitiatorTest.java │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── javax.imageio.spi.ImageInputStreamSpi │ │ ├── spi-cacert-2008.crt │ │ └── spi-cacert-2008.keystore │ ├── classloader-leak-prevention-servlet/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── se/ │ │ └── jiderhamn/ │ │ └── classloader/ │ │ └── leak/ │ │ └── prevention/ │ │ └── ClassLoaderLeakPreventorListener.java │ ├── classloader-leak-prevention-servlet3/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── se/ │ │ │ └── jiderhamn/ │ │ │ └── classloader/ │ │ │ └── leak/ │ │ │ └── prevention/ │ │ │ └── ClassLoaderLeakPreventionContainerInitializer.java │ │ └── resources/ │ │ └── META-INF/ │ │ ├── services/ │ │ │ └── javax.servlet.ServletContainerInitializer │ │ └── web-fragment.xml │ └── pom.xml ├── classloader-leak-test-framework/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── se/ │ │ └── jiderhamn/ │ │ ├── HeapDumper.java │ │ └── classloader/ │ │ ├── PackagesLoadedOutsideClassLoader.java │ │ ├── RedefiningClassLoader.java │ │ ├── ZombieMarker.java │ │ └── leak/ │ │ ├── JUnitClassloaderRunner.java │ │ ├── LeakPreventor.java │ │ └── Leaks.java │ └── test/ │ └── java/ │ ├── com/ │ │ └── classloader/ │ │ └── test/ │ │ └── CustomClass.java │ └── se/ │ └── jiderhamn/ │ └── classloader/ │ ├── RedefiningClassLoaderTest.java │ └── leak/ │ ├── JUnitClassloaderRunnerTest.java │ ├── NonLeakingTest.java │ ├── accused/ │ │ ├── CustomThreadLocalTest.java │ │ └── package-info.java │ └── known/ │ ├── CustomThreadLocalCustomValueTest.java │ ├── JEditorPaneTest.java │ └── package-info.java ├── mvnw ├── mvnw.cmd └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ target .classpath .project .settings .mvn/wrapper/maven-wrapper.jar ================================================ FILE: .mvn/wrapper/MavenWrapperDownloader.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.IOException; import java.io.InputStream; import java.net.Authenticator; import java.net.PasswordAuthentication; import java.net.URL; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.Properties; public final class MavenWrapperDownloader { private static final String WRAPPER_VERSION = "3.1.1"; private static final boolean VERBOSE = Boolean.parseBoolean( System.getenv( "MVNW_VERBOSE" ) ); /** * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. */ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; /** * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the * default one. */ private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; /** * Path where the maven-wrapper.jar will be saved to. */ private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; /** * Name of the property which should be used to override the default download url for the wrapper. */ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; public static void main( String[] args ) { if ( args.length == 0 ) { System.err.println( " - ERROR projectBasedir parameter missing" ); System.exit( 1 ); } log( " - Downloader started" ); final String dir = args[0].replace( "..", "" ); // Sanitize path final Path projectBasedir = Paths.get( dir ).toAbsolutePath().normalize(); if ( !Files.isDirectory( projectBasedir, LinkOption.NOFOLLOW_LINKS ) ) { System.err.println( " - ERROR projectBasedir not exists: " + projectBasedir ); System.exit( 1 ); } log( " - Using base directory: " + projectBasedir ); // If the maven-wrapper.properties exists, read it and check if it contains a custom // wrapperUrl parameter. Path mavenWrapperPropertyFile = projectBasedir.resolve( MAVEN_WRAPPER_PROPERTIES_PATH ); String url = readWrapperUrl( mavenWrapperPropertyFile ); try { Path outputFile = projectBasedir.resolve( MAVEN_WRAPPER_JAR_PATH ); createDirectories( outputFile.getParent() ); downloadFileFromURL( url, outputFile ); log( "Done" ); System.exit( 0 ); } catch ( IOException e ) { System.err.println( "- Error downloading" ); e.printStackTrace(); System.exit( 1 ); } } private static void downloadFileFromURL( String urlString, Path destination ) throws IOException { log( " - Downloading to: " + destination ); if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) { final String username = System.getenv( "MVNW_USERNAME" ); final char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); Authenticator.setDefault( new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( username, password ); } } ); } URL website = new URL( urlString ); try ( InputStream inStream = website.openStream() ) { Files.copy( inStream, destination, StandardCopyOption.REPLACE_EXISTING ); } log( " - Downloader complete" ); } private static void createDirectories(Path outputPath) throws IOException { if ( !Files.isDirectory( outputPath, LinkOption.NOFOLLOW_LINKS ) ) { Path createDirectories = Files.createDirectories( outputPath ); log( " - Directories created: " + createDirectories ); } } private static String readWrapperUrl( Path mavenWrapperPropertyFile ) { String url = DEFAULT_DOWNLOAD_URL; if ( Files.exists( mavenWrapperPropertyFile, LinkOption.NOFOLLOW_LINKS ) ) { log( " - Reading property file: " + mavenWrapperPropertyFile ); try ( InputStream in = Files.newInputStream( mavenWrapperPropertyFile, StandardOpenOption.READ ) ) { Properties mavenWrapperProperties = new Properties(); mavenWrapperProperties.load( in ); url = mavenWrapperProperties.getProperty( PROPERTY_NAME_WRAPPER_URL, DEFAULT_DOWNLOAD_URL ); } catch ( IOException e ) { System.err.println( " - ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'" ); } } log( " - Downloading from: " + url ); return url; } private static void log( String msg ) { if ( VERBOSE ) { System.out.println( msg ); } } } ================================================ FILE: .mvn/wrapper/maven-wrapper.properties ================================================ # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.5/apache-maven-3.8.5-bin.zip wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar ================================================ FILE: .travis.yml ================================================ dist: trusty language: java jdk: - oraclejdk8 - openjdk11 # Uncomment to upload heapdumps to S3 bucket; https://docs.travis-ci.com/user/uploading-artifacts/ #addons: # artifacts: # paths: # - $(ls $HOME/build/mjiderhamn/classloader-leak-prevention/classloader-leak-prevention/classloader-leak-prevention-core/target/surefire-reports/*.hprof | tr "\n" ":") cache: directories: - $HOME/.m2 ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Classloader Leak Prevention library [![Build Status](https://travis-ci.org/mjiderhamn/classloader-leak-prevention.svg?branch=master)](http://travis-ci.org/mjiderhamn/classloader-leak-prevention) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/se.jiderhamn.classloader-leak-prevention/classloader-leak-prevention-servlet3/badge.svg)](https://maven-badges.herokuapp.com/maven-central/se.jiderhamn.classloader-leak-prevention/classloader-leak-prevention-servlet3/) [![License](https://img.shields.io/badge/license-Apache2-blue.svg?style=flat)](https://github.com/mjiderhamn/classloader-leak-prevention/blob/master/LICENSE.txt) If you want to avoid the dreaded `java.lang.OutOfMemoryError: Metaspace` / `PermGen space`, just include this library into your Java EE application and it should take care of the rest! To learn more about classloader leaks, their causes, types, ways to find them and known offenders, see blog series here: http://java.jiderhamn.se/category/classloader-leaks/ ## Servlet 3.0+ In a Servlet 3.0+ environment, all you need to do is include this Maven dependency in your `.war`: ```xml se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-servlet3 2.7.0 ``` If you run into problems with the Servlet 3.0 module, try the Servlet 2.5 alternative below. Since the [Servlet spec does not guarantee the order of `ServletContainerInitializer`s](https://java.net/jira/browse/SERVLET_SPEC-79), it means this library may not initialize first and clean up last in case you have other Servlet 3.0 dependencies, which could lead to unexpected behaviour. ## Servlet 2.5 (and earlier) For Servlet 2.5 (and earlier) environments, you need to use a different Maven dependency (notice the difference in `artifactId`): ```xml se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-servlet 2.7.0 ``` You also have to add this to your `web.xml`: ```xml se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorListener ``` _Note that the name of the listener class has changed since 1.x!_ It makes sense to keep this listener "outermost" (initializing first, destroying last), so you should normally declare it before any other listeners in `web.xml`. ## Configuration The context listener used in both cases has a number of settings that can be configured with context parameters in `web.xml`: ```xml ClassLoaderLeakPreventor.stopThreads false ``` The available settings are
Parameter name Default value Description
ClassLoaderLeakPreventor.stopThreads true Should threads tied to the web app classloader be forced to stop at application shutdown?
ClassLoaderLeakPreventor.stopTimerThreads true Should Timer threads tied to the web app classloader be forced to stop at application shutdown?
ClassLoaderLeakPreventor.executeShutdownHooks true Should shutdown hooks registered from the application be executed at application shutdown?
ClassLoaderLeakPreventor.startOracleTimeoutThread true Should the oracle.jdbc.driver.OracleTimeoutPollingThread thread be forced to start with system ClassLoader, in case Oracle JDBC driver is present? This is normally a good idea, but can be disabled in case the Oracle JDBC driver is not used even though it is on the classpath.
ClassLoaderLeakPreventor.threadWaitMs 5000
(5 seconds)
No of milliseconds to wait for threads to finish execution, before stopping them.
ClassLoaderLeakPreventor.shutdownHookWaitMs 10000
(10 seconds)
No of milliseconds to wait for shutdown hooks to finish execution, before stopping them. If set to -1 there will be no waiting at all, but Thread is allowed to run until finished.
## Classloader leak detection / test framework The test framework has its own Maven module and its own documentation, see [classloader-leak-test-framework](classloader-leak-test-framework). ## Integration For non-servlet environments, please see the documentation for the [core module](classloader-leak-prevention/classloader-leak-prevention-core). ## License This project is licensed under the [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0), which allows you to include modified versions of the code in your distributed software, without having to release your source code. ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/README.md ================================================ # Classloader Leak Prevention library integration _This document is about using the Classloader Leak Prevention library in a non-servlet environment. For general information and use in servlet environments, please see the [root README.md](../../README.md)_ Version 2.x of the Classloader Leak Prevention library has been refactored to allow for use outside a servlet environment, or by all means in a servlet container (Java EE application server). # Setting up What you will want to do is first create a [ClassLoaderLeakPreventorFactory](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorFactory.java) instance, either by using the default constructor that will configure the system `ClassLoader` (`ClassLoader.getSystemClassLoader()`) to be used for pre-inits, or provide your own leak safe `ClassLoader` to the constructor. Make any configurations on the factory instance, i.e. add or remove any [cleanup](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderPreMortemCleanUp.java) or [pre-init](https://github.com/mjiderhamn/classloader-leak-prevention/blob/master/classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/PreClassLoaderInitiator.java) plugins, or change parameters of any of the default plugins. # Protect ClassLoader Then for every `ClassLoader` that needs leak protection, create a new [ClassLoaderLeakPreventor](src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventor.java) using ```java classLoaderLeakPreventor = classLoaderLeakPreventorFactory.newLeakPreventor(classLoader); ``` Before letting any code execute inside the `ClassLoader` (or at least as soon as possible), invoke ```java classLoaderLeakPreventor.runPreClassLoaderInitiators(); ``` You can reuse the same `ClassLoaderLeakPreventorFactory` for multiple `ClassLoaders`, but please be aware that any configuration changes made to plugins of the factory will affect all `ClassLoaderLeakPreventor`s created by the factory - both future and existing. If however you add or remove plugins, that will only affect new `ClassLoaderLeakPreventor`s. # Shutting down When you believe the `ClassLoader` should no longer be used, but be ready for Garbage Collection, invoke ```java classLoaderLeakPreventor.runCleanUps(); ``` on the `ClassLoaderLeakPreventor` that corresponds to the `ClassLoader`. # Example For an example how to use the framework, feel free to study the [ClassLoaderLeakPreventorListener](../classloader-leak-prevention-servlet/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorListener.java) in the `classloader-leak-prevention-servlet` module. # Maven The module is available in Maven as ```xml se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-core 2.7.0 ``` ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/pom.xml ================================================ 4.0.0 se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-parent 2.7.1-SNAPSHOT classloader-leak-prevention-core jar ClassLoader Leak Prevention library Library that prevents ClassLoader leaks / java.lang.OutOfMemoryError: PermGen space https://github.com/mjiderhamn/classloader-leak-prevention se.jiderhamn classloader-leak-test-framework 1.1.2 test commons-logging commons-logging 1.1.3 test javax.validation validation-api 1.0.0.GA test org.hibernate hibernate-validator 4.2.0.Final test org.apache.axis axis 1.4 test commons-discovery commons-discovery 0.5 test mysql mysql-connector-java 5.1.18 test javax.el el-api 2.2.1-b04 test com.sun.jersey.contribs jersey-apache-client 1.19 test org.apache.cxf cxf-rt-transports-http 2.6.10 test org.geotools gt-metadata 2.6.2 test com.sun.faces jsf-api 2.1.19 test com.sun.faces jsf-impl 2.1.19 test org.postgresql postgresql 9.4-1201-jdbc41 test org.eclipse.persistence org.eclipse.persistence.moxy 2.7.0 test com.fasterxml.jackson.core jackson-databind 2.9.2 test com.sun.xml.messaging.saaj saaj-impl 1.4.0 provided javax.xml.bind jaxb-api 2.2.11 test com.sun.xml.bind jaxb-core 2.2.11 test com.sun.xml.bind jaxb-impl 2.2.11 test javax.activation activation 1.1.1 test org.apache.maven.plugins maven-jar-plugin 3.0.2 test-jar ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventor.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.security.*; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * This class helps prevent classloader leaks. * @author Mattias Jiderhamn */ @SuppressWarnings("WeakerAccess") public class ClassLoaderLeakPreventor { /** Default no of milliseconds to wait for threads to finish execution */ public static final int THREAD_WAIT_MS_DEFAULT = 5 * 1000; // 5 seconds private static final ProtectionDomain[] NO_DOMAINS = new ProtectionDomain[0]; private static final AccessControlContext NO_DOMAINS_ACCESS_CONTROL_CONTEXT = new AccessControlContext(NO_DOMAINS); /** {@link ClassLoader#isAncestor(ClassLoader)} */ private final Method java_lang_ClassLoader_isAncestor; /** {@link ClassLoader#isAncestorOf(ClassLoader)} of IBM JRE */ private final Method java_lang_ClassLoader_isAncestorOf; private final Field java_security_AccessControlContext$combiner; private final Field java_security_AccessControlContext$parent; private final Field java_security_AccessControlContext$privilegedContext; /** * {@link ClassLoader} to be used when invoking the {@link PreClassLoaderInitiator}s. * This will normally be the {@link ClassLoader#getSystemClassLoader()}, but could be any other framework or * app server classloader. Normally, but not necessarily, a parent of {@link #classLoader}. */ private final ClassLoader leakSafeClassLoader; /** The {@link ClassLoader} we want to avoid leaking */ private final ClassLoader classLoader; private final Logger logger; private final Collection preClassLoaderInitiators; private final Collection cleanUps; /** {@link DomainCombiner} that filters any {@link ProtectionDomain}s loaded by our classloader */ private final DomainCombiner domainCombiner; public ClassLoaderLeakPreventor(ClassLoader leakSafeClassLoader, ClassLoader classLoader, Logger logger, Collection preClassLoaderInitiators, Collection cleanUps) { this.leakSafeClassLoader = leakSafeClassLoader; this.classLoader = classLoader; this.logger = logger; this.preClassLoaderInitiators = preClassLoaderInitiators; this.cleanUps = cleanUps; final String javaVendor = System.getProperty("java.vendor"); if(javaVendor != null && javaVendor.startsWith("IBM")) { // IBM java_lang_ClassLoader_isAncestor = null; java_lang_ClassLoader_isAncestorOf = findMethod(ClassLoader.class, "isAncestorOf", ClassLoader.class); } else { // Oracle java_lang_ClassLoader_isAncestor = findMethod(ClassLoader.class, "isAncestor", ClassLoader.class); java_lang_ClassLoader_isAncestorOf = null; } NestedProtectionDomainCombinerException.class.getName(); // Should be loaded before switching to leak safe classloader this.domainCombiner = createDomainCombiner(); // Reflection inits java_security_AccessControlContext$combiner = findField(AccessControlContext.class, "combiner"); java_security_AccessControlContext$parent = findField(AccessControlContext.class, "parent"); java_security_AccessControlContext$privilegedContext = findField(AccessControlContext.class, "privilegedContext"); } /** Invoke all the registered {@link PreClassLoaderInitiator}s in the {@link #leakSafeClassLoader} */ public void runPreClassLoaderInitiators() { info("Initializing by loading some known offenders with leak safe classloader"); doInLeakSafeClassLoader(new Runnable() { @Override public void run() { for(PreClassLoaderInitiator preClassLoaderInitiator : preClassLoaderInitiators) { preClassLoaderInitiator.doOutsideClassLoader(ClassLoaderLeakPreventor.this); } } }); } /** * Perform action in the provided ClassLoader (normally system ClassLoader, that may retain references to the * {@link Thread#contextClassLoader}. * The motive for the custom {@link AccessControlContext} is to avoid spawned threads from inheriting all the * {@link java.security.ProtectionDomain}s of the running code, since that may include the classloader we want to * avoid leaking. This however means the {@link AccessControlContext} will have a {@link DomainCombiner} referencing the * classloader, which will be taken care of in {@link #runCleanUps()}. */ protected void doInLeakSafeClassLoader(final Runnable runnable) { final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(leakSafeClassLoader); // Use doPrivileged() not to perform secured actions, but to avoid threads spawned inheriting the // AccessControlContext of the current thread, since among the ProtectionDomains there will be one // (the top one) whose classloader is the web app classloader AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { runnable.run(); return null; // Nothing to return } }, createAccessControlContext()); } finally { // Reset original classloader Thread.currentThread().setContextClassLoader(contextClassLoader); } } /** * Create {@link AccessControlContext} that is used in {@link #doInLeakSafeClassLoader(Runnable)}. * The motive is to avoid spawned threads from inheriting all the {@link java.security.ProtectionDomain}s of the * running code, since that will include the web app classloader. */ public AccessControlContext createAccessControlContext() { try { // Try the normal way return new AccessControlContext(NO_DOMAINS_ACCESS_CONTROL_CONTEXT, domainCombiner); } catch (SecurityException e) { // createAccessControlContext not granted try { // Try reflection Constructor constructor = AccessControlContext.class.getDeclaredConstructor(ProtectionDomain[].class, DomainCombiner.class); constructor.setAccessible(true); return constructor.newInstance(NO_DOMAINS, domainCombiner); } catch (Exception e1) { logger.error("createAccessControlContext not granted and AccessControlContext could not be created via reflection"); return AccessController.getContext(); } } } /** {@link DomainCombiner} that filters any {@link ProtectionDomain}s loaded by our classloader */ private DomainCombiner createDomainCombiner() { return new DomainCombiner() { /** Flag to detected recursive calls */ private final ThreadLocal isExecuting = new ThreadLocal(); @Override public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, ProtectionDomain[] assignedDomains) { if(assignedDomains != null && assignedDomains.length > 0) { logger.error("Unexpected assignedDomains - please report to developer of this library!"); } if(isExecuting.get() == Boolean.TRUE) throw new NestedProtectionDomainCombinerException(); try { isExecuting.set(Boolean.TRUE); // Throw NestedProtectionDomainCombinerException on nested calls // Keep all ProtectionDomain not involving the web app classloader final List output = new ArrayList(); for(ProtectionDomain protectionDomain : currentDomains) { if(protectionDomain.getClassLoader() == null || ! isClassLoaderOrChild(protectionDomain.getClassLoader())) { output.add(protectionDomain); } } return output.toArray(new ProtectionDomain[output.size()]); } finally { isExecuting.remove(); } } }; } /** * Recursively unset our custom {@link DomainCombiner} (loaded in the web app) from the {@link AccessControlContext} * and any parents or privilegedContext thereof. */ @Deprecated public void removeDomainCombiner(Thread thread, AccessControlContext accessControlContext) { removeDomainCombiner("thread " + thread, accessControlContext); } /** * Recursively unset our custom {@link DomainCombiner} (loaded in the web app) from the {@link AccessControlContext} * and any parents or privilegedContext thereof. */ public void removeDomainCombiner(String owner, AccessControlContext accessControlContext) { if(accessControlContext != null && java_security_AccessControlContext$combiner != null) { if(getFieldValue(java_security_AccessControlContext$combiner, accessControlContext) == this.domainCombiner) { warn(AccessControlContext.class.getSimpleName() + " of " + owner + " used custom combiner - unsetting"); try { java_security_AccessControlContext$combiner.set(accessControlContext, null); } catch (Exception e) { error(e); } } // Recurse if(java_security_AccessControlContext$parent != null) { removeDomainCombiner(owner, (AccessControlContext) getFieldValue(java_security_AccessControlContext$parent, accessControlContext)); } if(java_security_AccessControlContext$privilegedContext != null) { removeDomainCombiner(owner, (AccessControlContext) getFieldValue(java_security_AccessControlContext$privilegedContext, accessControlContext)); } } } /** Invoke all the registered {@link ClassLoaderPreMortemCleanUp}s */ public void runCleanUps() { if(isJvmShuttingDown()) { info("JVM is shutting down - skip cleanup"); // Don't do anything more } else { final Field inheritedAccessControlContext = this.findField(Thread.class, "inheritedAccessControlContext"); if(inheritedAccessControlContext != null) { // Check if threads have been started in doInLeakSafeClassLoader() and need fixed ACC for(Thread thread : getAllThreads()) { // (We actually only need to do this for threads not running in web app, as per StopThreadsCleanUp) final AccessControlContext accessControlContext = getFieldValue(inheritedAccessControlContext, thread); removeDomainCombiner("thread " + thread , accessControlContext); } } for(ClassLoaderPreMortemCleanUp cleanUp : cleanUps) { cleanUp.cleanUp(this); } } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Utility methods public ClassLoader getClassLoader() { return classLoader; } /** * Get {@link ClassLoader} to be used when invoking the {@link PreClassLoaderInitiator}s. * This will normally be the {@link ClassLoader#getSystemClassLoader()}, but could be any other framework or * app server classloader. Normally, but not necessarily, a parent of {@link #classLoader}. */ public ClassLoader getLeakSafeClassLoader() { return leakSafeClassLoader; } /** Test if provided object is loaded by {@link #classLoader} */ public boolean isLoadedInClassLoader(Object o) { return (o instanceof Class) && isLoadedByClassLoader((Class)o) || // Object is a java.lang.Class instance o != null && isLoadedByClassLoader(o.getClass()); } /** Test if provided class is loaded wby {@link #classLoader} */ public boolean isLoadedByClassLoader(Class clazz) { return clazz != null && isClassLoaderOrChild(clazz.getClassLoader()); } /** Test if provided ClassLoader is the {@link #classLoader}, or a child thereof */ public boolean isClassLoaderOrChild(ClassLoader cl) { if(cl == null) { return false; } else if(cl == classLoader) { return true; } else { // It could be a child of the webapp classloader if(java_lang_ClassLoader_isAncestor != null) { // Primarily use ClassLoader.isAncestor() try { return (Boolean) java_lang_ClassLoader_isAncestor.invoke(cl, classLoader); } catch (Exception e) { error(e); } } if(java_lang_ClassLoader_isAncestorOf != null) { // Secondarily use IBM ClassLoader.isAncestorOf() try { return (Boolean) java_lang_ClassLoader_isAncestorOf.invoke(classLoader, cl); } catch (Exception e) { error(e); } } // We were unable to use ClassLoader.isAncestor() or isAncestorOf() try { while(cl != null) { if(cl == classLoader) return true; cl = cl.getParent(); } } catch (NestedProtectionDomainCombinerException e) { return false; // Since we needed permission to call getParent(), it is unlikely it is a descendant } return false; } } /** * Is the {@link Thread} ties do the protected classloader, either by being a custom {@link Thread} class, having a * custom {@link ThreadGroup} or having the protected classloader as its {@link Thread#contextClassLoader}? */ public boolean isThreadInClassLoader(Thread thread) { return isLoadedInClassLoader(thread) || // Custom Thread class in classloader isLoadedInClassLoader(thread.getThreadGroup()) || // Custom ThreadGroup class in classloader isClassLoaderOrChild(thread.getContextClassLoader()); // Running in classloader } /** * Make the provided Thread stop sleep(), wait() or join() and then give it the provided no of milliseconds to finish * executing. * @param thread The thread to wake up and wait for * @param waitMs The no of milliseconds to wait. If <= 0 this method does nothing. * @param interrupt Should {@link Thread#interrupt()} be called first, to make thread stop sleep(), wait() or join()? */ public void waitForThread(Thread thread, long waitMs, boolean interrupt) { if(waitMs > 0) { if(interrupt) { try { thread.interrupt(); // Make Thread stop waiting in sleep(), wait() or join() } catch (SecurityException e) { error(e); } } try { thread.join(waitMs); // Wait for thread to run } catch (InterruptedException e) { // Do nothing } } } /** Get current stack trace or provided thread as string. Returns {@code "unavailable"} if stack trace could not be acquired. */ public String getStackTrace(Thread thread) { try { final StackTraceElement[] stackTrace = thread.getStackTrace(); if(stackTrace.length == 0) return "Thread state: " + thread.getState(); final StringBuilder output = new StringBuilder("Thread stack trace: "); for(StackTraceElement stackTraceElement : stackTrace) { // if(output.length() > 0) // Except first output.append("\n\tat "); output.append(stackTraceElement.toString()); } return output.toString().trim(); // } catch (Throwable t) { // SecurityException return "Thread details unavailable"; } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public E getStaticFieldValue(Class clazz, String fieldName) { Field staticField = findField(clazz, fieldName); return (staticField != null) ? (E) getStaticFieldValue(staticField) : null; } public E getStaticFieldValue(String className, String fieldName) { return (E) getStaticFieldValue(className, fieldName, false); } public E getStaticFieldValue(String className, String fieldName, boolean trySystemCL) { Field staticField = findFieldOfClass(className, fieldName, trySystemCL); return (staticField != null) ? (E) getStaticFieldValue(staticField) : null; } public Field findFieldOfClass(String className, String fieldName) { return findFieldOfClass(className, fieldName, false); } public Field findFieldOfClass(String className, String fieldName, boolean trySystemCL) { Class clazz = findClass(className, trySystemCL); if(clazz != null) { return findField(clazz, fieldName); } else return null; } public Class findClass(String className) { return findClass(className, false); } public Class findClass(String className, boolean trySystemCL) { try { return Class.forName(className); } // catch (NoClassDefFoundError e) { // // Silently ignore // return null; // } catch (ClassNotFoundException e) { if (trySystemCL) { try { return Class.forName(className, true, ClassLoader.getSystemClassLoader()); } catch (ClassNotFoundException e1) { // Silently ignore return null; } } // Silently ignore return null; } catch (Exception ex) { // Example SecurityException warn(ex); return null; } } public Field findField(Class clazz, String fieldName) { if(clazz == null) return null; try { final Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); // (Field is probably private) return field; } catch (NoSuchFieldException ex) { // Silently ignore return null; } catch (Exception ex) { // Example SecurityException warn(ex); return null; } } public T getStaticFieldValue(Field field) { try { if(! Modifier.isStatic(field.getModifiers())) { warn(field.toString() + " is not static"); return null; } return (T) field.get(null); } catch (Exception ex) { warn(ex); // Silently ignore return null; } } public T getFieldValue(Object obj, String fieldName) { final Field field = findField(obj.getClass(), fieldName); return (T) getFieldValue(field, obj); } public T getFieldValue(Field field, Object obj) { try { return (T) field.get(obj); } catch (Exception ex) { warn(ex); // Silently ignore return null; } } public void setFinalStaticField(Field field, Object newValue) { // Allow modification of final field try { Field modifiersField = Field.class.getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); } catch (NoSuchFieldException e) { warn("Unable to get 'modifiers' field of java.lang.Field"); } catch (IllegalAccessException e) { warn("Unable to set 'modifiers' field of java.lang.Field"); } catch (Throwable t) { warn(t); } // Update the field try { field.set(null, newValue); } catch (Throwable e) { error("Error setting value of " + field + " to " + newValue); } } public Method findMethod(String className, String methodName, Class... parameterTypes) { Class clazz = findClass(className); if(clazz != null) { return findMethod(clazz, methodName, parameterTypes); } else return null; } public Method findMethod(Class clazz, String methodName, Class... parameterTypes) { if(clazz == null) return null; try { final Method method = clazz.getDeclaredMethod(methodName, parameterTypes); method.setAccessible(true); return method; } catch (NoSuchMethodException ex) { warn(ex); // Silently ignore return null; } } /** Get a Collection with all Threads. * This method is heavily inspired by org.apache.catalina.loader.WebappClassLoader.getThreads() */ public Collection getAllThreads() { // This is some orders of magnitude slower... // return Thread.getAllStackTraces().keySet(); // Find root ThreadGroup ThreadGroup tg = Thread.currentThread().getThreadGroup(); while(tg.getParent() != null) tg = tg.getParent(); // Note that ThreadGroup.enumerate() silently ignores all threads that does not fit into array int guessThreadCount = tg.activeCount() + 50; Thread[] threads = new Thread[guessThreadCount]; int actualThreadCount = tg.enumerate(threads); while(actualThreadCount == guessThreadCount) { // Map was filled, there may be more guessThreadCount *= 2; threads = new Thread[guessThreadCount]; actualThreadCount = tg.enumerate(threads); } // Filter out nulls final List output = new ArrayList(); for(Thread t : threads) { if(t != null) { output.add(t); } } return output; } /** * Override this method if you want to customize how we determine if we're running in * JBoss WildFly (a.k.a JBoss AS). */ public boolean isJBoss() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); try { // If package org.jboss is found, we may be running under JBoss return (contextClassLoader.getResource("org/jboss") != null); } catch(Exception ex) { return false; } } /** * Are we running in the Oracle/Sun Java Runtime Environment? * Override this method if you want to customize how we determine if this is a Oracle/Sun * Java Runtime Environment. */ public boolean isOracleJRE() { String javaVendor = System.getProperty("java.vendor"); return javaVendor.startsWith("Oracle") || javaVendor.startsWith("Sun"); } /** * Unlike {@link System#gc()} this method guarantees that garbage collection has been performed before * returning. */ public static void gc() { if (isDisableExplicitGCEnabled()) { System.err.println(ClassLoaderLeakPreventor.class.getSimpleName() + ": " + "Skipping GC call since -XX:+DisableExplicitGC is supplied as VM option."); return; } Object obj = new Object(); WeakReference ref = new WeakReference(obj); //noinspection UnusedAssignment obj = null; while(ref.get() != null) { System.gc(); } } /** * Check is "-XX:+DisableExplicitGC" enabled. * * @return true is "-XX:+DisableExplicitGC" is set als vm argument, false otherwise. */ private static boolean isDisableExplicitGCEnabled() { RuntimeMXBean bean = ManagementFactory.getRuntimeMXBean(); List aList = bean.getInputArguments(); return aList.contains("-XX:+DisableExplicitGC"); } /** Is the JVM currently shutting down? */ public boolean isJvmShuttingDown() { try { final Thread dummy = new Thread(); // Will never be started Runtime.getRuntime().removeShutdownHook(dummy); return false; } catch (IllegalStateException isex) { return true; // Shutting down } catch (Throwable t) { // Any other Exception, assume we are not shutting down return false; } } /** * Exception thrown when {@link DomainCombiner#combine(ProtectionDomain[], ProtectionDomain[])} is called recursively * during the execution of that same method. */ private static class NestedProtectionDomainCombinerException extends RuntimeException { } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Delegate methods for Logger public void debug(String msg) { logger.debug(msg); } public void warn(Throwable t) { logger.warn(t); } public void error(Throwable t) { logger.error(t); } public void warn(String msg) { logger.warn(msg); } public void error(String msg) { logger.error(msg); } public void info(String msg) { logger.info(msg); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorFactory.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.cleanup.*; import se.jiderhamn.classloader.leak.prevention.preinit.*; import static java.util.Collections.synchronizedMap; /** * Orchestrator class responsible for invoking the preventative and cleanup measures. * Contains the configuration and can be reused for multiple classloaders (assume it is not itself loaded by the * classloader which we want to avoid leaking). In that case, the {@link #logger} may need to be thread safe. * @author Mattias Jiderhamn */ public class ClassLoaderLeakPreventorFactory { /** * {@link ClassLoader} to be used when invoking the {@link PreClassLoaderInitiator}s. * Defaults to {@link ClassLoader#getSystemClassLoader()}, but could be any other framework or * app server classloader. */ protected final ClassLoader leakSafeClassLoader; /** * The {@link Logger} that will be passed on to the different {@link PreClassLoaderInitiator}s and * {@link ClassLoaderPreMortemCleanUp}s */ protected Logger logger = new JULLogger(); /** * Map from name to {@link PreClassLoaderInitiator}s with all the actions to invoke in the * {@link #leakSafeClassLoader}. Maintains insertion order. Thread safe. */ protected final Map preInitiators = synchronizedMap(new LinkedHashMap()); /** * Map from name to {@link ClassLoaderPreMortemCleanUp}s with all the actions to invoke to make a * {@link ClassLoader} ready for Garbage Collection. Maintains insertion order. Thread safe. */ protected final Map cleanUps = synchronizedMap(new LinkedHashMap()); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Constructors /** * Create new {@link ClassLoaderLeakPreventorFactory} with {@link ClassLoader#getSystemClassLoader()} as the * {@link #leakSafeClassLoader} and default {@link PreClassLoaderInitiator}s and {@link ClassLoaderPreMortemCleanUp}s. */ public ClassLoaderLeakPreventorFactory() { this(ClassLoader.getSystemClassLoader()); } /** * Create new {@link ClassLoaderLeakPreventorFactory} with supplied {@link ClassLoader} as the * {@link #leakSafeClassLoader} and default {@link PreClassLoaderInitiator}s and {@link ClassLoaderPreMortemCleanUp}s. */ public ClassLoaderLeakPreventorFactory(ClassLoader leakSafeClassLoader) { this.leakSafeClassLoader = leakSafeClassLoader; configureDefaults(); } /** Configure default {@link PreClassLoaderInitiator}s and {@link ClassLoaderPreMortemCleanUp}s */ public void configureDefaults() { // The pre-initiators part is heavily inspired by Tomcats JreMemoryLeakPreventionListener // See http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java?view=markup this.addPreInitiator(new AwtToolkitInitiator()); // initSecurityProviders() this.addPreInitiator(new JdbcDriversInitiator()); this.addPreInitiator(new SunAwtAppContextInitiator()); this.addPreInitiator(new SecurityPolicyInitiator()); this.addPreInitiator(new SecurityProvidersInitiator()); this.addPreInitiator(new DocumentBuilderFactoryInitiator()); this.addPreInitiator(new ReplaceDOMNormalizerSerializerAbortException()); this.addPreInitiator(new DatatypeConverterImplInitiator()); this.addPreInitiator(new JavaxSecurityLoginConfigurationInitiator()); this.addPreInitiator(new JarUrlConnectionInitiator()); // Load Sun specific classes that may cause leaks this.addPreInitiator(new LdapPoolManagerInitiator()); this.addPreInitiator(new Java2dDisposerInitiator()); this.addPreInitiator(new Java2dRenderQueueInitiator()); this.addPreInitiator(new SunGCInitiator()); this.addPreInitiator(new OracleJdbcThreadInitiator()); this.addCleanUp(new BeanIntrospectorCleanUp()); this.addCleanUp(new ObjectStreamClassCleanup()); // Apache Commons Pool can leave unfinished threads. Anything specific we can do? this.addCleanUp(new BeanELResolverCleanUp()); this.addCleanUp(new BeanValidationCleanUp()); this.addCleanUp(new JacksonCleanUp()); this.addCleanUp(new JavaServerFaces2746CleanUp()); this.addCleanUp(new GeoToolsCleanUp()); // Can we do anything about Google Guice ? // Can we do anything about Groovy http://jira.codehaus.org/browse/GROOVY-4154 ? this.addCleanUp(new IntrospectionUtilsCleanUp()); // Can we do anything about Logback http://jira.qos.ch/browse/LBCORE-205 ? this.addCleanUp(new IIOServiceProviderCleanUp()); // clear ImageIO registry this.addCleanUp(new MoxyCleanUp()); this.addCleanUp(new ReactorNettyHttpResourcesCleanUp()); this.addCleanUp(new ThreadGroupContextCleanUp()); this.addCleanUp(new X509TrustManagerImplUnparseableExtensionCleanUp()); this.addCleanUp(new SAAJEnvelopeFactoryParserPoolCleanUp()); //////////////////// // Fix generic leaks this.addCleanUp(new DriverManagerCleanUp()); this.addCleanUp(new DefaultAuthenticatorCleanUp()); this.addCleanUp(new MBeanCleanUp()); this.addCleanUp(new MXBeanNotificationListenersCleanUp()); this.addCleanUp(new ShutdownHookCleanUp()); this.addCleanUp(new PropertyEditorCleanUp()); this.addCleanUp(new SecurityProviderCleanUp()); this.addCleanUp(new JceSecurityCleanUp()); // (Probably best to do after deregistering the providers) this.addCleanUp(new ProxySelectorCleanUp()); this.addCleanUp(new RmiTargetsCleanUp()); this.addCleanUp(new StopThreadsCleanUp()); this.addCleanUp(new ThreadGroupCleanUp()); this.addCleanUp(new ThreadLocalCleanUp()); // This must be done after threads have been stopped, or new ThreadLocals may be added by those threads this.addCleanUp(new KeepAliveTimerCacheCleanUp()); this.addCleanUp(new ResourceBundleCleanUp()); this.addCleanUp(new JDK8151486CleanUp()); this.addCleanUp(new JavaUtilLoggingLevelCleanUp()); // Do this last, in case other shutdown procedures want to log something. this.addCleanUp(new ApacheCommonsLoggingCleanUp()); // Do this last, in case other shutdown procedures want to log something. } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Factory methods /** * Create new {@link ClassLoaderLeakPreventor} used to prevent the provided {@link Thread#contextClassLoader} of the * {@link Thread#currentThread()} from leaking. * * Please be aware that {@link ClassLoaderLeakPreventor}s created by the same factory share the same * {@link PreClassLoaderInitiator} and {@link ClassLoaderPreMortemCleanUp} instances, in case their config is changed. */ public ClassLoaderLeakPreventor newLeakPreventor() { return newLeakPreventor(Thread.currentThread().getContextClassLoader()); } /** Create new {@link ClassLoaderLeakPreventor} used to prevent the provided {@link ClassLoader} from leaking */ public ClassLoaderLeakPreventor newLeakPreventor(ClassLoader classLoader) { return new ClassLoaderLeakPreventor(leakSafeClassLoader, classLoader, logger, new ArrayList(preInitiators.values()), // Snapshot new ArrayList(cleanUps.values())); // Snapshot } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Methods for configuring the factory /** Set logger */ public void setLogger(Logger logger) { this.logger = logger; } /** Add a new {@link PreClassLoaderInitiator}, using the class name as name */ public void addPreInitiator(PreClassLoaderInitiator preClassLoaderInitiator) { addConsideringOrder(this.preInitiators, preClassLoaderInitiator); } /** Add a new {@link ClassLoaderPreMortemCleanUp}, using the class name as name */ public void addCleanUp(ClassLoaderPreMortemCleanUp classLoaderPreMortemCleanUp) { addConsideringOrder(this.cleanUps, classLoaderPreMortemCleanUp); } /** Add new {@link I} entry to {@code map}, taking {@link MustBeAfter} into account */ private void addConsideringOrder(Map map, I newEntry) { for(Map.Entry entry : map.entrySet()) { if(entry.getValue() instanceof MustBeAfter) { final Class[] existingMustBeAfter = ((MustBeAfter)entry.getValue()).mustBeBeforeMe(); for(Class clazz : existingMustBeAfter) { if(clazz.isAssignableFrom(newEntry.getClass())) { // Entry needs to be after new entry // TODO Resolve order automatically #51 throw new IllegalStateException(clazz.getName() + " must be added after " + newEntry.getClass()); } } } } map.put(newEntry.getClass().getName(), newEntry); } /** Add a new named {@link ClassLoaderPreMortemCleanUp} */ public void addCleanUp(String name, ClassLoaderPreMortemCleanUp classLoaderPreMortemCleanUp) { this.cleanUps.put(name, classLoaderPreMortemCleanUp); } /** Remove all the currently configured {@link PreClassLoaderInitiator}s */ public void clearPreInitiators() { this.preInitiators.clear(); } /** Remove all the currently configured {@link ClassLoaderPreMortemCleanUp}s */ public void clearCleanUps() { this.cleanUps.clear(); } /** * Get instance of {@link PreClassLoaderInitiator} for further configuring. * * Please be aware that {@link ClassLoaderLeakPreventor}s created by the same factory share the same * {@link PreClassLoaderInitiator} and {@link ClassLoaderPreMortemCleanUp} instances, in case their config is changed. */ public C getPreInitiator(Class clazz) { return (C) this.preInitiators.get(clazz.getName()); } /** * Get instance of {@link ClassLoaderPreMortemCleanUp} for further configuring. * * Please be aware that {@link ClassLoaderLeakPreventor}s created by the same factory share the same * {@link PreClassLoaderInitiator} and {@link ClassLoaderPreMortemCleanUp} instances, in case their config is changed. */ public C getCleanUp(Class clazz) { return (C) this.cleanUps.get(clazz.getName()); } /** Get instance of {@link PreClassLoaderInitiator} for further configuring */ public void removePreInitiator(Class clazz) { this.preInitiators.remove(clazz.getName()); } /** Get instance of {@link ClassLoaderPreMortemCleanUp} for further configuring */ public void removeCleanUp(Class clazz) { this.cleanUps.remove(clazz.getName()); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderPreMortemCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention; /** * Interface for cleanup actions that should be performed as part of the preparations to make a {@link ClassLoader} available * for garbage collection. * @author Mattias Jiderhamn */ public interface ClassLoaderPreMortemCleanUp { /** * Perform cleanup actions needed to make provided {@link ClassLoaderLeakPreventor#classLoader} * ready for garbage collection. */ void cleanUp(ClassLoaderLeakPreventor preventor); } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/JULLogger.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.util.logging.Level; /** * Implementation of {@link Logger} interface, that uses {@link java.util.logging}. * * @author Mattias Jiderhamn */ public class JULLogger implements Logger { private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(ClassLoaderLeakPreventor.class.getName()); @Override public void debug(String msg) { LOG.config(msg); } @Override public void info(String msg) { LOG.info(msg); } @Override public void warn(String msg) { LOG.warning(msg); } @Override public void warn(Throwable t) { LOG.log(Level.WARNING, t.getMessage(), t); } @Override public void error(String msg) { LOG.severe(msg); } @Override public void error(Throwable t) { LOG.log(Level.SEVERE, t.getMessage(), t); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/Logger.java ================================================ package se.jiderhamn.classloader.leak.prevention; /** * Interface for logging, with similarities to common logging frameworks. If you want to plug in the leak preventor * into an existing architecture (such as an application server), you may want to use a custom implementation of this * interface. * * If the {@link ClassLoaderLeakPreventorFactory} is beeing reused, the {@link Logger} implementation may need to be * thread safe. * * @author Mattias Jiderhamn */ public interface Logger { /** Log debug level message */ void debug(String msg); /** Log info level message */ void info(String msg); /** Log a warning message */ void warn(String msg); /** Log a {@link Throwable} as a warning message */ void warn(Throwable t); /** Log an error message */ void error(String msg); /** Log a {@link Throwable} as an error message */ void error(Throwable t); } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/MustBeAfter.java ================================================ package se.jiderhamn.classloader.leak.prevention; /** * Interface to be implemented by {@link PreClassLoaderInitiator}s and {@link ClassLoaderPreMortemCleanUp}s when order * is important. The class implementing this interface will define what other implementations it needs to be invoked * *after* for correct behaviour. It is the responsibility of {@link ClassLoaderLeakPreventorFactory} to make sure * the implementations are ordered correctly. Currently an {@link IllegalStateException} will be thrown (TODO #51) * @param The interface that both this class and the dependent classes implements, * i.e. either {@link PreClassLoaderInitiator} or {@link ClassLoaderPreMortemCleanUp}. * * @author Mattias Jiderhamn */ public interface MustBeAfter { /** * Returns an array of classes that, if part of they or any subclass of them are part of the list of * {@link PreClassLoaderInitiator}s/{@link ClassLoaderPreMortemCleanUp}s, needs to be prior to this element in the list. */ Class[] mustBeBeforeMe(); } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/PreClassLoaderInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention; /** * Interface for preventative actions that should be executed in the system (or other parent) classloader before they * may be triggered within the classloader that is about to be launched, and thereby may trigger leaks. * @author Mattias Jiderhamn */ public interface PreClassLoaderInitiator { /** * Perform action that needs to be done outside the leak susceptible classloader, i.e. in the system or other parent * classloader. Assume that the system or parent classloader is the {@link Thread#contextClassLoader} of the current * thread when method is invoked. * Must NOT have modified {@link Thread#contextClassLoader} of the current thread when returning. */ void doOutsideClassLoader(ClassLoaderLeakPreventor preventor); } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/ReplaceDOMNormalizerSerializerAbortException.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.lang.reflect.Constructor; import java.lang.reflect.Field; /** * As reported at https://github.com/mjiderhamn/classloader-leak-prevention/issues/36, invoking * {@code DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().normalizeDocument();} or * * Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); * DOMImplementationLS implementation = (DOMImplementationLS)document.getImplementation(); * implementation.createLSSerializer().writeToString(document); * may trigger leaks caused by the static fields {@link com.sun.org.apache.xerces.internal.dom.DOMNormalizer#abort} and * {@link com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl#abort} respectively keeping stacktraces/backtraces * that may include references to classes loaded by our web application. * * Since the {@link java.lang.Throwable#backtrace} itself cannot be accessed via reflection (see * http://bugs.java.com/view_bug.do?bug_id=4496456) we need to replace the with new one without any stack trace. * * This can be done either as a {@link PreClassLoaderInitiator} (recommended) or {@link ClassLoaderPreMortemCleanUp}. * * @author Mattias Jiderhamn */ public class ReplaceDOMNormalizerSerializerAbortException implements PreClassLoaderInitiator, ClassLoaderPreMortemCleanUp { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { replaceDOMNormalizerSerializerAbortException(preventor); } @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { replaceDOMNormalizerSerializerAbortException(preventor); } @SuppressWarnings("WeakerAccess") protected void replaceDOMNormalizerSerializerAbortException(ClassLoaderLeakPreventor preventor) { final RuntimeException abort = constructRuntimeExceptionWithoutStackTrace(preventor, "abort", null); if(abort != null) { final Field normalizerAbort = preventor.findFieldOfClass("com.sun.org.apache.xerces.internal.dom.DOMNormalizer", "abort"); if(normalizerAbort != null) preventor.setFinalStaticField(normalizerAbort, abort); final Field serializerAbort = preventor.findFieldOfClass("com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl", "abort"); if(serializerAbort != null) preventor.setFinalStaticField(serializerAbort, abort); } } /** Construct a new {@link RuntimeException} without any stack trace, in order to avoid any references back to this class */ @SuppressWarnings("WeakerAccess") public static RuntimeException constructRuntimeExceptionWithoutStackTrace(ClassLoaderLeakPreventor preventor, String message, Throwable cause) { try { final Constructor constructor = RuntimeException.class.getDeclaredConstructor(String.class, Throwable.class, Boolean.TYPE, Boolean.TYPE); constructor.setAccessible(true); return constructor.newInstance(message, cause, true, false /* disable stack trace */); } catch (Throwable e) { // InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException preventor.warn("Unable to construct RuntimeException without stack trace. The likely reason is that you are using Java <= 1.6. " + "No worries, except there might be some leaks you're not protected from (https://github.com/mjiderhamn/classloader-leak-prevention/issues/36 , " + "https://github.com/mjiderhamn/classloader-leak-prevention/issues/69). " + "If you are already on Java 1.7+, please report issue to developer of this library!"); preventor.warn(e); return null; } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/StdLogger.java ================================================ package se.jiderhamn.classloader.leak.prevention; /** * Implementation of {@link Logger} interface, that uses {@link System#out} and {@link System#err}. * Because log frameworks may themselves cause leaks, we may want to avoid them altogether. * * To "turn off" a log level, override the corresponding method(s) with an empty implementation. * @author Mattias Jiderhamn */ public class StdLogger implements Logger { /** Get prefix to use when logging to {@link System#out}/{@link System#err} */ protected String getLogPrefix() { return "ClassLoader Leak Preventor: "; } @Override public void debug(String msg) { System.out.println(getLogPrefix() + msg); } @Override public void info(String s) { System.out.println(getLogPrefix() + s); } @Override public void warn(String s) { System.err.println(getLogPrefix() + s); } @Override public void warn(Throwable t) { t.printStackTrace(System.err); } @Override public void error(String s) { System.err.println(getLogPrefix() + s); } @Override public void error(Throwable t) { t.printStackTrace(System.err); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ApacheCommonsLoggingCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.security.CodeSource; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Release this classloader from Apache Commons Logging (ACL) by calling * {@code LogFactory.release(getCurrentClassLoader());} * Use reflection in case ACL is not present. * Tip: Do this last, in case other shutdown procedures want to log something. * * @author Mattias Jiderhamn */ public class ApacheCommonsLoggingCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class logFactory = preventor.findClass("org.apache.commons.logging.LogFactory"); if(logFactory != null) { // Apache Commons Logging present try { final CodeSource codeSource = logFactory.getProtectionDomain().getCodeSource(); final String codeSourceStr = codeSource != null ? codeSource.toString() : ""; if (codeSourceStr.contains("spring-jcl")) { preventor.info("ignore ApacheCommonsLoggingCleanUp for spring-jcl at " + codeSource); return; } } catch (SecurityException ex) { preventor.error(ex); } preventor.info("Releasing web app classloader from Apache Commons Logging"); try { logFactory.getMethod("release", java.lang.ClassLoader.class) .invoke(null, preventor.getClassLoader()); } catch (Exception ex) { preventor.error(ex); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/BeanELResolverCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean for the cache of {@link javax.el.BeanELResolver}, which leaks prior to version 2.2.4. * @author Mattias Jiderhamn */ public class BeanELResolverCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { java.beans.Introspector.flushCaches(); // This must also be done final Class beanElResolverClass = preventor.findClass("javax.el.BeanELResolver"); if(beanElResolverClass != null) { boolean cleared = false; try { final Method purgeBeanClasses = beanElResolverClass.getDeclaredMethod("purgeBeanClasses", ClassLoader.class); purgeBeanClasses.setAccessible(true); purgeBeanClasses.invoke(beanElResolverClass.newInstance(), preventor.getClassLoader()); cleared = true; } catch (NoSuchMethodException e) { // Version of javax.el probably > 2.2; no real need to clear } catch (Exception e) { preventor.error(e); } if(! cleared) { // Fallback, if purgeBeanClasses() could not be called final Field propertiesField = preventor.findField(beanElResolverClass, "properties"); if(propertiesField != null) { try { final Map properties = (Map) propertiesField.get(null); properties.clear(); } catch (Exception e) { preventor.error(e); } } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/BeanIntrospectorCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.lang.reflect.Method; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clear {@link java.beans.Introspector} cache * @author Mattias Jiderhamn */ public class BeanIntrospectorCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { java.beans.Introspector.flushCaches(); // Clear cache of strong references clearClassInfoCache(preventor); } /** * Clears the BeanInfo SoftReference-based cache introduced in JDK 9 * * References: * * Clear explanation of the root cause: https://bugs.openjdk.java.net/browse/JDK-8207331 * * Issue which triggered the JDK fix: https://bugs.openjdk.java.net/browse/JDK-8231454 * * Fix commit (JDK16+ only as of now): https://github.com/openjdk/jdk/commit/2ee2b4ae */ private void clearClassInfoCache(ClassLoaderLeakPreventor preventor) { try { final Class classInfoClass = preventor.findClass("com.sun.beans.introspect.ClassInfo"); if (classInfoClass == null) { return; } Field cacheField = preventor.findField(classInfoClass, "CACHE"); if (cacheField == null) { return; // Either pre-JDK9 or exception occurred (should have been logged as warn at this point) } Object cacheInstance = cacheField.get(null); if (cacheInstance == null) { return; } Method clearMethod = cacheInstance.getClass().getSuperclass().getDeclaredMethod("clear"); clearMethod.invoke(cacheInstance); } catch (Exception e) { preventor.warn(e); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/BeanValidationCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean up leak caused by cache in {@link javax.validation.Validation} * @author Mattias Jiderhamn */ public class BeanValidationCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class offendingClass = preventor.findClass("javax.validation.Validation$DefaultValidationProviderResolver"); if(offendingClass != null) { // Class is present on class path Field offendingField = preventor.findField(offendingClass, "providersPerClassloader"); if(offendingField != null) { final Object providersPerClassloader = preventor.getStaticFieldValue(offendingField); if(providersPerClassloader instanceof Map) { // Map>> in offending code //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (providersPerClassloader) { // Fix the leak! ((Map)providersPerClassloader).remove(preventor.getClassLoader()); } } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/DefaultAuthenticatorCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.ref.Reference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.Authenticator; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clear the default {@link java.net.Authenticator} (in case current one is loaded by protected ClassLoader). * Includes special workaround for CXF issue https://issues.apache.org/jira/browse/CXF-5442 * @author Mattias Jiderhamn */ public class DefaultAuthenticatorCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Authenticator defaultAuthenticator = getDefaultAuthenticator(preventor); if(defaultAuthenticator == null || // Can both mean not set, or error retrieving, so unset anyway to be safe preventor.isLoadedInClassLoader(defaultAuthenticator)) { if(defaultAuthenticator != null) // Log warning only if a default was actually found preventor.warn("Unsetting default " + Authenticator.class.getName() + ": " + defaultAuthenticator); Authenticator.setDefault(null); } else { if("org.apache.cxf.transport.http.ReferencingAuthenticator".equals(defaultAuthenticator.getClass().getName())) { /* Needed since org.apache.cxf.transport.http.ReferencingAuthenticator is loaded by dummy classloader that references protected classloader via AccessControlContext + ProtectionDomain. See https://issues.apache.org/jira/browse/CXF-5442 */ final Class cxfAuthenticator = preventor.findClass("org.apache.cxf.transport.http.CXFAuthenticator"); if(cxfAuthenticator != null && preventor.isLoadedByClassLoader(cxfAuthenticator)) { // CXF loaded in this application final Object cxfAuthenticator$instance = preventor.getStaticFieldValue(cxfAuthenticator, "instance"); if(cxfAuthenticator$instance != null) { // CXF authenticator has been initialized in protected ClassLoader final Object authReference = preventor.getFieldValue(defaultAuthenticator, "auth"); if(authReference instanceof Reference) { // WeakReference final Reference reference = (Reference) authReference; final Object referencedAuth = reference.get(); if(referencedAuth == cxfAuthenticator$instance) { // References CXFAuthenticator of this classloader preventor.warn("Default " + Authenticator.class.getName() + " was " + defaultAuthenticator + " that referenced " + cxfAuthenticator$instance + " loaded by protected ClassLoader"); // Let CXF unwrap in it's own way (in case there are multiple CXF webapps in the container) reference.clear(); // Remove the weak reference before calling check() try { final Method check = defaultAuthenticator.getClass().getMethod("check"); check.setAccessible(true); check.invoke(defaultAuthenticator); } catch (Exception e) { preventor.error(e); } } } } } } removeWrappedAuthenticators(preventor, defaultAuthenticator); preventor.info("Default " + Authenticator.class.getName() + " not loaded by protected ClassLoader: " + defaultAuthenticator); } } /** Find default {@link Authenticator} */ @SuppressWarnings("WeakerAccess") protected Authenticator getDefaultAuthenticator(ClassLoaderLeakPreventor preventor) { // Normally Corresponds to getStaticFieldValue(Authenticator.class, "theAuthenticator"); for(final Field f : Authenticator.class.getDeclaredFields()) { if (f.getType().equals(Authenticator.class)) { // Supposedly "theAuthenticator" try { f.setAccessible(true); return (Authenticator)f.get(null); } catch (Exception e) { preventor.error(e); } } } return null; } /** * Recursively removed wrapped {@link Authenticator} loaded in protected ClassLoader. * May be needed in case there are multiple CXF applications within the same container. */ @SuppressWarnings("WeakerAccess") protected void removeWrappedAuthenticators(final ClassLoaderLeakPreventor preventor, final Authenticator authenticator) { if(authenticator == null) return; try { Class authenticatorClass = authenticator.getClass(); do { for(final Field f : authenticator.getClass().getDeclaredFields()) { if(Authenticator.class.isAssignableFrom(f.getType())) { try { final boolean isStatic = Modifier.isStatic(f.getModifiers()); // In CXF case this should be false final Authenticator owner = isStatic ? null : authenticator; f.setAccessible(true); final Authenticator wrapped = (Authenticator)f.get(owner); if(preventor.isLoadedInClassLoader(wrapped)) { preventor.warn(Authenticator.class.getName() + ": " + wrapped + ", wrapped by " + authenticator + ", is loaded by protected ClassLoader"); f.set(owner, null); // For added safety } else { removeWrappedAuthenticators(preventor, wrapped); // Recurse } } catch (Exception e) { preventor.error(e); } } } authenticatorClass = authenticatorClass.getSuperclass(); } while (authenticatorClass != null && authenticatorClass != Object.class); } catch (Exception e) { // Should never happen preventor.error(e); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/DriverManagerCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Deregister JDBC drivers loaded by classloader * @author Mattias Jiderhamn */ public class DriverManagerCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final List driversToDeregister = new ArrayList(); final Enumeration allDrivers = DriverManager.getDrivers(); while(allDrivers.hasMoreElements()) { final Driver driver = allDrivers.nextElement(); if(preventor.isLoadedInClassLoader(driver)) // Should be true for all returned by DriverManager.getDrivers() driversToDeregister.add(driver); } for(Driver driver : driversToDeregister) { try { preventor.warn("JDBC driver loaded by protected ClassLoader deregistered: " + driver.getClass()); DriverManager.deregisterDriver(driver); } catch (SQLException e) { preventor.error(e); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/GeoToolsCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Shutdown GeoTools cleaner thread as of https://osgeo-org.atlassian.net/browse/GEOT-2742 * @author Mattias Jiderhamn */ public class GeoToolsCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class weakCollectionCleanerClass = preventor.findClass("org.geotools.util.WeakCollectionCleaner"); if(weakCollectionCleanerClass != null) { try { final Field DEFAULT = preventor.findField(weakCollectionCleanerClass, "DEFAULT"); weakCollectionCleanerClass.getMethod("exit").invoke(DEFAULT.get(null)); } catch (Exception ex) { preventor.error(ex); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/IIOServiceProviderCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.security.AccessControlContext; import java.util.*; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.IIOServiceProvider; import javax.imageio.spi.ServiceRegistry; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Unregister ImageIO Service Provider loaded by the protected ClassLoader * @author Thomas Scheffler (1.x version) * @author Mattias Jiderhamn */ public class IIOServiceProviderCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(final ClassLoaderLeakPreventor preventor) { final IIORegistry registry = IIORegistry.getDefaultInstance(); Iterator> categories = registry.getCategories(); ServiceRegistry.Filter classLoaderFilter = new ServiceRegistry.Filter() { @Override public boolean filter(Object provider) { //remove all service provider loaded by the current ClassLoader return preventor.isLoadedInClassLoader(provider); } }; while (categories.hasNext()) { @SuppressWarnings("unchecked") Class category = (Class) categories.next(); Iterator serviceProviders = registry.getServiceProviders( category, classLoaderFilter, true); if (serviceProviders.hasNext()) { //copy to list List serviceProviderList = new ArrayList(); while (serviceProviders.hasNext()) { serviceProviderList.add(serviceProviders.next()); } for (IIOServiceProvider serviceProvider : serviceProviderList) { preventor.warn("ImageIO " + category.getSimpleName() + " service provider deregistered: " + serviceProvider.getDescription(Locale.ROOT)); registry.deregisterServiceProvider(serviceProvider); } } } // Leak as of Java 1.8u141, see https://github.com/mjiderhamn/classloader-leak-prevention/issues/71 // The providers are probably registered by SunAwtAppContextInitiator final Field accMapField = preventor.findFieldOfClass("javax.imageio.spi.SubRegistry", "accMap"); if(accMapField != null) { final Field categoryMapField = preventor.findField(ServiceRegistry.class, "categoryMap"); if(categoryMapField != null) { final Map categoryMap = preventor.getFieldValue(categoryMapField, registry); if(categoryMap != null) { for(/*SubRegistry*/ Object subRegistry : categoryMap.values()) { final Map, AccessControlContext> accMap = preventor.getFieldValue(accMapField, subRegistry); if(accMap != null) { for(AccessControlContext acc : accMap.values()) { preventor.removeDomainCombiner(IIORegistry.class.getName(), acc); } } } } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/IntrospectionUtilsCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clear IntrospectionUtils caches of Tomcat and Apache Commons Modeler * @author Mattias Jiderhamn */ public class IntrospectionUtilsCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { // Tomcat final Class tomcatIntrospectionUtils = preventor.findClass("org.apache.tomcat.util.IntrospectionUtils"); if(tomcatIntrospectionUtils != null) { try { tomcatIntrospectionUtils.getMethod("clear").invoke(null); } catch (Exception ex) { if(! preventor.isJBoss()) // JBoss includes this class, but no cache and no clear() method preventor.error(ex); } } // Apache Commons Modeler final Class modelIntrospectionUtils = preventor.findClass("org.apache.commons.modeler.util.IntrospectionUtils"); if(modelIntrospectionUtils != null && ! preventor.isClassLoaderOrChild(modelIntrospectionUtils.getClassLoader())) { // Loaded outside protected ClassLoader try { modelIntrospectionUtils.getMethod("clear").invoke(null); } catch (Exception ex) { preventor.warn("org.apache.commons.modeler.util.IntrospectionUtils needs to be cleared but there was an error, " + "consider upgrading Apache Commons Modeler"); preventor.error(ex); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/JDK8151486CleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; import java.lang.reflect.Field; import java.util.Set; /** * Clear the "domains" field of the parent ClassLoader. * * See JDK-8151486 */ public class JDK8151486CleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { Field field = preventor.findField(ClassLoader.class, "domains"); if (field == null) { // field only exists in JDK versions [8u25, 9u140) return; } for (ClassLoader cl = preventor.getClassLoader().getParent(); cl != null; cl = cl.getParent()) { Set domains = preventor.getFieldValue(field, cl); if (domains != null) { domains.clear(); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/JacksonCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Method; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clear Jackson TypeFactory cache as per https://github.com/FasterXML/jackson-databind/issues/1363 * @author Mattias Jiderhamn */ public class JacksonCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class typeFactoryClass = preventor.findClass("com.fasterxml.jackson.databind.type.TypeFactory"); if(typeFactoryClass != null && ! preventor.isLoadedInClassLoader(typeFactoryClass)) { try { final Method defaultInstance = preventor.findMethod(typeFactoryClass, "defaultInstance"); if(defaultInstance != null) { final Object defaultTypeFactory = defaultInstance.invoke(null); if(defaultTypeFactory != null) { final Method clearCache = preventor.findMethod(typeFactoryClass, "clearCache"); if(clearCache != null) { clearCache.invoke(defaultTypeFactory); } else { // Version < 2.4.1 final Object typeCache = preventor.getFieldValue(defaultTypeFactory, "_typeCache"); if(typeCache instanceof Map) { //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (typeCache) { ((Map) typeCache).clear(); } } } } } } catch (Exception e) { preventor.error(e); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/JavaServerFaces2746CleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.util.HashSet; import java.util.Set; import java.util.WeakHashMap; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Workaround for leak caused by Mojarra JSF implementation if included in the container. * See JAVASERVERFACES-2746 * @author Mattias Jiderhamn */ public class JavaServerFaces2746CleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { /* Note that since a WeakHashMap is used, it is not the map key that is the problem. However the value is a Map with java.beans.PropertyDescriptor as value, and java.beans.PropertyDescriptor has a Hashtable in which a class is put with "type" as key. This class may have been loaded by the protected ClassLoader. One example case is the class org.primefaces.component.menubutton.MenuButton that points to a Map with a key "model" whose PropertyDescriptor.table has key "type" with the class org.primefaces.model.MenuModel as its value. For performance reasons however, we'll only look at the top level key and remove any that has been loaded by protected ClassLoader. */ Object o = preventor.getStaticFieldValue("javax.faces.component.UIComponentBase", "descriptors"); // Non-static as of JSF 2.2.5 if(o instanceof WeakHashMap) { WeakHashMap descriptors = (WeakHashMap) o; final Set> toRemove = new HashSet>(); for(Object key : descriptors.keySet()) { if(key instanceof Class && preventor.isLoadedByClassLoader((Class)key)) { // For performance reasons, remove all classes loaded by protected ClassLoader toRemove.add((Class) key); // This would be more correct, but presumably slower /* Map m = (Map) descriptors.get(key); for(Map.Entry entry : m.entrySet()) { Object type = entry.getValue().getValue("type"); // Key constant javax.el.ELResolver.TYPE if(type instanceof Class && isLoadedByWebApplication((Class)type)) { toRemove.add((Class) key); } } */ } } if(! toRemove.isEmpty()) { preventor.info("Removing " + toRemove.size() + " classes from Mojarra descriptors cache"); for(Class clazz : toRemove) { descriptors.remove(clazz); } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/JavaUtilLoggingLevelCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.util.*; import java.util.logging.Level; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Cleanup for removing custom {@link java.util.logging.Level}s loaded within the protected class loader. * @author Mattias Jiderhamn */ public class JavaUtilLoggingLevelCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class knownLevelClass = preventor.findClass("java.util.logging.Level$KnownLevel"); if(knownLevelClass != null) { final Field levelObjectField = preventor.findField(knownLevelClass, "levelObject"); if(levelObjectField != null) { //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (knownLevelClass) { final Map*/> nameToLevels = preventor.getStaticFieldValue(knownLevelClass, "nameToLevels"); final Map*/> intToLevels = preventor.getStaticFieldValue(knownLevelClass, "intToLevels"); if(nameToLevels != null) { final Set/**/ removed = process(preventor, knownLevelClass, levelObjectField, nameToLevels); if(intToLevels != null) { for(List/**/ knownLevels : intToLevels.values()) { knownLevels.removeAll(removed); } } } else if(intToLevels != null) { // Use intToLevels as fallback; both should contain same values process(preventor, knownLevelClass, levelObjectField, intToLevels); } } } else preventor.warn("Found " + knownLevelClass + " but not levelObject field"); } } private Set/**/ process(ClassLoaderLeakPreventor preventor, Class knownLevelClass, Field levelObjectField, Map*/> levelsMaps) { final Set/**/ output = new HashSet(); for(List/**/ knownLevels : levelsMaps.values()) { for(Iterator/**/ iter = knownLevels.listIterator(); iter.hasNext(); ) { final Object /* KnownLevel */ knownLevel = iter.next(); final Level levelObject = preventor.getFieldValue(levelObjectField, knownLevel); if(preventor.isLoadedInClassLoader(levelObject)) { preventor.warn(Level.class.getName() + " subclass loaded by protected ClassLoader: " + levelObject.getClass() + "; removing from " + knownLevelClass); iter.remove(); output.add(knownLevel); } } } return output; } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/JavaxSecurityAuthLoginConfigurationCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import javax.security.auth.login.Configuration; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Cleanup for removing custom {@link javax.security.auth.login.Configuration}s loaded within the protected class loader. * @author Nikos Epping */ public class JavaxSecurityAuthLoginConfigurationCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { if (preventor.isLoadedInClassLoader(Configuration.getConfiguration())) { Configuration.setConfiguration(null); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/JceSecurityCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.net.URL; import java.security.Provider; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean up for the static caches of {@link javax.crypto.JceSecurity} * @author Mattias Jiderhamn */ public class JceSecurityCleanUp implements ClassLoaderPreMortemCleanUp { @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class javax_crypto_JceSecurity = preventor.findClass("javax.crypto.JceSecurity"); if(javax_crypto_JceSecurity != null) { synchronized (javax_crypto_JceSecurity) { // synchronized methods are used for querying and updating the caches final Map verificationResults = preventor.getStaticFieldValue(javax_crypto_JceSecurity, "verificationResults"); final Map verifyingProviders = preventor.getStaticFieldValue(javax_crypto_JceSecurity, "verifyingProviders"); final Map, URL> codeBaseCacheRef = preventor.getStaticFieldValue(javax_crypto_JceSecurity, "codeBaseCacheRef"); if(verificationResults != null) { verificationResults.clear(); } if(verifyingProviders != null) { verifyingProviders.clear(); } if(codeBaseCacheRef != null) { codeBaseCacheRef.clear(); } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/KeepAliveTimerCacheCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; import se.jiderhamn.classloader.leak.prevention.MustBeAfter; /** * Since Keep-Alive-Timer thread may have terminated, but still be referenced, we need to make sure it does not * reference this classloader. * @author Mattias Jiderhamn */ public class KeepAliveTimerCacheCleanUp implements ClassLoaderPreMortemCleanUp, MustBeAfter { /** Needs to be done after {@link StopThreadsCleanUp}, since in there the Keep-Alive-Timer may be stopped. */ @Override public Class[] mustBeBeforeMe() { return new Class[] {StopThreadsCleanUp.class}; } @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { Object keepAliveCache = preventor.getStaticFieldValue("sun.net.www.http.HttpClient", "kac", true); if(keepAliveCache != null) { final Thread keepAliveTimer = preventor.getFieldValue(keepAliveCache, "keepAliveTimer"); if(keepAliveTimer != null) { if(preventor.isClassLoaderOrChild(keepAliveTimer.getContextClassLoader())) { keepAliveTimer.setContextClassLoader(preventor.getLeakSafeClassLoader()); preventor.error("ContextClassLoader of sun.net.www.http.HttpClient cached Keep-Alive-Timer set to " + preventor.getLeakSafeClassLoader()); } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/MBeanCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.management.ManagementFactory; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Set; import javax.management.MBeanServer; import javax.management.ObjectName; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Unregister MBeans loaded by the protected class loader * @author Mattias Jiderhamn * @author rapla */ public class MBeanCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { try { final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); final Set allMBeanNames = mBeanServer.queryNames(new ObjectName("*:*"), null); // Special treatment for Jetty, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=423255 JettyJMXRemover jettyJMXRemover = null; if(isJettyWithJMX(preventor)) { try { jettyJMXRemover = new JettyJMXRemover(preventor); } catch (Exception ex) { preventor.error(ex); } } // Look for custom MBeans for(ObjectName objectName : allMBeanNames) { try { if (jettyJMXRemover != null && jettyJMXRemover.unregisterJettyJMXBean(objectName)) { continue; } final ClassLoader mBeanClassLoader = mBeanServer.getClassLoaderFor(objectName); if(preventor.isClassLoaderOrChild(mBeanClassLoader)) { // MBean loaded by protected ClassLoader preventor.warn("MBean '" + objectName + "' was loaded by protected ClassLoader; unregistering"); mBeanServer.unregisterMBean(objectName); } /* else if(... instanceof NotificationBroadcasterSupport) { unregisterNotificationListeners((NotificationBroadcasterSupport) ...); } */ } catch(Exception e) { // MBeanRegistrationException / InstanceNotFoundException preventor.error(e); } } } catch (Exception e) { // MalformedObjectNameException preventor.error(e); } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Methods and classes for Jetty, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=423255 /** Are we running in Jetty with JMX enabled? */ @SuppressWarnings("WeakerAccess") protected boolean isJettyWithJMX(ClassLoaderLeakPreventor preventor) { final ClassLoader classLoader = preventor.getClassLoader(); try { // If package org.eclipse.jetty is found, we may be running under jetty if (classLoader.getResource("org/eclipse/jetty") == null) { return false; } Class.forName("org.eclipse.jetty.jmx.MBeanContainer", false, classLoader.getParent()); // JMX enabled? Class.forName("org.eclipse.jetty.webapp.WebAppContext", false, classLoader.getParent()); } catch(Exception ex) { // For example ClassNotFoundException return false; } // Seems we are running in Jetty with JMX enabled return true; } /** * Inner utility class that helps dealing with Jetty MBeans class. * If you enable JMX support in Jetty 8 or 9 some MBeans (e.g. for the ServletHolder or SessionManager) are * instantiated in the web application thread and a reference to the WebappClassloader is stored in a private * ObjectMBean._loader which is unfortunately not the classloader that loaded the class. Therefore we need to access * the MBeanContainer class of the Jetty container and unregister the MBeans. */ private class JettyJMXRemover { private final ClassLoaderLeakPreventor preventor; /** List of objects that may be wrapped in MBean by Jetty. Should be allowed to contain null. */ private List objectsWrappedWithMBean; /** The org.eclipse.jetty.jmx.MBeanContainer instance */ private Object beanContainer; /** org.eclipse.jetty.jmx.MBeanContainer.findBean() */ private Method findBeanMethod; /** org.eclipse.jetty.jmx.MBeanContainer.removeBean() */ private Method removeBeanMethod; @SuppressWarnings("WeakerAccess") public JettyJMXRemover(ClassLoaderLeakPreventor preventor) throws Exception { this.preventor = preventor; // First we need access to the MBeanContainer to access the beans // WebAppContext webappContext = (WebAppContext)servletContext; final Object webappContext = findJettyClass("org.eclipse.jetty.webapp.WebAppClassLoader") .getMethod("getContext").invoke(preventor.getClassLoader()); if(webappContext == null) return; // Server server = (Server)webappContext.getServer(); final Class webAppContextClass = findJettyClass("org.eclipse.jetty.webapp.WebAppContext"); final Object server = webAppContextClass.getMethod("getServer").invoke(webappContext); if(server == null) return; // MBeanContainer beanContainer = (MBeanContainer)server.getBean(MBeanContainer.class); final Class mBeanContainerClass = findJettyClass("org.eclipse.jetty.jmx.MBeanContainer"); beanContainer = findJettyClass("org.eclipse.jetty.server.Server") .getMethod("getBean", Class.class).invoke(server, mBeanContainerClass); // Now we store all objects that belong to the web application and that will be wrapped by MBeans in a list if (beanContainer != null) { findBeanMethod = mBeanContainerClass.getMethod("findBean", ObjectName.class); try { removeBeanMethod = mBeanContainerClass.getMethod("removeBean", Object.class); } catch (NoSuchMethodException e) { preventor.warn("MBeanContainer.removeBean() method can not be found. giving up"); return; } objectsWrappedWithMBean = new ArrayList(); // SessionHandler sessionHandler = webappContext.getSessionHandler(); final Object sessionHandler = webAppContextClass.getMethod("getSessionHandler").invoke(webappContext); if(sessionHandler != null) { objectsWrappedWithMBean.add(sessionHandler); // SessionManager sessionManager = sessionHandler.getSessionManager(); final Object sessionManager = findJettyClass("org.eclipse.jetty.server.session.SessionHandler") .getMethod("getSessionManager").invoke(sessionHandler); if(sessionManager != null) { objectsWrappedWithMBean.add(sessionManager); // SessionIdManager sessionIdManager = sessionManager.getSessionIdManager(); final Object sessionIdManager = findJettyClass("org.eclipse.jetty.server.SessionManager") .getMethod("getSessionIdManager").invoke(sessionManager); objectsWrappedWithMBean.add(sessionIdManager); } } // SecurityHandler securityHandler = webappContext.getSecurityHandler(); objectsWrappedWithMBean.add(webAppContextClass.getMethod("getSecurityHandler").invoke(webappContext)); // ServletHandler servletHandler = webappContext.getServletHandler(); final Object servletHandler = webAppContextClass.getMethod("getServletHandler").invoke(webappContext); if(servletHandler != null) { objectsWrappedWithMBean.add(servletHandler); final Class servletHandlerClass = findJettyClass("org.eclipse.jetty.servlet.ServletHandler"); // Object[] servletMappings = servletHandler.getServletMappings(); objectsWrappedWithMBean.add(Arrays.asList((Object[]) servletHandlerClass.getMethod("getServletMappings").invoke(servletHandler))); // Object[] servlets = servletHandler.getServlets(); objectsWrappedWithMBean.add(Arrays.asList((Object[]) servletHandlerClass.getMethod("getServlets").invoke(servletHandler))); } } } /** * Test if objectName denotes a wrapping Jetty MBean and if so unregister it. * @return {@code true} if Jetty MBean was unregistered, otherwise {@code false} */ boolean unregisterJettyJMXBean(ObjectName objectName) { if (objectsWrappedWithMBean == null || ! objectName.getDomain().contains("org.eclipse.jetty")) { return false; } else { // Possibly a Jetty MBean that needs to be unregistered try { final Object bean = findBeanMethod.invoke(beanContainer, objectName); if(bean == null) return false; // Search suspect list for (Object wrapped : objectsWrappedWithMBean) { if (wrapped == bean) { preventor.warn("Jetty MBean '" + objectName + "' is a suspect in causing memory leaks; unregistering"); removeBeanMethod.invoke(beanContainer, bean); // Remove it via the MBeanContainer return true; } } } catch (Exception ex) { preventor.error(ex); } return false; } } Class findJettyClass(String className) throws ClassNotFoundException { try { return Class.forName(className, false, preventor.getClassLoader()); } catch (ClassNotFoundException e1) { try { return Class.forName(className); } catch (ClassNotFoundException e2) { e2.addSuppressed(e1); throw e2; } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/MXBeanNotificationListenersCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.management.ManagementFactory; import java.lang.management.PlatformManagedObject; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.Set; import javax.management.*; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Unregister MBeans, MXBean {@link NotificationListener}s/{@link NotificationFilter}s/handbacks loaded by the * protected class loader * @author Mattias Jiderhamn */ public class MXBeanNotificationListenersCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class notificationEmitterSupportClass = preventor.findClass("sun.management.NotificationEmitterSupport"); final Field listenerListField = preventor.findField(notificationEmitterSupportClass, "listenerList"); final Class listenerInfoClass = preventor.findClass("sun.management.NotificationEmitterSupport$ListenerInfo"); final Field listenerField = preventor.findField(listenerInfoClass, "listener"); final Field filterField = preventor.findField(listenerInfoClass, "filter"); final Field handbackField = preventor.findField(listenerInfoClass, "handback"); final Class listenerWrapperClass = preventor.findClass("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor$ListenerWrapper"); final boolean canProcessNotificationEmitterSupport = listenerListField != null && listenerInfoClass != null && listenerField != null && filterField != null && handbackField != null; if (!canProcessNotificationEmitterSupport) { preventor.warn("Unable to unregister NotificationEmitterSupport listeners, because details could not be found using reflection"); } final Set> platformInterfaces = ManagementFactory.getPlatformManagementInterfaces(); if (platformInterfaces != null) { for (Class platformInterface : platformInterfaces) { for (Object mxBean : ManagementFactory.getPlatformMXBeans(platformInterface)) { if (mxBean instanceof NotificationEmitter) { // The MXBean may have NotificationListeners if (canProcessNotificationEmitterSupport && notificationEmitterSupportClass.isAssignableFrom(mxBean.getClass())) { final List listenerList = preventor.getFieldValue(listenerListField, mxBean); if (listenerList != null) { for (Object listenerInfo : listenerList) { // Loop all listeners final NotificationListener listener = preventor.getFieldValue(listenerField, listenerInfo); final NotificationListener rawListener = unwrap(preventor, listenerWrapperClass, listener); final NotificationFilter filter = preventor.getFieldValue(filterField, listenerInfo); final Object handback = preventor.getFieldValue(handbackField, listenerInfo); if (preventor.isLoadedInClassLoader(rawListener) || preventor.isLoadedInClassLoader(filter) || preventor.isLoadedInClassLoader(handback)) { preventor.warn(((listener == rawListener) ? "Listener '" : "Wrapped listener '") + listener + "' (or its filter or handback) of MXBean " + mxBean + " of PlatformManagedObject " + platformInterface + " was loaded in protected ClassLoader; removing"); // This is safe, as the implementation (as of this writing) works with a copy, // not altering the original try { ((NotificationEmitter) mxBean).removeNotificationListener(listener, filter, handback); } catch (ListenerNotFoundException e) { // Should never happen preventor.error(e); } } } } } else if(mxBean instanceof NotificationBroadcasterSupport) { // Unlikely case unregisterNotificationListeners(preventor, (NotificationBroadcasterSupport) mxBean, listenerWrapperClass); } } } } } } /** * Unregister {@link NotificationListener}s from subclass of {@link NotificationBroadcasterSupport}, if listener, * filter or handback is loaded by the protected ClassLoader. */ protected void unregisterNotificationListeners(ClassLoaderLeakPreventor preventor, NotificationBroadcasterSupport mBean, final Class listenerWrapperClass) { final Field listenerListField = preventor.findField(NotificationBroadcasterSupport.class, "listenerList"); if(listenerListField != null) { final Class listenerInfoClass = preventor.findClass("javax.management.NotificationBroadcasterSupport$ListenerInfo"); final List listenerList = preventor.getFieldValue(listenerListField, mBean); if(listenerList != null) { final Field listenerField = preventor.findField(listenerInfoClass, "listener"); final Field filterField = preventor.findField(listenerInfoClass, "filter"); final Field handbackField = preventor.findField(listenerInfoClass, "handback"); for(Object listenerInfo : listenerList) { final NotificationListener listener = preventor.getFieldValue(listenerField, listenerInfo); final NotificationListener rawListener = unwrap(preventor, listenerWrapperClass, listener); final NotificationFilter filter = preventor.getFieldValue(filterField, listenerInfo); final Object handback = preventor.getFieldValue(handbackField, listenerInfo); if(preventor.isLoadedInClassLoader(rawListener) || preventor.isLoadedInClassLoader(filter) || preventor.isLoadedInClassLoader(handback)) { preventor.warn(((listener == rawListener) ? "Listener '" : "Wrapped listener '") + listener + "' (or its filter or handback) of MBean " + mBean + " was loaded in protected ClassLoader; removing"); // This is safe, as the implementation works with a copy, not altering the original try { mBean.removeNotificationListener(listener, filter, handback); } catch (ListenerNotFoundException e) { // Should never happen preventor.error(e); } } } } } } /** Unwrap {@link NotificationListener} wrapped by {@link com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.ListenerWrapper} */ private NotificationListener unwrap(ClassLoaderLeakPreventor preventor, Class listenerWrapperClass, NotificationListener listener) { if(listenerWrapperClass != null && listenerWrapperClass.isInstance(listener)) { return preventor.getFieldValue(listener, "listener"); // Unwrap } else return listener; } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/MoxyCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ConcurrentModificationException; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Cleanup for leak caused by EclipseLink MOXy * See https://bugs.eclipse.org/bugs/show_bug.cgi?id=529270 * @author Mattias Jiderhamn */ public class MoxyCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class helperClass = findClass(preventor, "org.eclipse.persistence.jaxb.javamodel.Helper"); if(helperClass != null) { unsetField(preventor, helperClass, "COLLECTION_CLASS"); unsetField(preventor, helperClass, "LIST_CLASS"); unsetField(preventor, helperClass, "SET_CLASS"); unsetField(preventor, helperClass, "MAP_CLASS"); unsetField(preventor, helperClass, "JAXBELEMENT_CLASS"); unsetField(preventor, helperClass, "OBJECT_CLASS"); } final Class propertyClass = findClass(preventor, "org.eclipse.persistence.jaxb.compiler.Property"); if(propertyClass != null) { unsetField(preventor, propertyClass, "OBJECT_CLASS"); unsetField(preventor, propertyClass, "XML_ADAPTER_CLASS"); } } public Class findClass(ClassLoaderLeakPreventor preventor, String className) { try { return Class.forName(className, true, preventor.getLeakSafeClassLoader()); } catch (ClassNotFoundException e) { // Silently ignore return null; } catch (Exception ex) { // Example SecurityException preventor.warn(ex); return null; } } private void unsetField(ClassLoaderLeakPreventor preventor, Class clazz, String fieldName) { final Field field = preventor.findField(clazz, fieldName); if(field != null) { try { final Object /* org.eclipse.persistence.jaxb.javamodel.reflection.JavaClassImpl */ javaClass = field.get(null); if(javaClass != null) { final Object /* org.eclipse.persistence.jaxb.javamodel.reflection.JavaModelImpl */ javaModelImpl = preventor.getFieldValue(javaClass, "javaModelImpl"); if(javaModelImpl != null) { final Method getClassLoader = preventor.findMethod(javaModelImpl.getClass(), "getClassLoader"); if(getClassLoader != null) { final ClassLoader classLoader = (ClassLoader) getClassLoader.invoke(javaModelImpl); if(preventor.isClassLoaderOrChild(classLoader)) { preventor.info("Changing ClassLoader of " + field); preventor.findMethod(javaModelImpl.getClass(), "setClassLoader", ClassLoader.class) .invoke(javaModelImpl, preventor.getLeakSafeClassLoader()); final Field isJaxbClassLoader = preventor.findField(javaModelImpl.getClass(), "isJaxbClassLoader"); if(isJaxbClassLoader != null) { isJaxbClassLoader.set(javaModelImpl, false); } } } else preventor.error("Cannot get ClassLoader of " + javaModelImpl); // Clear cachedJavaClasses final Map cachedJavaClasses = preventor.getFieldValue(javaModelImpl, "cachedJavaClasses"); if(cachedJavaClasses != null) { try { cachedJavaClasses.clear(); } catch (ConcurrentModificationException e) { preventor.error("Unable to clear " + javaModelImpl + ".cachedJavaClasses"); } } } else { preventor.error("Cannot get javaModelImpl of " + javaClass); field.set(null, null); } } } catch (Exception e) { preventor.warn(e); } } else preventor.warn("Unable to find field " + fieldName + " of class " + clazz); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/MultiThreadedHttpConnectionManagerCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Invokes static method org.apache.commons.httpclient.MultiThreadedHttpConnectionManager.shutdownAll() to close connections left out by com.sun.jersey.client.apache.ApacheHttpClient. * * @author Marian Petrik */ public class MultiThreadedHttpConnectionManagerCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Class connManager = preventor.findClass("org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"); if(connManager != null && preventor.isLoadedByClassLoader(connManager)) { try { connManager.getMethod("shutdownAll").invoke(null); } catch (Throwable t) { preventor.warn(t); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ObjectStreamClassCleanup.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.util.concurrent.ConcurrentHashMap; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean up for the static caches of {@link java.io.ObjectStreamClass} */ public class ObjectStreamClassCleanup implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { try { final Class cacheClass = preventor.findClass("java.io.ObjectStreamClass$Caches"); if (cacheClass == null) { return; } Object localDescsCache = preventor.getStaticFieldValue(cacheClass, "localDescs"); clearIfConcurrentHashMap(localDescsCache, preventor); Object reflectorsCache = preventor.getStaticFieldValue(cacheClass, "reflectors"); clearIfConcurrentHashMap(reflectorsCache, preventor); } catch (Exception e) { preventor.error(e); } } protected void clearIfConcurrentHashMap(Object object, ClassLoaderLeakPreventor preventor) { if (!(object instanceof ConcurrentHashMap)) { return; } ConcurrentHashMap map = (ConcurrentHashMap) object; int nbOfEntries=map.size(); map.clear(); preventor.info("Detected and fixed leak situation for java.io.ObjectStreamClass ("+nbOfEntries+" entries were flushed)."); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/PropertyEditorCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.beans.PropertyEditorManager; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Map; import java.util.Set; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Deregister custom property editors. * This has been fixed in Java 7. * @author Mattias Jiderhamn */ public class PropertyEditorCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Field registryField = preventor.findField(PropertyEditorManager.class, "registry"); if(registryField == null) { // We're probably on a newer JDK preventor.info("Internal registry of " + PropertyEditorManager.class.getName() + " not found"); } else { try { synchronized (PropertyEditorManager.class) { final Map, Class> registry = (Map, Class>) registryField.get(null); if(registry != null) { // Initialized final Set> toRemove = new HashSet>(); for(Map.Entry, Class> entry : registry.entrySet()) { if(preventor.isLoadedByClassLoader(entry.getKey()) || preventor.isLoadedByClassLoader(entry.getValue())) { // More likely toRemove.add(entry.getKey()); } } for(Class clazz : toRemove) { preventor.warn("Property editor for type " + clazz + " = " + registry.get(clazz) + " needs to be deregistered"); PropertyEditorManager.registerEditor(clazz, null); // Deregister } } } } catch (Exception e) { // Such as IllegalAccessException preventor.error(e); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ProxySelectorCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.net.ProxySelector; import java.security.AccessController; import java.security.PrivilegedAction; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * If default {@link java.net.ProxySelector} is loaded by protected ClassLoader it needs to be unset * @author Mattias Jiderhamn */ public class ProxySelectorCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(final ClassLoaderLeakPreventor preventor) { AccessController.doPrivileged(new PrivilegedAction() { @Override public Void run() { ProxySelector selector = ProxySelector.getDefault(); if(preventor.isLoadedInClassLoader(selector)) { ProxySelector.setDefault(null); preventor.warn("Removing default java.net.ProxySelector: " + selector); } return null; } }); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ReactorNettyHttpResourcesCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Method; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean up Reactor Netty resources * @author Mattias Jiderhamn */ public class ReactorNettyHttpResourcesCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { Class clazz = preventor.findClass("reactor.ipc.netty.http.HttpResources"); if(preventor.isLoadedByClassLoader(clazz)) { final Method shutdown = preventor.findMethod(clazz, "shutdown"); if(shutdown != null) { try { shutdown.invoke(null); } catch (Throwable e) { preventor.warn(e); } } } clazz = preventor.findClass("reactor.netty.http.HttpResources"); if(preventor.isLoadedByClassLoader(clazz)) { final Method shutdown = preventor.findMethod(clazz, "shutdown"); if(shutdown != null) { try { shutdown.invoke(null); } catch (Throwable e) { preventor.warn(e); } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ResourceBundleCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Iterator; import java.util.Map; import java.util.ResourceBundle; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean up caches in {@link ResourceBundle} * @author Mattias Jiderhamn */ public class ResourceBundleCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { try { try { // First try Java 1.6 method final Method clearCache16 = ResourceBundle.class.getMethod("clearCache", ClassLoader.class); preventor.debug("Since Java 1.6+ is used, we can call " + clearCache16); clearCache16.invoke(null, preventor.getClassLoader()); } catch (NoSuchMethodException e) { // Not Java 1.6+, we have to clear manually final Map cacheList = preventor.getStaticFieldValue(ResourceBundle.class, "cacheList"); // Java 5: SoftCache extends AbstractMap final Iterator iter = cacheList.keySet().iterator(); Field loaderRefField = null; while(iter.hasNext()) { Object key = iter.next(); // CacheKey if(loaderRefField == null) { // First time loaderRefField = key.getClass().getDeclaredField("loaderRef"); loaderRefField.setAccessible(true); } WeakReference loaderRef = (WeakReference) loaderRefField.get(key); // LoaderReference extends WeakReference ClassLoader classLoader = loaderRef.get(); if(preventor.isClassLoaderOrChild(classLoader)) { preventor.info("Removing ResourceBundle from cache: " + key); iter.remove(); } } } } catch(Exception ex) { preventor.error(ex); } // (CacheKey of java.util.ResourceBundle.NONEXISTENT_BUNDLE will point to first referring classloader...) } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/RmiTargetsCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.util.Iterator; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Heavily inspired by org.apache.catalina.loader.WebappClassLoader.clearReferencesRmiTargets() * @author Mattias Jiderhamn */ public class RmiTargetsCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { try { final Class objectTableClass = preventor.findClass("sun.rmi.transport.ObjectTable"); if(objectTableClass != null) { clearRmiTargetsMap(preventor, (Map) preventor.getStaticFieldValue(objectTableClass, "objTable")); clearRmiTargetsMap(preventor, (Map) preventor.getStaticFieldValue(objectTableClass, "implTable")); } } catch (Exception ex) { preventor.error(ex); } } /** Iterate RMI Targets Map and remove entries loaded by protected ClassLoader */ @SuppressWarnings("WeakerAccess") protected void clearRmiTargetsMap(ClassLoaderLeakPreventor preventor, Map rmiTargetsMap) { try { final Field cclField = preventor.findFieldOfClass("sun.rmi.transport.Target", "ccl"); preventor.debug("Looping " + rmiTargetsMap.size() + " RMI Targets to find leaks"); for(Iterator iter = rmiTargetsMap.values().iterator(); iter.hasNext(); ) { Object target = iter.next(); // sun.rmi.transport.Target ClassLoader ccl = (ClassLoader) cclField.get(target); if(preventor.isClassLoaderOrChild(ccl)) { preventor.warn("Removing RMI Target: " + target); iter.remove(); } } } catch (Exception ex) { preventor.error(ex); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/SAAJEnvelopeFactoryParserPoolCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean up leak caused by {@link javax.xml.parsers.SAXParser} attribute/property being loaded by protected class loader * and cached in {@link com.sun.xml.internal.messaging.saaj.soap.EnvelopeFactory#parserPool}. * See here. * @author Mattias Jiderhamn */ public class SAAJEnvelopeFactoryParserPoolCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { // Internal class from the JDK (Removed in JDK11) cleanupWithFactoryClass(preventor, preventor.findClass("com.sun.xml.internal.messaging.saaj.soap.EnvelopeFactory")); // Maven dependency cleanupWithFactoryClass(preventor, preventor.findClass("com.sun.xml.messaging.saaj.soap.EnvelopeFactory")); } private void cleanupWithFactoryClass(final ClassLoaderLeakPreventor preventor, Class factoryClass) { final Object parserPool = preventor.getStaticFieldValue(factoryClass, "parserPool"); if(parserPool != null) { final Field CACHE = preventor.findField(parserPool.getClass().getSuperclass(), "CACHE"); if(CACHE != null) { final Object cache = preventor.getFieldValue(CACHE, parserPool); if(cache instanceof Map) { // WeakHashMap ((Map) cache).remove(preventor.getClassLoader()); } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/SecurityProviderCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.util.HashSet; import java.util.Set; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Deregister custom security providers * @author Mattias Jiderhamn */ public class SecurityProviderCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Set providersToRemove = new HashSet(); for(java.security.Provider provider : java.security.Security.getProviders()) { if(preventor.isLoadedInClassLoader(provider)) { providersToRemove.add(provider.getName()); } } if(! providersToRemove.isEmpty()) { preventor.warn("Removing security providers loaded by protected ClassLoader: " + providersToRemove); for(String providerName : providersToRemove) { java.security.Security.removeProvider(providerName); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ShutdownHookCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.util.ArrayList; import java.util.Map; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Find and deregister shutdown hooks. Will by default execute the hooks immediately after removing them. * @author Mattias Jiderhamn */ public class ShutdownHookCleanUp implements ClassLoaderPreMortemCleanUp { /** Default no of milliseconds to wait for shutdown hook to finish execution */ public static final int SHUTDOWN_HOOK_WAIT_MS_DEFAULT = 10 * 1000; // 10 seconds /** Should shutdown hooks registered from the application be executed at application shutdown? */ @SuppressWarnings("WeakerAccess") protected boolean executeShutdownHooks = true; /** * No of milliseconds to wait for shutdown hooks to finish execution, before stopping them. * If set to -1 there will be no waiting at all, but Thread is allowed to run until finished. */ @SuppressWarnings("WeakerAccess") protected int shutdownHookWaitMs = SHUTDOWN_HOOK_WAIT_MS_DEFAULT; /** Constructor for test case */ @SuppressWarnings("unused") public ShutdownHookCleanUp() { this(true, SHUTDOWN_HOOK_WAIT_MS_DEFAULT); } public ShutdownHookCleanUp(boolean executeShutdownHooks, int shutdownHookWaitMs) { this.executeShutdownHooks = executeShutdownHooks; this.shutdownHookWaitMs = shutdownHookWaitMs; } public void setExecuteShutdownHooks(boolean executeShutdownHooks) { this.executeShutdownHooks = executeShutdownHooks; } public void setShutdownHookWaitMs(int shutdownHookWaitMs) { this.shutdownHookWaitMs = shutdownHookWaitMs; } @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { // We will not remove known shutdown hooks, since loading the owning class of the hook, // may register the hook if previously unregistered final Map shutdownHooks = preventor.getStaticFieldValue("java.lang.ApplicationShutdownHooks", "hooks"); if(shutdownHooks != null) { // Could be null during JVM shutdown, which we already avoid, but be extra precautious // Iterate copy to avoid ConcurrentModificationException for(Thread shutdownHook : new ArrayList(shutdownHooks.keySet())) { if(preventor.isThreadInClassLoader(shutdownHook)) { // Planned to run in protected ClassLoader removeShutdownHook(preventor, shutdownHook); } } } } /** Deregister shutdown hook and execute it immediately */ @SuppressWarnings({"deprecation", "WeakerAccess"}) protected void removeShutdownHook(ClassLoaderLeakPreventor preventor, Thread shutdownHook) { final String displayString = "'" + shutdownHook + "' of type " + shutdownHook.getClass().getName(); preventor.error("Removing shutdown hook: " + displayString); Runtime.getRuntime().removeShutdownHook(shutdownHook); if(executeShutdownHooks) { // Shutdown hooks should be executed preventor.info("Executing shutdown hook now: " + displayString); // Make sure it's from protected ClassLoader shutdownHook.start(); // Run cleanup immediately if(shutdownHookWaitMs > 0) { // Wait for shutdown hook to finish try { shutdownHook.join(shutdownHookWaitMs); // Wait for thread to run } catch (InterruptedException e) { // Do nothing } if(shutdownHook.isAlive()) { preventor.warn(shutdownHook + "still running after " + shutdownHookWaitMs + " ms - Stopping!"); shutdownHook.stop(); } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/StopThreadsCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.security.AccessControlContext; import java.util.List; import java.util.concurrent.ThreadPoolExecutor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; import static se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT; /** * Check if there are threads running within the protected {@link ClassLoader}, or otherwise referencing it, * and either warn or stop those threads depending on settings. * @author Mattias Jiderhamn */ @SuppressWarnings("WeakerAccess") public class StopThreadsCleanUp implements ClassLoaderPreMortemCleanUp { protected static final String JURT_ASYNCHRONOUS_FINALIZER = "com.sun.star.lib.util.AsynchronousFinalizer"; /** Thread {@link Runnable} for Sun/Oracle JRE i.e. java.lang.Thread.target */ private Field oracleTarget; /** Thread {@link Runnable} for IBM JRE i.e. java.lang.Thread.runnable */ private Field ibmRunnable; protected boolean stopThreads; /** * No of milliseconds to wait for threads to finish execution, before stopping them. */ protected int threadWaitMs = THREAD_WAIT_MS_DEFAULT; /** Should Timer threads tied to the protected ClassLoader classloader be forced to stop at application shutdown? */ protected boolean stopTimerThreads; /** Default constructor with {@link #stopThreads} = true and {@link #stopTimerThreads} = true */ @SuppressWarnings("unused") public StopThreadsCleanUp() { this(true, true); } public StopThreadsCleanUp(boolean stopThreads, boolean stopTimerThreads) { this.stopThreads = stopThreads; this.stopTimerThreads = stopTimerThreads; } public void setStopThreads(boolean stopThreads) { this.stopThreads = stopThreads; } public void setStopTimerThreads(boolean stopTimerThreads) { this.stopTimerThreads = stopTimerThreads; } public void setThreadWaitMs(int threadWaitMs) { this.threadWaitMs = threadWaitMs; } @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { // Force the execution of the cleanup code for JURT; see https://issues.apache.org/ooo/show_bug.cgi?id=122517 forceStartOpenOfficeJurtCleanup(preventor); // (Do this before stopThreads()) //////////////////// // Fix generic leaks stopThreads(preventor); } /** * The bug detailed at https://issues.apache.org/ooo/show_bug.cgi?id=122517 is quite tricky. This is a try to * avoid the issues by force starting the threads and it's job queue. */ protected void forceStartOpenOfficeJurtCleanup(ClassLoaderLeakPreventor preventor) { if(stopThreads) { if(preventor.isLoadedByClassLoader(preventor.findClass(JURT_ASYNCHRONOUS_FINALIZER))) { /* The com.sun.star.lib.util.AsynchronousFinalizer class was found and loaded, which means that in case the static block that starts the daemon thread had not been started yet, it has been started now. Now let's force Garbage Collection, with the hopes of having the finalize()ers that put Jobs on the AsynchronousFinalizer queue be executed. Then just leave it, and handle the rest in {@link #stopThreads}. */ preventor.info("OpenOffice JURT AsynchronousFinalizer thread started - forcing garbage collection to invoke finalizers"); ClassLoaderLeakPreventor.gc(); } } else { // Check for class existence without loading class and thus executing static block if(preventor.getClassLoader().getResource("com/sun/star/lib/util/AsynchronousFinalizer.class") != null) { preventor.warn("OpenOffice JURT AsynchronousFinalizer thread will not be stopped if started, as stopThreads is false"); /* By forcing Garbage Collection, we'll hopefully start the thread now, in case it would have been started by GC later, so that at least it will appear in the logs. */ ClassLoaderLeakPreventor.gc(); } } } /** * Partially inspired by org.apache.catalina.loader.WebappClassLoader.clearReferencesThreads() */ protected void stopThreads(ClassLoaderLeakPreventor preventor) { final Class workerClass = preventor.findClass("java.util.concurrent.ThreadPoolExecutor$Worker"); final boolean waitForThreads = threadWaitMs > 0; for(Thread thread : preventor.getAllThreads()) { final Runnable runnable = getRunnable(preventor, thread); final boolean threadLoadedByClassLoader = preventor.isLoadedInClassLoader(thread); final boolean threadGroupLoadedByClassLoader = preventor.isLoadedInClassLoader(thread.getThreadGroup()); final boolean runnableLoadedByClassLoader = preventor.isLoadedInClassLoader(runnable); final boolean hasContextClassLoader = preventor.isClassLoaderOrChild(thread.getContextClassLoader()); if(thread != Thread.currentThread() && // Ignore current thread (threadLoadedByClassLoader || threadGroupLoadedByClassLoader || hasContextClassLoader || // = preventor.isThreadInClassLoader(thread) runnableLoadedByClassLoader)) { if (thread.getClass().getName().startsWith(StopThreadsCleanUp.JURT_ASYNCHRONOUS_FINALIZER)) { // Note, the thread group of this thread may be "system" if it is triggered by the Garbage Collector // however if triggered by us in forceStartOpenOfficeJurtCleanup() it may depend on the application server if(stopThreads) { preventor.info("Found JURT thread " + thread.getName() + "; starting " + JURTKiller.class.getSimpleName()); new JURTKiller(preventor, thread).start(); } else preventor.warn("JURT thread " + thread.getName() + " is still running in protected ClassLoader"); } else if(thread.getThreadGroup() != null && ("system".equals(thread.getThreadGroup().getName()) || // System thread "RMI Runtime".equals(thread.getThreadGroup().getName()))) { // RMI thread (honestly, just copied from Tomcat) if("Keep-Alive-Timer".equals(thread.getName())) { thread.setContextClassLoader(preventor.getLeakSafeClassLoader()); preventor.debug("Changed contextClassLoader of HTTP keep alive thread"); } } else if(thread.isAlive()) { // Non-system, running in protected ClassLoader if(thread.getClass().getName().startsWith("java.util.Timer")) { // Sun/Oracle = "java.util.TimerThread"; IBM = "java.util.Timer$TimerImpl" if(thread.getName() != null && thread.getName().startsWith("PostgreSQL-JDBC-SharedTimer-")) { // Postgresql JDBC timer thread // Replace contextClassLoader, if needed if(hasContextClassLoader) { final Class postgresqlDriver = preventor.findClass("org.postgresql.Driver"); final ClassLoader postgresqlCL = (postgresqlDriver != null && ! preventor.isLoadedByClassLoader(postgresqlDriver)) ? postgresqlDriver.getClassLoader() : // Postgresql driver loaded by other classloader than we want to protect preventor.getLeakSafeClassLoader(); thread.setContextClassLoader(postgresqlCL); preventor.warn("Changing contextClassLoader of " + thread + " to " + postgresqlCL); } // Replace AccessControlContext setThreadSafeAccessControlContext(preventor, thread); } else if(stopTimerThreads) { preventor.warn("Stopping Timer thread '" + thread.getName() + "' running in protected ClassLoader. " + preventor.getStackTrace(thread)); stopTimerThread(preventor, thread); } else { preventor.info("Timer thread is running in protected ClassLoader, but will not be stopped. " + preventor.getStackTrace(thread)); } } else { final String displayString = "Thread '" + thread + "'" + (threadLoadedByClassLoader ? " of type " + thread.getClass().getName() + " loaded by protected ClassLoader" : "") + (runnableLoadedByClassLoader ? " with Runnable of type " + runnable.getClass().getName() + " loaded by protected ClassLoader" : "") + (threadGroupLoadedByClassLoader ? " with ThreadGroup of type " + thread.getThreadGroup().getClass().getName() + " loaded by protected ClassLoader" : "") + (hasContextClassLoader ? " with contextClassLoader = protected ClassLoader or child" : ""); // If threads is running an java.util.concurrent.ThreadPoolExecutor.Worker try shutting down the executor if(workerClass != null && workerClass.isInstance(runnable)) { try { // java.util.concurrent.ThreadPoolExecutor, introduced in Java 1.5 final Field workerExecutor = preventor.findField(workerClass, "this$0"); final ThreadPoolExecutor executor = preventor.getFieldValue(workerExecutor, runnable); if(executor != null) { if("org.apache.tomcat.util.threads.ThreadPoolExecutor".equals(executor.getClass().getName())) { // Tomcat pooled thread preventor.debug(displayString + " is worker of " + executor.getClass().getName()); } else if(preventor.isLoadedInClassLoader(executor) || preventor.isLoadedInClassLoader(executor.getThreadFactory())) { if(stopThreads) { preventor.warn("Shutting down ThreadPoolExecutor of type " + executor.getClass().getName()); executor.shutdownNow(); } else { preventor.warn("ThreadPoolExecutor of type " + executor.getClass().getName() + " should be shut down."); } } else { preventor.info(displayString + " is a ThreadPoolExecutor.Worker of " + executor.getClass().getName() + " but found no reason to shut down ThreadPoolExecutor."); } } } catch (Exception ex) { preventor.error(ex); } } if(! threadLoadedByClassLoader && ! runnableLoadedByClassLoader && ! threadGroupLoadedByClassLoader) { // Not loaded in protected ClassLoader - just running there // This would for example be the case with org.apache.tomcat.util.threads.TaskThread if(waitForThreads) { preventor.warn(displayString + "; waiting " + threadWaitMs + " ms. " + preventor.getStackTrace(thread)); preventor.waitForThread(thread, threadWaitMs, false /* No interrupt */); } if(thread.isAlive() && preventor.isClassLoaderOrChild(thread.getContextClassLoader())) { // Still running in ClassLoader preventor.warn(displayString + (waitForThreads ? " still" : "") + " alive; changing context ClassLoader to leak safe (" + preventor.getLeakSafeClassLoader() + "). " + preventor.getStackTrace(thread)); thread.setContextClassLoader(preventor.getLeakSafeClassLoader()); // Replace AccessControlContext since we already replaced ClassLoader, // for test/use cease @see StopThreadsClenup_ExecutorTest setThreadSafeAccessControlContext(preventor, thread); } } else if(stopThreads) { // Thread/Runnable/ThreadGroup loaded by protected ClassLoader if(waitForThreads) { preventor.warn("Waiting for " + displayString + " for " + threadWaitMs + " ms. " + preventor.getStackTrace(thread)); preventor.waitForThread(thread, threadWaitMs, true /* Interrupt if needed */); } // Normally threads should not be stopped (method is deprecated), since it may cause an inconsistent state. // In this case however, the alternative is a classloader leak, which may or may not be considered worse. if(thread.isAlive()) { preventor.warn("Stopping " + displayString + ". " + preventor.getStackTrace(thread)); //noinspection deprecation thread.stop(); } else { preventor.info(displayString + " no longer alive - no action needed."); } } else { preventor.warn(displayString + " would cause leak. " + preventor.getStackTrace(thread)); } } } } } } /** * Replace Thread AccessControlContext to allow for Protection Domain GC */ private void setThreadSafeAccessControlContext(ClassLoaderLeakPreventor preventor, Thread thread) { // Replace AccessControlContext final Field inheritedAccessControlContext = preventor.findField(Thread.class, "inheritedAccessControlContext"); if(inheritedAccessControlContext != null) { try { final AccessControlContext acc = preventor.createAccessControlContext(); inheritedAccessControlContext.set(thread, acc); preventor.removeDomainCombiner("thread " + thread, acc); } catch (Exception e) { preventor.error(e); } } } /** Get {@link Runnable} of given thread, if any */ private Runnable getRunnable(ClassLoaderLeakPreventor preventor, Thread thread) { if(oracleTarget == null && ibmRunnable == null) { // Not yet initialized oracleTarget = preventor.findField(Thread.class, "target"); // Sun/Oracle JRE ibmRunnable = preventor.findField(Thread.class, "runnable"); // IBM JRE } return (oracleTarget != null) ? (Runnable) preventor.getFieldValue(oracleTarget, thread) : // Sun/Oracle JRE (Runnable) preventor.getFieldValue(ibmRunnable, thread); // IBM JRE } protected void stopTimerThread(ClassLoaderLeakPreventor preventor, Thread thread) { // Seems it is not possible to access Timer of TimerThread, so we need to mimic Timer.cancel() /** try { Timer timer = (Timer) findField(thread.getClass(), "this$0").get(thread); // This does not work! warn("Cancelling Timer " + timer + " / TimeThread '" + thread + "'"); timer.cancel(); } catch (IllegalAccessException iaex) { error(iaex); } */ try { final Field newTasksMayBeScheduled = preventor.findField(thread.getClass(), "newTasksMayBeScheduled"); final Object queue = preventor.findField(thread.getClass(), "queue").get(thread); // java.lang.TaskQueue final Method clear = preventor.findMethod(queue.getClass(), "clear"); // Do what java.util.Timer.cancel() does //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized (queue) { newTasksMayBeScheduled.set(thread, Boolean.FALSE); clear.invoke(queue); queue.notify(); // "In case queue was already empty." } // We shouldn't need to join() here, thread will finish soon enough } catch (Exception ex) { preventor.error(ex); } } /** * Inner class with the sole task of killing JURT finalizer thread after it is done processing jobs. * We need to postpone the stopping of this thread, since more Jobs may in theory be add()ed when the protected * ClassLoader is closing down and being garbage collected. * See https://issues.apache.org/ooo/show_bug.cgi?id=122517 */ protected class JURTKiller extends Thread { private final ClassLoaderLeakPreventor preventor; private final Thread jurtThread; private final List jurtQueue; public JURTKiller(ClassLoaderLeakPreventor preventor, Thread jurtThread) { super("JURTKiller"); this.preventor = preventor; this.jurtThread = jurtThread; jurtQueue = preventor.getStaticFieldValue(StopThreadsCleanUp.JURT_ASYNCHRONOUS_FINALIZER, "queue"); // Make sure all classes are loaded from the current app classloader before it executes, // as it may use them *after* the classloader has been "shutdown" by the container (if any). State state = State.RUNNABLE; } @Override public void run() { try { if(jurtQueue == null || jurtThread == null) { preventor.error(getName() + ": No queue or thread!?"); return; } if(! jurtThread.isAlive()) { preventor.warn(getName() + ": " + jurtThread.getName() + " is already dead?"); } boolean queueIsEmpty = false; while(! queueIsEmpty) { try { preventor.debug(getName() + " goes to sleep for " + ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT + " ms"); Thread.sleep(ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT); } catch (InterruptedException e) { // Do nothing } if(State.RUNNABLE != jurtThread.getState()) { // Unless thread is currently executing a Job preventor.debug(getName() + " about to force Garbage Collection"); ClassLoaderLeakPreventor.gc(); // Force garbage collection, which may put new items on queue synchronized (jurtQueue) { queueIsEmpty = jurtQueue.isEmpty(); preventor.debug(getName() + ": JURT queue is empty? " + queueIsEmpty); } } else preventor.debug(getName() + ": JURT thread " + jurtThread.getName() + " is executing Job"); } preventor.info(getName() + " about to kill " + jurtThread); if(jurtThread.isAlive()) { //noinspection deprecation jurtThread.stop(); } } catch (Throwable t) { preventor.error(t); } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ThreadGroupCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Method; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; import se.jiderhamn.classloader.leak.prevention.MustBeAfter; /** * Destroy any {@link ThreadGroup}s that are loaded by the protected classloader * @author Mattias Jiderhamn */ public class ThreadGroupCleanUp implements ClassLoaderPreMortemCleanUp, MustBeAfter { @Override public Class[] mustBeBeforeMe() { return new Class[] {JavaServerFaces2746CleanUp.class}; } @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { boolean threadGroupDestroyed = false; try { ThreadGroup systemThreadGroup = Thread.currentThread().getThreadGroup(); while(systemThreadGroup.getParent() != null) { systemThreadGroup = systemThreadGroup.getParent(); } // systemThreadGroup should now be the topmost ThreadGroup, "system" int enumeratedGroups; ThreadGroup[] allThreadGroups; int noOfGroups = systemThreadGroup.activeGroupCount(); // Estimate no of groups do { noOfGroups += 10; // Make room for 10 extra allThreadGroups = new ThreadGroup[noOfGroups]; enumeratedGroups = systemThreadGroup.enumerate(allThreadGroups); } while(enumeratedGroups >= noOfGroups); // If there was not room for all groups, try again for(ThreadGroup threadGroup : allThreadGroups) { if(preventor.isLoadedInClassLoader(threadGroup) && ! threadGroup.isDestroyed()) { preventor.warn("ThreadGroup '" + threadGroup + "' was loaded inside application, needs to be destroyed"); int noOfThreads = threadGroup.activeCount(); if(noOfThreads > 0) { preventor.warn("There seems to be " + noOfThreads + " running in ThreadGroup '" + threadGroup + "'; interrupting"); try { threadGroup.interrupt(); } catch (Exception e) { preventor.error(e); } } try { threadGroup.destroy(); threadGroupDestroyed = true; preventor.info("ThreadGroup '" + threadGroup + "' successfully destroyed"); } catch (Exception e) { preventor.error(e); } } } } catch (Exception ex) { preventor.error(ex); } try { final Object contexts = preventor.getStaticFieldValue("java.beans.ThreadGroupContext", "contexts"); if(contexts != null) { // Since Java 1.7 if(threadGroupDestroyed) // At least one ThreadGroup destroyed by this clean up ClassLoaderLeakPreventor.gc(); // Force GC so WeakIdentityMap turns destroyed ThreadGroups into stale entries final Method removeStaleEntries = preventor.findMethod("java.beans.WeakIdentityMap", "removeStaleEntries"); if(removeStaleEntries != null) removeStaleEntries.invoke(contexts); } } catch (Throwable t) { // IllegalAccessException, InvocationTargetException preventor.warn(t); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ThreadGroupContextCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Method; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * Clean all {@link java.beans.ThreadGroupContext#beanInfoCache}s in {@link java.beans.ThreadGroupContext#contexts} * since they may contain beans/properties loaded in the protected classloader. * @author Mattias Jiderhamn */ public class ThreadGroupContextCleanUp implements ClassLoaderPreMortemCleanUp { @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final Object /*WeakIdentityMap*/ contexts = preventor.getStaticFieldValue("java.beans.ThreadGroupContext", "contexts"); if(contexts != null) { // Since Java 1.7 // final WeakReference/*java.beans.WeakIdentityMap.Entry*/[] table = preventor.getFieldValue(contexts, "table"); final Field tableField = preventor.findField(preventor.findClass("java.beans.WeakIdentityMap"), "table"); if(tableField != null) { final WeakReference/*java.beans.WeakIdentityMap.Entry*/[] table = preventor.getFieldValue(tableField, contexts); if(table != null) { Method clearBeanInfoCache = null; for(WeakReference entry : table) { if(entry != null) { Object /*ThreadGroupContext*/ context = preventor.getFieldValue(entry, "value"); if(context != null) { if(clearBeanInfoCache == null) { // FirstThreadGroupContext clearBeanInfoCache = preventor.findMethod(context.getClass(), "clearBeanInfoCache"); } try { clearBeanInfoCache.invoke(context); } catch (Exception e) { preventor.error(e); } } } } } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/ThreadLocalCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.ref.Reference; import java.lang.reflect.Field; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; import se.jiderhamn.classloader.leak.prevention.MustBeAfter; /** * Clear {@link ThreadLocal}s for which {@link ThreadLocal#remove()} has not been called, in case either the * {@link ThreadLocal} is a custom one (subclassed in the protected ClassLoader), or the value is loaded by (or is) * the protected ClassLoader. * This must be done after threads have been stopped, or new ThreadLocals may be added by those threads. * @author Mattias Jiderhamn */ @SuppressWarnings("WeakerAccess") public class ThreadLocalCleanUp implements ClassLoaderPreMortemCleanUp, MustBeAfter { /** Class name for per thread transaction in Caucho Resin transaction manager */ private static final String CAUCHO_TRANSACTION_IMPL = "com.caucho.transaction.TransactionImpl"; protected Field java_lang_Thread_threadLocals; protected Field java_lang_Thread_inheritableThreadLocals; protected Field java_lang_ThreadLocal$ThreadLocalMap_table; protected Field java_lang_ThreadLocal$ThreadLocalMap$Entry_value; /** Needs to be done after {@link StopThreadsCleanUp}, since new {@link ThreadLocal}s may be added when threads are * shutting down. */ @Override public Class[] mustBeBeforeMe() { return new Class[] {StopThreadsCleanUp.class}; } @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { initFields(preventor); // Initialize some reflection variables if(java_lang_Thread_threadLocals == null) preventor.error("java.lang.Thread.threadLocals not found; something is seriously wrong!"); if(java_lang_Thread_inheritableThreadLocals == null) preventor.error("java.lang.Thread.inheritableThreadLocals not found; something is seriously wrong!"); if(java_lang_ThreadLocal$ThreadLocalMap_table == null) preventor.error("java.lang.ThreadLocal$ThreadLocalMap.table not found; something is seriously wrong!"); for(Thread thread : preventor.getAllThreads()) { forEachThreadLocalInThread(preventor, thread); } } /** Make sure fields are initialized */ private void initFields(ClassLoaderLeakPreventor preventor) { if(java_lang_Thread_threadLocals == null) { // First invokation of this preventor java_lang_Thread_threadLocals = preventor.findField(Thread.class, "threadLocals"); java_lang_Thread_inheritableThreadLocals = preventor.findField(Thread.class, "inheritableThreadLocals"); java_lang_ThreadLocal$ThreadLocalMap_table = preventor.findFieldOfClass("java.lang.ThreadLocal$ThreadLocalMap", "table"); } } protected void forEachThreadLocalInThread(ClassLoaderLeakPreventor preventor, Thread thread) { try { if(java_lang_Thread_threadLocals != null) { processThreadLocalMap(preventor, thread, java_lang_Thread_threadLocals.get(thread)); } if(java_lang_Thread_inheritableThreadLocals != null) { processThreadLocalMap(preventor, thread, java_lang_Thread_inheritableThreadLocals.get(thread)); } } catch (/*IllegalAccess*/Exception ex) { preventor.error(ex); } } protected void processThreadLocalMap(ClassLoaderLeakPreventor preventor, Thread thread, Object threadLocalMap) throws IllegalAccessException { if(threadLocalMap != null && java_lang_ThreadLocal$ThreadLocalMap_table != null) { Field resin_suspendState = null; Field resin_isSuspended = null; final Object[] threadLocalMapTable = (Object[]) java_lang_ThreadLocal$ThreadLocalMap_table.get(threadLocalMap); // java.lang.ThreadLocal.ThreadLocalMap.Entry[] for(Object entry : threadLocalMapTable) { if(entry != null) { // Key is kept in WeakReference Reference reference = (Reference) entry; final ThreadLocal threadLocal = (ThreadLocal) reference.get(); if(java_lang_ThreadLocal$ThreadLocalMap$Entry_value == null) { java_lang_ThreadLocal$ThreadLocalMap$Entry_value = preventor.findField(entry.getClass(), "value"); } // Dereference the value if this is a Reference: all Reference implementations are all loaded using the bootstrap classloader, // so checking the Reference classloader won't indicate if the held value was itself loaded using the app classloader // We could have called Reference.clear() directly, which would have fixed the leak even when not allowed to modify the ThreadLocalMap.Entry final Object value = dereferenceIfApplicable(java_lang_ThreadLocal$ThreadLocalMap$Entry_value.get(entry)); // Workaround for http://bugs.caucho.com/view.php?id=5647 if(value != null && CAUCHO_TRANSACTION_IMPL.equals(value.getClass().getName())) { // Resin transaction if(resin_suspendState == null && resin_isSuspended == null) { // First thread with Resin transaction, look up fields resin_suspendState = preventor.findField(value.getClass(), "_suspendState"); resin_isSuspended = preventor.findField(value.getClass(), "_isSuspended"); } if(resin_suspendState != null && resin_isSuspended != null) { // Both fields exist (as per version 4.0.37) if(preventor.getFieldValue(resin_suspendState, value) != null) { // There is a suspended state that may cause leaks // In theory a new transaction can be started and suspended between where we read and write the state, // and flag, therefore we suspend the thread meanwhile. try { //noinspection deprecation thread.suspend(); // Suspend the thread if(preventor.getFieldValue(resin_suspendState, value) != null) { // Re-read suspend state when thread is suspended final Object isSuspended = preventor.getFieldValue(resin_isSuspended, value); if(!(isSuspended instanceof Boolean)) { preventor.error(thread.toString() + " has " + CAUCHO_TRANSACTION_IMPL + " but _isSuspended is not boolean: " + isSuspended); } else if((Boolean) isSuspended) { // Is currently suspended - suspend state is correct preventor.debug(thread.toString() + " has " + CAUCHO_TRANSACTION_IMPL + " that is suspended"); } else { // Is not suspended, and thus should not have suspend state resin_suspendState.set(value, null); preventor.error(thread.toString() + " had " + CAUCHO_TRANSACTION_IMPL + " with unused _suspendState that was removed"); } } } catch (Throwable t) { // Such as SecurityException preventor.error(t); } finally { //noinspection deprecation thread.resume(); } } } } final boolean customThreadLocal = preventor.isLoadedInClassLoader(threadLocal); // This is not an actual problem final boolean valueLoadedInWebApp = preventor.isLoadedInClassLoader(value); if(customThreadLocal || valueLoadedInWebApp || (value instanceof ClassLoader && preventor.isClassLoaderOrChild((ClassLoader) value))) { // The value is classloader (child) itself // This ThreadLocal is either itself loaded by the web app classloader, or it's value is // Let's do something about it StringBuilder message = new StringBuilder(); if(threadLocal != null) { if(customThreadLocal) { message.append("Custom "); } message.append("ThreadLocal of type ").append(threadLocal.getClass().getName()).append(": ").append(threadLocal); } else { message.append("Unknown ThreadLocal"); } message.append(" with value ").append(value); if(value != null) { message.append(" of type ").append(value.getClass().getName()); if(valueLoadedInWebApp) message.append(" that is loaded by web app"); } // Process the detected potential leak processLeak(preventor, thread, reference, threadLocal, value, message.toString()); } } } } } protected Object dereferenceIfApplicable(Object value) { return value instanceof Reference ? dereferenceIfApplicable(((Reference) value).get()) : value; } /** * After having detected potential ThreadLocal leak, this method is called. * Default implementation tries to clear the entry to avoid a leak. */ protected void processLeak(ClassLoaderLeakPreventor preventor, Thread thread, Reference entry, ThreadLocal threadLocal, Object value, String message) { if(threadLocal != null && thread == Thread.currentThread()) { // If running for current thread and we have the ThreadLocal ... // ... remove properly preventor.info(message + " will be remove()d from " + thread); threadLocal.remove(); } else { // We cannot remove entry properly, so just make it stale preventor.info(message + " will be made stale for later expunging from " + thread); } // It seems like remove() doesn't really do the job, so play it safe and remove references from entry either way // (Example problem org.infinispan.context.SingleKeyNonTxInvocationContext) entry.clear(); // Clear the key if(java_lang_ThreadLocal$ThreadLocalMap$Entry_value == null) { java_lang_ThreadLocal$ThreadLocalMap$Entry_value = preventor.findField(entry.getClass(), "value"); } try { java_lang_ThreadLocal$ThreadLocalMap$Entry_value.set(entry, null); // Clear value to avoid circular references } catch (IllegalAccessException iaex) { preventor.error(iaex); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/WarningThreadLocalCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.ref.Reference; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * {@link ClassLoaderPreMortemCleanUp} that does not clear {@link ThreadLocal}s to remove the leak, but only logs a * warning * @author Mattias Jiderhamn */ @SuppressWarnings("unused") public class WarningThreadLocalCleanUp extends ThreadLocalCleanUp { /** * Log not {@link ThreadLocal#remove()}ed leak as a warning. */ protected void processLeak(ClassLoaderLeakPreventor preventor, Thread thread, Reference entry, ThreadLocal threadLocal, Object value, String message) { preventor.warn(message); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/cleanup/X509TrustManagerImplUnparseableExtensionCleanUp.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.security.cert.X509Certificate; import java.util.Map; import javax.net.ssl.SSLContextSpi; import javax.net.ssl.X509TrustManager; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; /** * {@link sun.security.ssl.X509TrustManagerImpl} keeps a list set of trusted certs, which may include * {@link sun.security.x509.UnparseableExtension} that in turn may include an {@link Exception} with a backtrace * with references to the classloader that we want to protect * @author Mattias Jiderhamn */ public class X509TrustManagerImplUnparseableExtensionCleanUp implements ClassLoaderPreMortemCleanUp { private static final String SUN_SECURITY_X509_X509_CERT_IMPL = "sun.security.x509.X509CertImpl"; @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { final SSLContextSpi sslContext = preventor.getStaticFieldValue("sun.security.ssl.SSLContextImpl$DefaultSSLContext", "defaultImpl"); if(sslContext != null) { final Field trustManagerField = preventor.findFieldOfClass("sun.security.ssl.SSLContextImpl", "trustManager"); final Method get = preventor.findMethod(SUN_SECURITY_X509_X509_CERT_IMPL, "get", String.class); final Method getUnparseableExtensions = preventor.findMethod("sun.security.x509.CertificateExtensions", "getUnparseableExtensions"); final Field why = preventor.findFieldOfClass("sun.security.x509.UnparseableExtension", "why"); if(trustManagerField != null && get != null && getUnparseableExtensions != null && why != null) { final X509TrustManager/*Impl*/ trustManager = preventor.getFieldValue(trustManagerField, sslContext); for(X509Certificate x509Certificate : trustManager.getAcceptedIssuers()) { if(SUN_SECURITY_X509_X509_CERT_IMPL.equals(x509Certificate.getClass().getName())) { try { final /* sun.security.x509.CertificateExtensions*/ Object extensions = get.invoke(x509Certificate, "x509.info.extensions"); if(extensions != null) { Map/**/ unparseableExtensions = (Map) getUnparseableExtensions.invoke(extensions); for(Object unparseableExtension : unparseableExtensions.values()) { if(why.get(unparseableExtension) != null) { preventor.warn(trustManager + " cached X509Certificate that had unparseable extension; removing 'why': " + x509Certificate); why.set(unparseableExtension, null); } } } } catch (Exception e) { preventor.error(e); } } } } } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/AwtToolkitInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * The first call to java.awt.Toolkit.getDefaultToolkit() will spawn a new thread with the * same contextClassLoader as the caller. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * @author Mattias Jiderhamn */ public class AwtToolkitInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { java.awt.Toolkit.getDefaultToolkit(); // Will start a Thread } catch (Throwable t) { preventor.error(t); preventor.warn("Consider adding -Djava.awt.headless=true to your JVM parameters"); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/DatatypeConverterImplInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * {@link javax.xml.bind.DatatypeConverterImpl} in the JAXB Reference Implementation shipped with JDK 1.6+ will * keep a static reference ({@link javax.xml.bind.DatatypeConverterImpl#datatypeFactory}) to a concrete subclass of * {@link javax.xml.datatype.DatatypeFactory}, that is resolved when the class is loaded (which I believe happens if you * have custom bindings that reference the static methods in {@link javax.xml.bind.DatatypeConverter}). It seems that if * for example you have a version of Xerces inside your application, the factory method may resolve {@code * org.apache.xerces.jaxp.datatype.DatatypeFactoryImpl} as the implementation to use (rather than * {@code com.sun.org.apache.xerces.internal.jaxp.datatype.DatatypeFactoryImpl} shipped with the JDK), which * means there will a reference from {@link javax.xml.bind.DatatypeConverterImpl} to your classloader. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * @author Mattias Jiderhamn */ public class DatatypeConverterImplInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { Class.forName("javax.xml.bind.DatatypeConverterImpl"); // Since JDK 1.6. May throw java.lang.Error } catch (ClassNotFoundException e) { // Do nothing } catch (Throwable t) { preventor.warn(t); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/DocumentBuilderFactoryInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * The classloader of the first thread to call DocumentBuilderFactory.newInstance().newDocumentBuilder() * seems to be unable to garbage collection. Is it believed this is caused by some JVM internal bug. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * @author Mattias Jiderhamn */ public class DocumentBuilderFactoryInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { javax.xml.parsers.DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (Exception ex) { // Example: ParserConfigurationException preventor.error(ex); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/JarUrlConnectionInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import java.net.URL; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * The caching mechanism of JarURLConnection can prevent JAR files to be reloaded. See * this bug report. * It is not entirely clear whether this will actually leak classloaders. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * @author Mattias Jiderhamn */ public class JarUrlConnectionInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { // This probably does not affect classloaders, but prevents some problems with .jar files try { // URL needs to be well-formed, but does not need to exist new URL("jar:file://dummy.jar!/").openConnection().setDefaultUseCaches(false); } catch (Exception ex) { preventor.error(ex); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/Java2dDisposerInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * Loading the class sun.java2d.Disposer will spawn a new thread with the same contextClassLoader. * More info. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * @author Mattias Jiderhamn */ public class Java2dDisposerInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { Class.forName("sun.java2d.Disposer"); // Will start a Thread } catch (ClassNotFoundException cnfex) { if(preventor.isOracleJRE() && ! preventor.isJBoss()) // JBoss blocks this package/class, so don't warn preventor.error(cnfex); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/Java2dRenderQueueInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; import java.lang.reflect.Method; /** * Using the class sun.java2d.opengl.OGLRenderQueue will spawn a new QueueFlusher thread with the same contextClassLoader. */ public class Java2dRenderQueueInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { Method getInstance = preventor.findMethod("sun.java2d.opengl.OGLRenderQueue", "getInstance"); if (getInstance != null) { getInstance.invoke(null); } } catch (Throwable e) { preventor.warn(e); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/JavaxSecurityLoginConfigurationInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * The class javax.security.auth.login.Configuration will keep a strong static reference to the * contextClassLoader of Thread from which the class is loaded. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * @author Mattias Jiderhamn */ public class JavaxSecurityLoginConfigurationInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { Class.forName("javax.security.auth.login.Configuration", true, preventor.getLeakSafeClassLoader()); } catch (ClassNotFoundException e) { // Do nothing } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/JdbcDriversInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; import se.jiderhamn.classloader.leak.prevention.cleanup.DriverManagerCleanUp; /** * Your JDBC driver will be registered in java.sql.DriverManager, which means that if * you include your JDBC driver inside your web application, there will be a reference * to your webapps classloader from system classes (see * part II). * The simple solution is to put JDBC driver on server level instead, but you can also * deregister the driver at application shutdown. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * TODO {@link DriverManagerCleanUp} * @author Mattias Jiderhamn */ public class JdbcDriversInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { java.sql.DriverManager.getDrivers(); // Load initial drivers using leak safe classloader } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/LdapPoolManagerInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * The contextClassLoader of the thread loading the com.sun.jndi.ldap.LdapPoolManager class may be kept * from being garbage collected, since it will start a new thread if the system property * {@code com.sun.jndi.ldap.connect.pool.timeout} is set to a value greater than 0. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * @author Mattias Jiderhamn */ public class LdapPoolManagerInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { Class.forName("com.sun.jndi.ldap.LdapPoolManager"); } catch(ClassNotFoundException cnfex) { if(preventor.isOracleJRE()) preventor.error(cnfex); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/OracleJdbcThreadInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.util.Set; import javax.management.MBeanServer; import javax.management.ObjectName; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; import se.jiderhamn.classloader.leak.prevention.ReplaceDOMNormalizerSerializerAbortException; /** * See https://github.com/mjiderhamn/classloader-leak-prevention/issues/8 * and https://github.com/mjiderhamn/classloader-leak-prevention/issues/23 * and https://github.com/mjiderhamn/classloader-leak-prevention/issues/69 * and http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * @author Mattias Jiderhamn */ public class OracleJdbcThreadInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { // Cause oracle.jdbc.driver.OracleTimeoutPollingThread to be started with contextClassLoader = system classloader try { Class.forName("oracle.jdbc.driver.OracleTimeoutThreadPerVM"); } catch (ClassNotFoundException e) { // Ignore silently - class not present } // Cause oracle.jdbc.driver.BlockSource.ThreadedCachingBlockSource.BlockReleaser to be started with contextClassLoader = system classloader try { Class.forName("oracle.jdbc.driver.BlockSource$ThreadedCachingBlockSource$BlockReleaser"); } catch (ClassNotFoundException e) { // Ignore silently - class not present } // Cause TimerThread to be started with contextClassLoader = safe classloader try { Class.forName("oracle.net.nt.TimeoutInterruptHandler"); } catch (ClassNotFoundException e) { // Ignore silently - class not present } // Cause instance to be created and in turn Timer and TimeThread to be created with contextClassLoader = safe classloader try { Class.forName("oracle.jdbc.driver.NoSupportHAManager"); } catch (ClassNotFoundException e) { // Ignore silently - class not present } // Avoid stack trace with trace elements being referenced from MBean try { Class.forName("oracle.jdbc.driver.OracleDriver"); // Cause oracle.jdbc.driver.OracleDiagnosabilityMBean to be registered final Class oracleDiagnosabilityMBeanClass = Class.forName("oracle.jdbc.driver.OracleDiagnosabilityMBean"); final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); final Set mBeanNames = mBeanServer.queryNames(new ObjectName("com.oracle.jdbc:type=diagnosability,*"), null); for(ObjectName mBeanName : mBeanNames) { final /* oracle.jdbc.driver.OracleDiagnosabilityMBean */ Object oracleDiagnosabilityMBean = oracleDiagnosabilityMBeanClass.newInstance(); final /* oracle.jdbc.logging.runtime.TraceControllerImpl */ Object traceController = preventor.getFieldValue(oracleDiagnosabilityMBean, "tc"); if(traceController != null) { final Field reSuspendedField = preventor.findField(traceController.getClass(), "reSuspended"); if(reSuspendedField != null) { final Object oldValue = reSuspendedField.get(traceController); reSuspendedField.set(traceController, ReplaceDOMNormalizerSerializerAbortException.constructRuntimeExceptionWithoutStackTrace(preventor, (oldValue instanceof Exception) ? ((Exception)oldValue).getMessage() : "trace controller is currently suspended", null)); preventor.info("Replacing MBean " + mBeanName + " with " + reSuspendedField.getName() + " field of " + traceController + " replaced to avoid backtrace references."); // Replace MBean mBeanServer.unregisterMBean(mBeanName); mBeanServer.registerMBean(oracleDiagnosabilityMBean, mBeanName); } else preventor.warn("Unable to find 'reSuspended' field of " + traceController); } else preventor.warn("Found " + oracleDiagnosabilityMBeanClass + " but it has no 'tm' TraceController attribute"); } } catch (Exception e) { // Ignore silently } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/SecurityPolicyInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import java.lang.reflect.InvocationTargetException; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * javax.security.auth.Policy.getPolicy() will keep a strong static reference to * the contextClassLoader of the first calling thread. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * @author Mattias Jiderhamn */ public class SecurityPolicyInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { Class.forName("javax.security.auth.Policy") .getMethod("getPolicy") .invoke(null); } catch (IllegalAccessException iaex) { preventor.error(iaex); } catch (InvocationTargetException itex) { preventor.error(itex); } catch (NoSuchMethodException nsmex) { preventor.error(nsmex); } catch (ClassNotFoundException e) { // Ignore silently - class is deprecated } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/SecurityProvidersInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * Custom java.security.Provider loaded in your web application and registered with * java.security.Security.addProvider() must be unregistered with java.security.Security.removeProvider() * at application shutdown, or it will cause leaks. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * @author Mattias Jiderhamn */ public class SecurityProvidersInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { java.security.Security.getProviders(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/SunAwtAppContextInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import javax.swing.*; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * There will be a strong reference from {@link sun.awt.AppContext#contextClassLoader} to the classloader of the calls * to {@link sun.awt.AppContext#getAppContext()}. Avoid leak by forcing initialization using system classloader. * Note that Google Web Toolkit (GWT) will trigger this leak via its use of javax.imageio. * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * @author Mattias Jiderhamn */ public class SunAwtAppContextInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { javax.imageio.ImageIO.getCacheDirectory(); // Will call sun.awt.AppContext.getAppContext() new JEditorPane("text/plain", "dummy"); // According to GitHub user dany52, the above is not enough } catch (Throwable t) { preventor.error(t); preventor.warn("Consider adding -Djava.awt.headless=true to your JVM parameters"); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/main/java/se/jiderhamn/classloader/leak/prevention/preinit/SunGCInitiator.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; /** * sun.misc.GC.requestLatency(long), which is known to be called from * javax.management.remote.rmi.RMIConnectorServer.start(), will cause the current * contextClassLoader to be unavailable for garbage collection. * * See http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java?view=markup#l106 and * http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java?view=markup#l296 * * See http://java.jiderhamn.se/2012/02/26/classloader-leaks-v-common-mistakes-and-known-offenders/ * * @author Mattias Jiderhamn */ public class SunGCInitiator implements PreClassLoaderInitiator { @Override public void doOutsideClassLoader(ClassLoaderLeakPreventor preventor) { try { Class gcClass = this.getGCClass(); final Method requestLatency = gcClass.getDeclaredMethod("requestLatency", long.class); requestLatency.setAccessible(true); requestLatency.invoke(null, Long.valueOf(Long.MAX_VALUE - 1)); } catch (ClassNotFoundException cnfex) { if(preventor.isOracleJRE()) preventor.error(cnfex); } catch (NoSuchMethodException nsmex) { preventor.error(nsmex); } catch (IllegalAccessException iaex) { preventor.error(iaex); } catch (InvocationTargetException itex) { preventor.error(itex); } } private Class getGCClass() throws ClassNotFoundException { try { return Class.forName("sun.misc.GC"); } catch (ClassNotFoundException cnfex) { // Try Jre 9 classpath return Class.forName("sun.rmi.transport.GC"); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorFactoryTest.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.util.ArrayList; import java.util.List; import org.junit.Test; import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; /** * Test cases for {@link ClassLoaderLeakPreventorFactory} * @author Mattias Jiderhamn */ public class ClassLoaderLeakPreventorFactoryTest { /** Test that {@link MustBeAfter} has expected effect on {@link ClassLoaderPreMortemCleanUp}s */ @Test(expected = IllegalStateException.class) // TODO #51 public void cleanUpMustBeAfter() { final List executionOrder = new ArrayList(); final RecordingCleanUp unordered1 = new RecordingCleanUp(executionOrder) { }; final RecordingCleanUp unordered2 = new RecordingCleanUp(executionOrder) { }; final Foo foo = new Foo(executionOrder); final Bar bar = new Bar(executionOrder); final AfterFoo afterFoo1 = new AfterFoo(executionOrder) { }; final AfterFoo afterFoo2 = new AfterFoo(executionOrder) { }; final AfterBar afterBar = new AfterBar(executionOrder); final AfterFooAndBar afterFooAndBar = new AfterFooAndBar(executionOrder); ClassLoaderLeakPreventorFactory factory = new ClassLoaderLeakPreventorFactory(); factory.addCleanUp(unordered2); factory.addCleanUp(bar); factory.addCleanUp(afterFooAndBar); factory.addCleanUp(afterBar); factory.addCleanUp(unordered1); factory.addCleanUp(afterFoo2); factory.addCleanUp(afterFoo1); factory.addCleanUp(foo); // TODO #51 factory.newLeakPreventor().runCleanUps(); // Make sure imposed order is achieved, while retaining order among moved elements assertThat(executionOrder, contains((ClassLoaderPreMortemCleanUp) unordered2, bar, afterBar, unordered1, foo, afterFooAndBar, // Moved afterFoo2, // Moved afterFoo1)); // Moved /////////// // Subclass executionOrder.clear(); final Foo fooSubClass = new Foo(executionOrder) { }; factory.addCleanUp(fooSubClass); factory.newLeakPreventor().runCleanUps(); // Make sure imposed order is achieved, while retaining order among moved elements assertThat(executionOrder, contains((ClassLoaderPreMortemCleanUp) unordered2, bar, afterBar, unordered1, foo, fooSubClass, // Inserted afterFooAndBar, afterFoo2, afterFoo1)); } @Test(expected = IllegalStateException.class) public void circularMustBeAfter() { ClassLoaderLeakPreventorFactory factory = new ClassLoaderLeakPreventorFactory(); factory.addCleanUp(new Circle1()); factory.addCleanUp(new Circle2()); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** Base class for {@link ClassLoaderPreMortemCleanUp}s that will record their execution */ private abstract static class RecordingCleanUp implements ClassLoaderPreMortemCleanUp { private final List cleanUps; RecordingCleanUp(List cleanUps) { this.cleanUps = cleanUps; } @Override public void cleanUp(ClassLoaderLeakPreventor preventor) { cleanUps.add(this); } @Override public String toString() { return this.getClass().getName() + "@" + System.identityHashCode(this); } } private static class Foo extends RecordingCleanUp { Foo(List cleanUps) { super(cleanUps); } } private static class Bar extends RecordingCleanUp { Bar(List cleanUps) { super(cleanUps); } } private static class AfterFoo extends RecordingCleanUp implements MustBeAfter { AfterFoo(List cleanUps) { super(cleanUps); } @Override public Class[] mustBeBeforeMe() { return new Class[] {Foo.class}; } } private static class AfterBar extends RecordingCleanUp implements MustBeAfter { AfterBar(List cleanUps) { super(cleanUps); } @Override public Class[] mustBeBeforeMe() { return new Class[] {Bar.class}; } } private static class AfterFooAndBar extends RecordingCleanUp implements MustBeAfter { AfterFooAndBar(List cleanUps) { super(cleanUps); } @Override public Class[] mustBeBeforeMe() { return new Class[] {Foo.class, Bar.class}; } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// private static class Circle1 extends RecordingCleanUp implements MustBeAfter { Circle1() { super(null); } @Override public Class[] mustBeBeforeMe() { return new Class[] {Circle2.class}; } } private static class Circle2 extends RecordingCleanUp implements MustBeAfter { Circle2() { super(null); } @Override public Class[] mustBeBeforeMe() { return new Class[] {Circle2.class}; } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/PreventionsTestBase.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.lang.reflect.ParameterizedType; import java.util.Collections; /** * Base class for test cases testing {@link PreClassLoaderInitiator} and {@link ClassLoaderPreMortemCleanUp} implementations. * @author Mattias Jiderhamn */ public abstract class PreventionsTestBase { /** * Get an instance of the implementation under test. Will use the generics parameter type information. */ protected C getTestedImplementation() throws IllegalAccessException, InstantiationException { ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass(); Object actualType = genericSuperclass.getActualTypeArguments()[genericSuperclass.getActualTypeArguments().length - 1]; Class cleanUpImplClass = (actualType instanceof ParameterizedType) ? (Class) ((ParameterizedType)actualType).getRawType() : (Class) actualType; return cleanUpImplClass.newInstance(); } /** * Concrete tests may override this method, in case they to provide a specific {@link ClassLoaderLeakPreventor} * to the {@link ClassLoaderPreMortemCleanUp}. * @return */ protected ClassLoaderLeakPreventor getClassLoaderLeakPreventor() { return new ClassLoaderLeakPreventor(getLeakSafeClassLoader(), getClass().getClassLoader(), new StdLogger(), Collections.emptyList(), Collections.emptyList()); } /** * Get {@link ClassLoader} to be used as the {@link ClassLoaderLeakPreventor#leakSafeClassLoader} of the * {@link ClassLoaderLeakPreventor}. This is normally the parent of the classloader of the test class. */ protected ClassLoader getLeakSafeClassLoader() { return getClass().getClassLoader().getParent(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/StopThreadsCleanUp_TimerTest.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.util.Collection; import java.util.Timer; import org.junit.Test; import org.junit.runner.RunWith; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; import se.jiderhamn.classloader.leak.LeakPreventor; import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp; /** * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) @LeakPreventor(StopThreadsCleanUp_TimerTest.Preventor.class) public class StopThreadsCleanUp_TimerTest { /** * Having a custom ThreadLocal with at non-custom value does not leak, since the key in the ThreadLocalMap is weak */ @Test public void createTimer() throws IllegalAccessException, NoSuchFieldException { new Timer("MyTimer"); // Create new Timer to spawn new TimerThread Thread.yield(); // Allow the Timer thread to start } public static class Preventor implements Runnable { public void run() { ClassLoaderLeakPreventorFactory factory = new ClassLoaderLeakPreventorFactory(); final ClassLoaderLeakPreventor preventor = factory.newLeakPreventor(); final TimerThreadsCleanUp timerThreadsCleanUp = new TimerThreadsCleanUp(); final Collection threads = preventor.getAllThreads(); for(Thread thread : threads) { if("java.util.TimerThread".equals(thread.getClass().getName())) { System.out.println(thread + " is a TimerThread"); timerThreadsCleanUp.stopTimerThread(preventor, thread); try { thread.join(10000); // Give thread up to 10 seconds to finish } catch (InterruptedException e) { // Silently ignore } } } } } private static class TimerThreadsCleanUp extends StopThreadsCleanUp { public TimerThreadsCleanUp() { super(true, true); } /** Change visibility */ @Override public void stopTimerThread(ClassLoaderLeakPreventor preventor, Thread thread) { super.stopTimerThread(preventor, thread); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/BeanELResolverCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import javax.el.*; import org.junit.Before; /** * Test case for {@link BeanELResolverCleanUp} * @author Mattias Jiderhamn */ public class BeanELResolverCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Before public void setUp() { // Must be done outside test classloader javax.imageio.ImageIO.getCacheDirectory(); // Will call sun.awt.AppContext.getAppContext() } @Override protected void triggerLeak() throws Exception { BeanELResolver beanELResolver = new BeanELResolver(); beanELResolver.getValue(new MyELContext(), new Bean(), "foo"); // Will put class in strong reference cache } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** Bean for testing */ @SuppressWarnings("unused") public static class Bean { private String foo; public String getFoo() { return foo; } public void setFoo(String foo) { this.foo = foo; } } /** Dummy ELContext */ private static class MyELContext extends ELContext { @Override public ELResolver getELResolver() { throw new UnsupportedOperationException("dummy"); } @Override public FunctionMapper getFunctionMapper() { throw new UnsupportedOperationException("dummy"); } @Override public VariableMapper getVariableMapper() { throw new UnsupportedOperationException("dummy"); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/BeanIntrospectorCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.beans.Introspector; /** * Test case for {@link BeanIntrospectorCleanUp} */ public class BeanIntrospectorCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { Introspector.getBeanInfo(Bean.class); } protected class Bean { private int dummyField; public int getDummyField() { return dummyField; } public void setDummyField(int dummyField) { this.dummyField = dummyField; } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/BeanValidationCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import org.apache.axis.utils.XMLUtils; import org.junit.Ignore; /** * Test case for {@link BeanValidationCleanUp} * @author Mattias Jiderhamn */ public class BeanValidationCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { javax.validation.Validation.buildDefaultValidatorFactory(); } /** * Test case that {@link ThreadLocalCleanUp} fixes leak caused by Axis 1.4 * @author Mattias Jiderhamn */ @Ignore // Fixed in newer versions of Java??? public static class ThreadLocalCleanUp_ApacheAxis14Test extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { // Trigger leak of Axis 1.4 XMLUtils.getDocumentBuilder(); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ClassLoaderPreMortemCleanUpTestBase.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import org.junit.Test; import org.junit.runner.RunWith; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; import se.jiderhamn.classloader.leak.Leaks; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; import se.jiderhamn.classloader.leak.prevention.PreventionsTestBase; /** * Abstract base class for testing {@link ClassLoaderPreMortemCleanUp} implementations. * TODO Move this to test framework(?) * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) public abstract class ClassLoaderPreMortemCleanUpTestBase extends PreventionsTestBase { /** * Test case that verifies that {@link #triggerLeak()} indeed does cause a leak, in case the * {@link ClassLoaderPreMortemCleanUp} is not executed. */ @SuppressWarnings("DefaultAnnotationParam") @Test @Leaks(true) // Without the cleanup we should expect a leak public void triggerLeakWithoutCleanup() throws Exception { triggerLeak(); } @Test @Leaks(false) // After having run the cleanup, there should be no leak public void cleanUpAfterTriggeringLeak() throws Exception { triggerLeak(); getClassLoaderPreMortemCleanUp().cleanUp(getClassLoaderLeakPreventor()); } /** Concrete tests should implement this method to trigger the leak */ protected abstract void triggerLeak() throws Exception; /** * Concrete tests may override this method to return a {@link ClassLoaderPreMortemCleanUp} * that will clean up after the leak triggered by {@link #triggerLeak()}, so that {@link ClassLoader} can be * garbage collected. * The default implementation will use the generics parameter type information. */ @SuppressWarnings("WeakerAccess") protected C getClassLoaderPreMortemCleanUp() throws IllegalAccessException, InstantiationException { return getTestedImplementation(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/DefaultAuthenticatorCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import org.apache.cxf.transport.http.CXFAuthenticator; /** * Test that the leak caused by CXF custom {@link java.net.Authenticator} is cleared. * Thanks to Arild Froeland for the report. * @author Mattias Jiderhamn */ public class DefaultAuthenticatorCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { CXFAuthenticator.addAuthenticator(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/DriverManagerCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import se.jiderhamn.classloader.PackagesLoadedOutsideClassLoader; /** * Test case for {@link DriverManagerCleanUp} * @author Mattias Jiderhamn */ @PackagesLoadedOutsideClassLoader(packages = "org.postgresql", addToDefaults = true) // Postgresql driver not part of test public class DriverManagerCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws ClassNotFoundException { Class.forName("com.mysql.jdbc.Driver"); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/GeoToolsCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import org.geotools.util.WeakCollectionCleaner; /** * Test case for {@link GeoToolsCleanUp} * @author Mattias Jiderhamn */ public class GeoToolsCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { WeakCollectionCleaner.DEFAULT.getReferenceQueue(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/IIOServiceProviderCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import javax.imageio.ImageIO; import javax.swing.*; import org.junit.Before; /** * Test case for {@link IIOServiceProviderCleanUp} * @author Thomas Scheffler (1.x version) * @author Mattias Jiderhamn */ public class IIOServiceProviderCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { // ImageIO.scanForPlugins() triggers two different leaks, but the preventor only removes one, so we need to avoid // the other one by running some code in parent classloader @Before public void systemClassLoader() { new JEditorPane("text/plain", "dummy"); // java.awt.Toolkit.getDefaultToolkit() } @Override protected void triggerLeak() throws Exception { ImageIO.scanForPlugins(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ImageIOMockImageInputStreamSPI.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.io.File; import java.io.IOException; import java.util.Locale; import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.stream.ImageInputStream; public class ImageIOMockImageInputStreamSPI extends ImageInputStreamSpi { @Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) throws IOException { throw new IllegalArgumentException("mock class"); } @Override public String getDescription(Locale locale) { return "mock ImageInputStream provider"; } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/JDK8151486CleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import org.junit.Ignore; import java.security.Permission; /** * Test case for {@link JDK8151486CleanUp} */ @Ignore public class JDK8151486CleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { System.setSecurityManager(new SecurityManager() { @Override public void checkPermission(final Permission perm) { } }); Class.forName("java.lang.String", false, ClassLoader.getSystemClassLoader()); // leaving the security manager will cause a leak, but not the one we're testing for System.setSecurityManager(null); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/JacksonCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import com.fasterxml.jackson.databind.type.TypeFactory; import se.jiderhamn.classloader.PackagesLoadedOutsideClassLoader; /** * Test cases for {@link JacksonCleanUp} * @author Mattias Jiderhamn */ @PackagesLoadedOutsideClassLoader(packages = {"com.fasterxml.jackson.databind"}, addToDefaults = true) public class JacksonCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { TypeFactory.defaultInstance().constructSimpleType(JacksonCleanUpTest.class, null); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/JavaServerFaces2746CleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import javax.el.BeanELResolver; import javax.el.ELContext; import javax.faces.component.UIComponentBase; import com.sun.faces.el.ELContextImpl; /** * Test case for {@link JavaServerFaces2746CleanUp} * @author Mattias Jiderhamn */ public class JavaServerFaces2746CleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { /** * Trigger leak by explicit call to {@link BeanELResolver#getFeatureDescriptors(javax.el.ELContext, Object)}. * With Caucho Resin EL implementation, a call to {@link BeanELResolver#getValue(javax.el.ELContext, Object, Object)}, * {@link BeanELResolver#setValue(javax.el.ELContext, Object, Object, Object)}, * {@link BeanELResolver#getType(javax.el.ELContext, Object, Object)} or * {@link BeanELResolver#isReadOnly(javax.el.ELContext, Object, Object)} would render the same result. */ private static void doTriggerLeak() { final MyComponent myComponent = new MyComponent(); final BeanELResolver beanELResolver = new BeanELResolver(); ELContext elContext = new ELContextImpl(new BeanELResolver()); // Irrelevant, could have been mock beanELResolver.getFeatureDescriptors(elContext, myComponent); } /** Dummy custom component */ @SuppressWarnings("unused") private static class MyComponent extends UIComponentBase { @Override public String getFamily() { throw new UnsupportedOperationException(); } /** Getter and setter must use custom attribute */ public MyAttribute getAttribute() { return null; } /** Getter and setter must use custom attribute */ public void setAttribute(MyAttribute myAttribute) { } } /** Dummy custom component attribute type */ private static class MyAttribute { } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Since {@link #doTriggerLeak()} also triggers another leak, by {@link java.beans.Introspector#getBeanInfo(java.lang.Class)} * invoking {@link java.beans.ThreadGroupContext}, we need to fix that leak as part of the triggering. */ @SuppressWarnings("UnusedAssignment") @Override protected void triggerLeak() throws Exception { doTriggerLeak(); new ThreadGroupContextCleanUp().cleanUp(getClassLoaderLeakPreventor()); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/JavaUtilLoggingLevelCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; /** * Test cases for {@link JavaUtilLoggingLevelCleanUp} * @author Mattias Jiderhamn */ public class JavaUtilLoggingLevelCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { // TODO Log // Logger logger = Logger.getLogger(JavaUtilLoggingLevelCleanUpTest.class.getName()); // logger.setLevel( new CustomLevel(); } /** PropertyEditor for testing */ public static class CustomLevel extends java.util.logging.Level { public CustomLevel() { super("Foo", 10); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/JavaxSecurityAuthLoginConfigurationCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; /** * Test case for {@link JavaxSecurityAuthLoginConfigurationCleanUp} * @author Nikos Epping */ public class JavaxSecurityAuthLoginConfigurationCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { Configuration.setConfiguration(new MockConfiguration()); } private class MockConfiguration extends Configuration { @Override public AppConfigurationEntry[] getAppConfigurationEntry(String name) { return null; } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/JceSecurityCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.security.NoSuchAlgorithmException; import java.security.Provider; /** * Test that the leak caused by {@link javax.crypto.JceSecurity} is cleared. * Thanks to Paul Kiman for the report. * @author Mattias Jiderhamn */ public class JceSecurityCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { try { // Alternative classes: KeyAgreement, KeyGenerator, ExemptionMechanism MyProvider myProvider = new MyProvider("foo", 1.0, "bar"); javax.crypto.Mac.getInstance("baz", myProvider); } catch (SecurityException e) { // CS:IGNORE // Leak is triggered despite an exception being thrown } catch (NoSuchAlgorithmException e) { // CS:IGNORE // Leak is triggered despite an exception being thrown } } /** Custom {@link Provider}, to be put in {@link javax.crypto.JceSecurity} caches */ public static class MyProvider extends Provider { public MyProvider(String name, double version, String info) { super(name, version, info); } @Override public synchronized Service getService(String type, String algorithm) { return new Service(this, "type", "algorithm", "className", null, null); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/MBeanCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.management.ManagementFactory; import javax.management.MBeanServer; import javax.management.ObjectName; /** * Test case for {@link MBeanCleanUp} * @author Mattias Jiderhamn */ public class MBeanCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); mBeanServer.registerMBean(new Custom(), new ObjectName("se.jiderhamn:foo=bar" + System.currentTimeMillis() /* Unique name per test */)); } public interface CustomMBean { } public static class Custom implements CustomMBean { } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/MXBeanNotificationListenersCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.management.ManagementFactory; import javax.management.Notification; import javax.management.NotificationEmitter; import javax.management.NotificationListener; /** * Test case for {@link MXBeanNotificationListenersCleanUp} * @author Mattias Jiderhamn */ public class MXBeanNotificationListenersCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { ((NotificationEmitter) ManagementFactory.getMemoryMXBean()).addNotificationListener( new CustomNotificationListener(), null, null); } static class CustomNotificationListener implements NotificationListener { @Override public void handleNotification(Notification notification, Object handback) { } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/MXBeanNotificationListenersCleanUp_ListenerWrapperTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.management.ManagementFactory; import java.lang.reflect.Constructor; import javax.management.NotificationEmitter; import javax.management.NotificationListener; import com.sun.jmx.interceptor.DefaultMBeanServerInterceptor; /** * Test case for {@link MXBeanNotificationListenersCleanUp} when {@link DefaultMBeanServerInterceptor.ListenerWrapper} * is used. * @author Mattias Jiderhamn */ public class MXBeanNotificationListenersCleanUp_ListenerWrapperTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { final Class listenerWrapperClass = getClassLoaderLeakPreventor().findClass("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor$ListenerWrapper"); final Constructor constructor = listenerWrapperClass.getDeclaredConstructors()[0]; constructor.setAccessible(true); final NotificationListener wrappedListener = (NotificationListener) constructor.newInstance( new MXBeanNotificationListenersCleanUpTest.CustomNotificationListener(), null, null); ((NotificationEmitter) ManagementFactory.getMemoryMXBean()).addNotificationListener( wrappedListener, null, null); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/MoxyCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import org.eclipse.persistence.jaxb.compiler.CompilerHelper; import se.jiderhamn.classloader.PackagesLoadedOutsideClassLoader; /** * To trigger leak {@link org.eclipse.persistence.jaxb.compiler.CompilerHelper} must be loaded by leaking classloader * {@link org.eclipse.persistence.jaxb.javamodel.Helper} and/or {@link org.eclipse.persistence.jaxb.compiler.Property} must not * @author Mattias Jiderhamn */ @PackagesLoadedOutsideClassLoader(packages = {"org.eclipse.persistence.jaxb.javamodel"}, addToDefaults = true) public class MoxyCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { final ClassLoader leakSafeCL = Thread.currentThread().getContextClassLoader().getParent(); // Class.forName("org.eclipse.persistence.jaxb.javamodel.Helper", true, leakSafeCL); // Class.forName("org.eclipse.persistence.jaxb.compiler.Property", true, leakSafeCL); Class.forName("org.eclipse.persistence.jaxb.compiler.CompilerHelper", true, leakSafeCL); try { CompilerHelper.getXmlBindingsModelContext(); } catch(IllegalAccessError e) { // CompilerHelper.getXmlBindingsModelContext() tries to load some internal classes that // cannot be loaded anymore with java versions > 8 (Jigsaw) // Leak is triggered despite exception being thrown } // This prevention needs to be run in addition new ResourceBundleCleanUp().cleanUp(getClassLoaderLeakPreventor()); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/MultiThreadedHttpConnectionManagerCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.client.apache.ApacheHttpClient; /** * Test case for leaks caused by {@link ApacheHttpClient} failing to * close {@link org.apache.commons.httpclient.MultiThreadedHttpConnectionManager} * * @author Marian Petrik */ public class MultiThreadedHttpConnectionManagerCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() { Client client = ApacheHttpClient.create(new DefaultClientConfig()); try { // it doesn't matter where we make our call, we only want to initiate connections to create the leak WebResource webResource = client.resource("http://localhost:1234"); webResource.accept("application/json").get(ClientResponse.class); } catch (Throwable ex) { //exception thrown for a non existing url, we do not need to call a real url, only to start the relevant leaking classes } client.destroy(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ObjectStreamClassCleanupTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.io.ObjectStreamClass; import java.io.Serializable; /** * Test cases for {@link ObjectStreamClassCleanup} */ public class ObjectStreamClassCleanupTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { ObjectStreamClass.lookup(SerializableEntity.class); } protected final class SerializableEntity implements Serializable { private static final long serialVersionUID = 1L; public int value; } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/PropertyEditorCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.beans.PropertyEditorManager; import org.junit.Ignore; /** * Test cases for {@link PropertyEditorCleanUp} * @author Mattias Jiderhamn */ @Ignore // No longer leaks in Java 7+ public class PropertyEditorCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { PropertyEditorManager.registerEditor(String[].class, Foo.class); // Before Java 7, this caused a leak } /** PropertyEditor for testing */ public static class Foo { } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ProxySelectorCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.io.IOException; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URI; import java.util.List; /** * Test case for {@link ProxySelectorCleanUp} * @author Mattias Jiderhamn */ public class ProxySelectorCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { ProxySelector.setDefault(new MyProxySelector()); } /** Custom {@link ProxySelector} to trigger leak */ private static class MyProxySelector extends ProxySelector { @Override public List select(URI uri) { throw new UnsupportedOperationException(); } @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { throw new UnsupportedOperationException(); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ReplaceDOMNormalizerSerializerAbortExceptionCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp; import se.jiderhamn.classloader.leak.prevention.ReplaceDOMNormalizerSerializerAbortException; import se.jiderhamn.classloader.leak.prevention.preinit.ReplaceDOMNormalizerSerializerAbortExceptionInitiatorTest; /** * Test cases for {@link ReplaceDOMNormalizerSerializerAbortException} when used as {@link ClassLoaderPreMortemCleanUp} * @author Mattias Jiderhamn */ public class ReplaceDOMNormalizerSerializerAbortExceptionCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override public void triggerLeak() throws Exception { ReplaceDOMNormalizerSerializerAbortExceptionInitiatorTest.triggerLeak(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/SAAJEnvelopeFactoryParserPoolCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.reflect.Method; import javax.xml.parsers.SAXParser; import se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor; import static org.junit.Assert.assertNotNull; /** * Test case for {@link SAAJEnvelopeFactoryParserPoolCleanUp} * @author Mattias Jiderhamn */ public class SAAJEnvelopeFactoryParserPoolCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { final ClassLoaderLeakPreventor preventor = getClassLoaderLeakPreventor(); /* try { EnvelopeFactory.createEnvelope(new StreamSource(), new SOAPPart1_1Impl()); } catch (SOAPException e) { // CS:IGNORE } */ Class envelopeFactoryClass = preventor.findClass("com.sun.xml.internal.messaging.saaj.soap.EnvelopeFactory"); if (envelopeFactoryClass == null) { // Try the package for the maven dependency if the internal one is not available envelopeFactoryClass = preventor.findClass("com.sun.xml.messaging.saaj.soap.EnvelopeFactory"); } final Object /* com.sun.xml.internal.messaging.saaj.soap.ContextClassloaderLocal */ parserPool = preventor.getStaticFieldValue(envelopeFactoryClass, "parserPool"); final Object currentParserPool = preventor.findMethod(parserPool.getClass().getSuperclass(), "get").invoke(parserPool); assertNotNull(currentParserPool); final Method getMethod = preventor.findMethod(currentParserPool.getClass(), "get"); final SAXParser saxParser = (SAXParser) getMethod.invoke(currentParserPool); saxParser.setProperty("http://apache.org/xml/properties/internal/error-handler", getCustomErrorHandlerInstance(preventor)); final Method putMethod = preventor.findMethod(currentParserPool.getClass(), "put", SAXParser.class); putMethod.invoke(currentParserPool, saxParser); } /* * Create a dummy XMLErrorHandler to be loaded by the classloader that sould be garbage collected * Create using reflection, since the type is not compile time accessible in newer Java Versions */ public Object getCustomErrorHandlerInstance(final ClassLoaderLeakPreventor preventor) { final Class errorHandlerClass = preventor.findClass("com.sun.org.apache.xerces.internal.xni.parser.XMLErrorHandler"); Object instance = java.lang.reflect.Proxy.newProxyInstance( this.getClass().getClassLoader(), new java.lang.Class[] { errorHandlerClass }, new java.lang.reflect.InvocationHandler() { @Override public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) throws java.lang.Throwable { // Can be empty since we don't really want to use the handler return null; } }); return errorHandlerClass.cast(instance); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/SecurityProviderCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.security.Provider; /** * Test case for {@link SecurityProviderCleanUp} * @author Mattias Jiderhamn */ public class SecurityProviderCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { private static final Provider customProvider = new Provider("Foo", 1.0, "Bar") { // Nothing }; @Override protected void triggerLeak() throws Exception { java.security.Security.addProvider(customProvider); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ShutdownHookCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; /** * Test case for {@link ShutdownHookCleanUp} * @author Mattias Jiderhamn */ public class ShutdownHookCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { Runtime.getRuntime().addShutdownHook(new ShutdownHookThread()); } /** Dummy shutdown hook */ private class ShutdownHookThread extends Thread { } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/StopThreadsCleanUp_MultiThreadedHttpConnectionManagerTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.client.apache.ApacheHttpClient; /** * Test case for leaks caused by {@link com.sun.jersey.client.apache.ApacheHttpClient} failing to * close {@link org.apache.commons.httpclient.MultiThreadedHttpConnectionManager}. * Demonstrates {@link StopThreadsCleanUp} is sufficient, although {@link MultiThreadedHttpConnectionManagerCleanUp} may * be used in case you want to avoid {@link StopThreadsCleanUp}. * * @author Mattias Jiderhamn */ public class StopThreadsCleanUp_MultiThreadedHttpConnectionManagerTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() { Client client = ApacheHttpClient.create(new DefaultClientConfig()); try { // it doesn't matter where we make our call, we only want to initiate connections to create the leak WebResource webResource = client.resource("http://localhost:1234"); webResource.accept("application/json").get(ClientResponse.class); } catch (Throwable ex) { //exception thrown for a non existing url, we do not need to call a real url, only to start the relevant leaking classes } client.destroy(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/StopThreadsCleanUp_PostgresqlJdbcTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import org.junit.After; import se.jiderhamn.classloader.PackagesLoadedOutsideClassLoader; /** * Test case for leaks caused by Postgresql JDBC driver timer threads are avoided by {@link StopThreadsCleanUp}. * @author Mattias Jiderhamn */ @PackagesLoadedOutsideClassLoader(packages = {"org.postgresql", "com.mysql"}, addToDefaults = true) public class StopThreadsCleanUp_PostgresqlJdbcTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() { org.postgresql.Driver.getSharedTimer().getTimer(); } @After public void tearDown() { org.postgresql.Driver.getSharedTimer().releaseTimer(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/StopThreadsCleanUp_Runnable.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; /** * Test case for leaks caused by {@link Runnable} of thread being loaded by classloader. * * @author Mattias Jiderhamn */ public class StopThreadsCleanUp_Runnable extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() { Thread t = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000L); } catch (InterruptedException e) { System.out.println("Thread with custom Runnable was interrupted from sleeping. Going back to sleep."); } } }); t.setContextClassLoader(ClassLoader.getSystemClassLoader()); t.start(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/StopThreadsClenup_ExecutorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Test case for leaks caused by shared ThreadPool creating new Threads * * Treads are started by ExecutorService would have Protection Domain of inheritedAccessControlContext loaded by classLoader * * @author Vlad Skarzhevsky */ public class StopThreadsClenup_ExecutorTest extends ClassLoaderPreMortemCleanUpTestBase { private static final ExecutorService executor = createSharedExecutor(); private static ExecutorService createSharedExecutor() { ExecutorService executor = Executors.newFixedThreadPool(3); // initialize executor with one thread if (true) { executor.submit(new Runnable() { @Override public void run() { } }); } return executor; } @Override protected void triggerLeak() throws Exception { // Start multiple threads to be sure that ThreadPool created new threads for (int i = 0; i < 1; i++) { executor.submit(new Runnable() { @Override public void run() { } }); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ThreadGroupCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; /** * Test cases for {@link ThreadGroupCleanUp} * @author Mattias Jiderhamn */ public class ThreadGroupCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { /** * Having a custom ThreadGroup that is not destroyed will cause a leak */ @Override protected void triggerLeak() throws Exception { new ThreadGroup("customThreadGroup") { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("Pretend to do something"); super.uncaughtException(t, e); } }; } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ThreadLocalCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; /** * Test cases for {@link ThreadLocalCleanUp} * * ThreadLocals work the same way as WeakHashMaps; from http://docs.oracle.com/javase/6/docs/api/java/util/WeakHashMap.html * "Implementation note: The value objects in a WeakHashMap are held by ordinary strong references. Thus care should be * taken to ensure that value objects do not strongly refer to their own keys, either directly or indirectly, * since that will prevent the keys from being discarded." * * This means that the reference chain is like this: Thread -> custom value -> custom class -> * custom classloader -> class containing ThreadLocal -> static ThreadLocal * * @author Mattias Jiderhamn */ public class ThreadLocalCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { private static final ThreadLocal threadLocalWithCustomValue = new ThreadLocal(); /** * This may - and will - also leak, since the values aren't removed even when the weak referenced key is * garbage collected. See java.lang.ThreadLocal.ThreadLocalMap JavaDoc: "However, since reference queues are not * used, stale entries are guaranteed to be removed only when the table starts running out of space." */ @Override protected void triggerLeak() throws Exception { threadLocalWithCustomValue.set(new Value()); } /** Custom value class to create leak */ private static class Value { } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ThreadLocalWithNestedRefValueCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.ref.SoftReference; /** * Another variant test case for {@link ThreadLocalCleanUp} using chained {@link SoftReference}s to the value, * checking that the value is recursively dereferenced. * Also check {@link ThreadLocalWithRefValueCleanUpTest} */ public class ThreadLocalWithNestedRefValueCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { private static final ThreadLocal>> threadLocalWithCustomValue = new ThreadLocal>>(); @Override protected void triggerLeak() throws Exception { threadLocalWithCustomValue.set(new SoftReference>(new SoftReference(new Value()))); } /** Custom value class to create leak */ private static class Value { } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/ThreadLocalWithRefValueCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.lang.ref.Reference; import java.lang.ref.SoftReference; /** * Variant test case for {@link ThreadLocalCleanUp} using a {@link Reference} instead of a direct, strong reference to the value. * * All {@link Reference} implementations are using the bootstrap classloader, so it's required to dereference the value * to check which classloader was used for the value held by the Reference instance. * * Also check {@link ThreadLocalWithNestedRefValueCleanUpTest} */ public class ThreadLocalWithRefValueCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { private static final ThreadLocal> threadLocalWithCustomValue = new ThreadLocal>(); @Override protected void triggerLeak() throws Exception { threadLocalWithCustomValue.set(new SoftReference(new Value())); } /** Custom value class to create leak */ private static class Value { } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/X509TrustManagerImplUnparseableExtensionCleanUpTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; import java.io.File; import javax.net.ssl.SSLContext; /** * Test case for {@link X509TrustManagerImplUnparseableExtensionCleanUp} * @author Mattias Jiderhamn */ public class X509TrustManagerImplUnparseableExtensionCleanUpTest extends ClassLoaderPreMortemCleanUpTestBase { @Override protected void triggerLeak() throws Exception { final File keystore = new File(this.getClass().getClassLoader().getResource("./spi-cacert-2008.keystore").toURI()); System.setProperty("javax.net.ssl.trustStore", keystore.getAbsolutePath()); SSLContext.getDefault(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/cleanup/package-info.java ================================================ package se.jiderhamn.classloader.leak.prevention.cleanup; /** * Test cases in this package are used to confirm a) that a leak exists and b) that the * {@link se.jiderhamn.classloader.leak.prevention.ClassLoaderPreMortemCleanUp} is able to circumvent the leak. */ ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/package-info.java ================================================ package se.jiderhamn.classloader.leak.prevention; /** * Test cases in this package are used to confirm a) that a leak exists and b) that the * {@link se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventorListener} is able to circumvent the leak. */ ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/AwtToolkitInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; /** * Test case for {@link AwtToolkitInitiator} * @author Mattias Jiderhamn */ @Ignore // Fixed in newer versions of Java? public class AwtToolkitInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/DatatypeConverterImplInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import java.math.BigDecimal; import java.math.BigInteger; import java.util.GregorianCalendar; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.Duration; import javax.xml.datatype.XMLGregorianCalendar; import org.junit.Before; import org.junit.Ignore; /** * Test cases for {@link DatatypeConverterImplInitiator} * @author Mattias Jiderhamn */ @Ignore // Doesn't leak in Java 1.7.0 (_55, _56), but does in 1.8.0 (_74) public class DatatypeConverterImplInitiatorTest extends PreClassLoaderInitiatorTestBase { @Before public void setSystemProperty() { System.setProperty("javax.xml.datatype.DatatypeFactory", MyDatatypeFactory.class.getName()); } /** Custom {@link DatatypeFactory} loaded by "our" classloader */ public static class MyDatatypeFactory extends DatatypeFactory { @Override public Duration newDuration(String lexicalRepresentation) { throw new UnsupportedOperationException(); } @Override public Duration newDuration(long durationInMilliSeconds) { throw new UnsupportedOperationException(); } @Override public Duration newDuration(boolean isPositive, BigInteger years, BigInteger months, BigInteger days, BigInteger hours, BigInteger minutes, BigDecimal seconds) { throw new UnsupportedOperationException(); } @Override public XMLGregorianCalendar newXMLGregorianCalendar() { throw new UnsupportedOperationException(); } @Override public XMLGregorianCalendar newXMLGregorianCalendar(String lexicalRepresentation) { throw new UnsupportedOperationException(); } @Override public XMLGregorianCalendar newXMLGregorianCalendar(GregorianCalendar cal) { throw new UnsupportedOperationException(); } @Override public XMLGregorianCalendar newXMLGregorianCalendar(BigInteger year, int month, int day, int hour, int minute, int second, BigDecimal fractionalSecond, int timezone) { throw new UnsupportedOperationException(); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/DocumentBuilderFactoryInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; /** * Test cases for {@link DocumentBuilderFactoryInitiator} * @author Mattias Jiderhamn */ @Ignore // Fixed in newer versions of Java? public class DocumentBuilderFactoryInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/Java2dDisposerInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; /** * Test cases for {@link Java2dDisposerInitiator} * @author Mattias Jiderhamn */ @Ignore // Fixed in newer versions of Java public class Java2dDisposerInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/Java2dRenderQueueInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; /** * Test cases for {@link Java2dRenderQueueInitiator} */ public class Java2dRenderQueueInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/JavaxSecurityLoginConfigurationInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; /** * Test cases for {@link JavaxSecurityLoginConfigurationInitiator} * @author Mattias Jiderhamn */ @Ignore // Fixed in newer versions of Java public class JavaxSecurityLoginConfigurationInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/JdbcDriversInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; /** * Test cases for {@link JdbcDriversInitiator} * @author Mattias Jiderhamn */ @Ignore // Cannot be tested this way public class JdbcDriversInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/LdapPoolManagerInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Before; /** * Test cases for {@link LdapPoolManagerInitiator} * @author Mattias Jiderhamn */ public class LdapPoolManagerInitiatorTest extends PreClassLoaderInitiatorTestBase { @Before public void setSystemProperty() { System.setProperty("com.sun.jndi.ldap.connect.pool.timeout", "1"); // Required to trigger leak } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/OracleJdbcThreadInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; import se.jiderhamn.classloader.PackagesLoadedOutsideClassLoader; /** * Test cases for {@link OracleJdbcThreadInitiator} * @author Mattias Jiderhamn */ @Ignore // Oracle JDBC driver needs to be available for this test @PackagesLoadedOutsideClassLoader(packages = "oracle.", addToDefaults = true) public class OracleJdbcThreadInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/PreClassLoaderInitiatorTestBase.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; import se.jiderhamn.classloader.leak.Leaks; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; import se.jiderhamn.classloader.leak.prevention.PreventionsTestBase; /** * Base class for testing {@link PreClassLoaderInitiator} implementations * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) // Execute tests in name order public class PreClassLoaderInitiatorTestBase extends PreventionsTestBase { /** First time {@link PreClassLoaderInitiator} is invoked we expect a leak */ @SuppressWarnings("DefaultAnnotationParam") @Leaks(true) @Test public void firstShouldLeak() throws Exception { invokeInitiator(); } /** Second time {@link PreClassLoaderInitiator} is invoked there should be no leak, since only first call is affected */ @Leaks(false) @Test public void secondShouldNotLeak() throws Exception { invokeInitiator(); } private void invokeInitiator() throws IllegalAccessException, InstantiationException, InterruptedException { getTestedImplementation().doOutsideClassLoader(getClassLoaderLeakPreventor()); } /** Use the {@link ClassLoader} of the test as the leak safe classloader, to test leaks. */ @Override protected ClassLoader getLeakSafeClassLoader() { return getClass().getClassLoader(); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/ReplaceDOMNormalizerSerializerAbortExceptionInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.junit.Test; import org.junit.runner.RunWith; import org.w3c.dom.Document; import org.w3c.dom.ls.DOMImplementationLS; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; import se.jiderhamn.classloader.leak.Leaks; import se.jiderhamn.classloader.leak.prevention.PreClassLoaderInitiator; import se.jiderhamn.classloader.leak.prevention.PreventionsTestBase; import se.jiderhamn.classloader.leak.prevention.ReplaceDOMNormalizerSerializerAbortException; /** * Test cases for {@link ReplaceDOMNormalizerSerializerAbortException} when used as {@link PreClassLoaderInitiator} * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) public class ReplaceDOMNormalizerSerializerAbortExceptionInitiatorTest extends PreventionsTestBase { @Leaks(false) @Test public void noLeakAfterInitiatorRun() throws Exception { getTestedImplementation().doOutsideClassLoader(getClassLoaderLeakPreventor()); triggerLeak(); } /** Invoke code that may trigger leak */ public static void triggerLeak() throws ParserConfigurationException { // Alternative 1 DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument().normalizeDocument(); // Alternative 2 Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); DOMImplementationLS implementation = (DOMImplementationLS)document.getImplementation(); implementation.createLSSerializer().writeToString(document); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/SecurityPolicyInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; /** * Test cases for {@link SecurityPolicyInitiator} * @author Mattias Jiderhamn */ @Ignore // Fixed in newer versions of Java? public class SecurityPolicyInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/SecurityProvidersInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; import org.junit.Ignore; /** * Test cases for {@link SecurityProvidersInitiator} * @author Mattias Jiderhamn */ @Ignore // Cannot be tested this way? public class SecurityProvidersInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/SunAwtAppContextInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; /** * Test cases for {@link SunAwtAppContextInitiator} * @author Mattias Jiderhamn */ public class SunAwtAppContextInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/java/se/jiderhamn/classloader/leak/prevention/preinit/SunGCInitiatorTest.java ================================================ package se.jiderhamn.classloader.leak.prevention.preinit; /** * Test cases for {@link SunGCInitiator} * @author Mattias Jiderhamn */ public class SunGCInitiatorTest extends PreClassLoaderInitiatorTestBase { } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/resources/META-INF/services/javax.imageio.spi.ImageInputStreamSpi ================================================ se.jiderhamn.classloader.leak.prevention.cleanup.ImageIOMockImageInputStreamSPI ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-core/src/test/resources/spi-cacert-2008.crt ================================================ -----BEGIN CERTIFICATE----- MIIIDjCCBfagAwIBAgIJAOiOtsn4KhQoMA0GCSqGSIb3DQEBBQUAMIG8MQswCQYD VQQGEwJVUzEQMA4GA1UECBMHSW5kaWFuYTEVMBMGA1UEBxMMSW5kaWFuYXBvbGlz MSgwJgYDVQQKEx9Tb2Z0d2FyZSBpbiB0aGUgUHVibGljIEludGVyZXN0MRMwEQYD VQQLEwpob3N0bWFzdGVyMR4wHAYDVQQDExVDZXJ0aWZpY2F0ZSBBdXRob3JpdHkx JTAjBgkqhkiG9w0BCQEWFmhvc3RtYXN0ZXJAc3BpLWluYy5vcmcwHhcNMDgwNTEz MDgwNzU2WhcNMTgwNTExMDgwNzU2WjCBvDELMAkGA1UEBhMCVVMxEDAOBgNVBAgT B0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMfU29mdHdh cmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1hc3RlcjEe MBwGA1UEAxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSUwIwYJKoZIhvcNAQkBFhZo b3N0bWFzdGVyQHNwaS1pbmMub3JnMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC CgKCAgEA3DbmR0LCxFF1KYdAw9iOIQbSGE7r7yC9kDyFEBOMKVuUY/b0LfEGQpG5 GcRCaQi/izZF6igFM0lIoCdDkzWKQdh4s/Dvs24t3dHLfer0dSbTPpA67tfnLAS1 fOH1fMVO73e9XKKTM5LOfYFIz2u1IiwIg/3T1c87Lf21SZBb9q1NE8re06adU1Fx Y0b4ShZcmO4tbZoWoXaQ4mBDmdaJ1mwuepiyCwMs43pPx93jzONKao15Uvr0wa8u jyoIyxspgpJyQ7zOiKmqp4pRQ1WFmjcDeJPI8L20QcgHQprLNZd6ioFl3h1UCAHx ZFy3FxpRvB7DWYd2GBaY7r/2Z4GLBjXFS21ZGcfSxki+bhQog0oQnBv1b7ypjvVp /rLBVcznFMn5WxRTUQfqzj3kTygfPGEJ1zPSbqdu1McTCW9rXRTunYkbpWry9vjQ co7qch8vNGopCsUK7BxAhRL3pqXTT63AhYxMfHMgzFMY8bJYTAH1v+pk1Vw5xc5s zFNaVrpBDyXfa1C2x4qgvQLCxTtVpbJkIoRRKFauMe5e+wsWTUYFkYBE7axt8Feo +uthSKDLG7Mfjs3FIXcDhB78rKNDCGOM7fkn77SwXWfWT+3Qiz5dW8mRvZYChD3F TbxCP3T9PF2sXEg2XocxLxhsxGjuoYvJWdAY4wCAs1QnLpnwFVMCAwEAAaOCAg8w ggILMB0GA1UdDgQWBBQ0cdE41xU2g0dr1zdkQjuOjVKdqzCB8QYDVR0jBIHpMIHm gBQ0cdE41xU2g0dr1zdkQjuOjVKdq6GBwqSBvzCBvDELMAkGA1UEBhMCVVMxEDAO BgNVBAgTB0luZGlhbmExFTATBgNVBAcTDEluZGlhbmFwb2xpczEoMCYGA1UEChMf U29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDETMBEGA1UECxMKaG9zdG1h c3RlcjEeMBwGA1UEAxMVQ2VydGlmaWNhdGUgQXV0aG9yaXR5MSUwIwYJKoZIhvcN AQkBFhZob3N0bWFzdGVyQHNwaS1pbmMub3JnggkA6I62yfgqFCgwDwYDVR0TAQH/ BAUwAwEB/zARBglghkgBhvhCAQEEBAMCAAcwCQYDVR0SBAIwADAuBglghkgBhvhC AQ0EIRYfU29mdHdhcmUgaW4gdGhlIFB1YmxpYyBJbnRlcmVzdDAwBglghkgBhvhC AQQEIxYhaHR0cHM6Ly9jYS5zcGktaW5jLm9yZy9jYS1jcmwucGVtMDIGCWCGSAGG +EIBAwQlFiNodHRwczovL2NhLnNwaS1pbmMub3JnL2NlcnQtY3JsLnBlbTAhBgNV HREEGjAYgRZob3N0bWFzdGVyQHNwaS1pbmMub3JnMA4GA1UdDwEB/wQEAwIBBjAN BgkqhkiG9w0BAQUFAAOCAgEAtM294LnqsgMrfjLp3nI/yUuCXp3ir1UJogxU6M8Y PCggHam7AwIvUjki+RfPrWeQswN/2BXja367m1YBrzXU2rnHZxeb1NUON7MgQS4M AcRb+WU+wmHo0vBqlXDDxm/VNaSsWXLhid+hoJ0kvSl56WEq2dMeyUakCHhBknIP qxR17QnwovBc78MKYiC3wihmrkwvLo9FYyaW8O4x5otVm6o6+YI5HYg84gd1GuEP sTC8cTLSOv76oYnzQyzWcsR5pxVIBcDYLXIC48s9Fmq6ybgREOJJhcyWR2AFJS7v dVkz9UcZFu/abF8HyKZQth3LZjQl/GaD68W2MEH4RkRiqMEMVObqTFoo5q7Gt/5/ O5aoLu7HaD7dAD0prypjq1/uSSotxdz70cbT0ZdWUoa2lOvUYFG3/B6bzAKb1B+P +UqPti4oOxfMxaYF49LTtcYDyeFIQpvLP+QX4P4NAZUJurgNceQJcHdC2E3hQqlg g9cXiUPS1N2nGLar1CQlh7XU4vwuImm9rWgs/3K1mKoGnOcqarihk3bOsPN/nOHg T7jYhkalMwIsJWE3KpLIrIF0aGOHM3a9BX9e1dUCbb2v/ypaqknsmHlHU5H2DjRa yaXG67Ljxay2oHA1u8hRadDytaIybrw/oDc5fHE2pgXfDBLkFqfF1stjo5VwP+YE o2A= -----END CERTIFICATE----- ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-servlet/pom.xml ================================================ 4.0.0 se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-parent 2.7.1-SNAPSHOT classloader-leak-prevention-servlet jar ClassLoader Leak Prevention library for servlet environments ServletContextListener that prevents ClassLoader leaks / java.lang.OutOfMemoryError: PermGen space https://github.com/mjiderhamn/classloader-leak-prevention se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-core ${project.parent.version} javax.servlet servlet-api 2.5 provided ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-servlet/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventorListener.java ================================================ /* Copyright 2012 Mattias Jiderhamn Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package se.jiderhamn.classloader.leak.prevention; import java.util.LinkedList; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp; import se.jiderhamn.classloader.leak.prevention.cleanup.StopThreadsCleanUp; import se.jiderhamn.classloader.leak.prevention.preinit.OracleJdbcThreadInitiator; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; import static se.jiderhamn.classloader.leak.prevention.cleanup.ShutdownHookCleanUp.SHUTDOWN_HOOK_WAIT_MS_DEFAULT; /** * This class helps prevent classloader leaks. *

Basic setup

*

Activate protection by adding this class as a context listener * in your web.xml, like this:

*
 *  <listener>
 *     <listener-class>se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor</listener-class>
 *  </listener> 
 * 
* * You should usually declare this listener before any other listeners, to make it "outermost". * *

Configuration

* The context listener has a number of settings that can be configured with context parameters in web.xml, * i.e.: * *
 *   <context-param>
 *     <param-name>ClassLoaderLeakPreventor.stopThreads</param-name>
 *     <param-value>false</param-value>
 *   </context-param>
 * 
* * The available settings are * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Parameter nameDefault valueDescription
ClassLoaderLeakPreventor.stopThreadstrueShould threads tied to the web app classloader be forced to stop at application shutdown?
ClassLoaderLeakPreventor.stopTimerThreadstrueShould Timer threads tied to the web app classloader be forced to stop at application shutdown?
ClassLoaderLeakPreventor.executeShutdownHookstrueShould shutdown hooks registered from the application be executed at application shutdown?
ClassLoaderLeakPreventor.threadWaitMs5000 (5 seconds)No of milliseconds to wait for threads to finish execution, before stopping them.
ClassLoaderLeakPreventor.shutdownHookWaitMs10000 (10 seconds) * No of milliseconds to wait for shutdown hooks to finish execution, before stopping them. * If set to -1 there will be no waiting at all, but Thread is allowed to run until finished. *
* * *

License

*

This code is licensed under the Apache 2 license, * which allows you to include modified versions of the code in your distributed software, without having to release * your source code.

* *

More info

*

For more info, see * here. *

* *

Design goals

*

If you want to help improve this component, you should be aware of the design goals

*

* Primary design goal: Zero dependencies. The component should build and run using nothing but the JDK and the * Servlet API. Specifically we should not depend on any logging framework, since they are part of the problem. * We also don't want to use any utility libraries, in order not to impose any further dependencies into any project * that just wants to get rid of classloader leaks. * Access to anything outside of the standard JDK (in order to prevent a known leak) should be managed * with reflection. *

*

* Secondary design goal: Keep the runtime component in a single .java file. It should be possible to * just add this one .java file into your own source tree. *

* * @author Mattias Jiderhamn, 2012-2016 */ @SuppressWarnings("WeakerAccess") public class ClassLoaderLeakPreventorListener implements ServletContextListener { protected ClassLoaderLeakPreventor classLoaderLeakPreventor; /** Other {@link javax.servlet.ServletContextListener}s to use also */ protected final List otherListeners = new LinkedList(); /** * Get the default set of other {@link ServletContextListener} to be automatically invoked. * NOTE! Anything added here should be idempotent, i.e. there should be no problem executing the listener twice. */ static List getDefaultOtherListeners() { try{ Class introspectorCleanupListenerClass = (Class) Class.forName("org.springframework.web.util.IntrospectorCleanupListener"); return singletonList(introspectorCleanupListenerClass.newInstance()); } catch (ClassNotFoundException e) { // Ignore silently - Spring not present on classpath } catch(Exception e){ e.printStackTrace(System.err); } return emptyList(); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Implement javax.servlet.ServletContextListener ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @Override public void contextInitialized(ServletContextEvent servletContextEvent) { otherListeners.addAll(getDefaultOtherListeners()); contextInitialized(servletContextEvent.getServletContext()); for(ServletContextListener listener : otherListeners) { try { listener.contextInitialized(servletContextEvent); } catch(Exception e){ classLoaderLeakPreventor.error(e); } } } void contextInitialized(final ServletContext servletContext) { // Should threads tied to the web app classloader be forced to stop at application shutdown? boolean stopThreads = ! "false".equals(servletContext.getInitParameter("ClassLoaderLeakPreventor.stopThreads")); // Should Timer threads tied to the web app classloader be forced to stop at application shutdown? boolean stopTimerThreads = ! "false".equals(servletContext.getInitParameter("ClassLoaderLeakPreventor.stopTimerThreads")); // Should shutdown hooks registered from the application be executed at application shutdown? boolean executeShutdownHooks = ! "false".equals(servletContext.getInitParameter("ClassLoaderLeakPreventor.executeShutdownHooks")); /* * Should the {@code oracle.jdbc.driver.OracleTimeoutPollingThread} thread be forced to start with system classloader, * in case Oracle JDBC driver is present? This is normally a good idea, but can be disabled in case the Oracle JDBC * driver is not used even though it is on the classpath. */ boolean startOracleTimeoutThread = ! "false".equals(servletContext.getInitParameter("ClassLoaderLeakPreventor.startOracleTimeoutThread")); // No of milliseconds to wait for threads to finish execution, before stopping them. int threadWaitMs = getIntInitParameter(servletContext, "ClassLoaderLeakPreventor.threadWaitMs", ClassLoaderLeakPreventor.THREAD_WAIT_MS_DEFAULT); /* * No of milliseconds to wait for shutdown hooks to finish execution, before stopping them. * If set to -1 there will be no waiting at all, but Thread is allowed to run until finished. */ int shutdownHookWaitMs = getIntInitParameter(servletContext, "ClassLoaderLeakPreventor.shutdownHookWaitMs", SHUTDOWN_HOOK_WAIT_MS_DEFAULT); final ClassLoader webAppClassLoader = Thread.currentThread().getContextClassLoader(); info("Settings for " + this.getClass().getName() + " (CL: 0x" + Integer.toHexString(System.identityHashCode(webAppClassLoader)) + "):"); info(" stopThreads = " + stopThreads); info(" stopTimerThreads = " + stopTimerThreads); info(" executeShutdownHooks = " + executeShutdownHooks); info(" threadWaitMs = " + threadWaitMs + " ms"); info(" shutdownHookWaitMs = " + shutdownHookWaitMs + " ms"); // Create factory with default PreClassLoaderInitiators and ClassLoaderPreMortemCleanUps final ClassLoaderLeakPreventorFactory classLoaderLeakPreventorFactory = createClassLoaderLeakPreventorFactory(); // Configure default PreClassLoaderInitiators if(! startOracleTimeoutThread) classLoaderLeakPreventorFactory.removePreInitiator(OracleJdbcThreadInitiator.class); // Configure default ClassLoaderPreMortemCleanUps final ShutdownHookCleanUp shutdownHookCleanUp = classLoaderLeakPreventorFactory.getCleanUp(ShutdownHookCleanUp.class); shutdownHookCleanUp.setExecuteShutdownHooks(executeShutdownHooks); shutdownHookCleanUp.setShutdownHookWaitMs(shutdownHookWaitMs); final StopThreadsCleanUp stopThreadsCleanUp = classLoaderLeakPreventorFactory.getCleanUp(StopThreadsCleanUp.class); stopThreadsCleanUp.setStopThreads(stopThreads); stopThreadsCleanUp.setStopTimerThreads(stopTimerThreads); stopThreadsCleanUp.setThreadWaitMs(threadWaitMs); classLoaderLeakPreventor = classLoaderLeakPreventorFactory.newLeakPreventor(webAppClassLoader); classLoaderLeakPreventor.runPreClassLoaderInitiators(); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { info(getClass().getName() + " shutting down context by removing known leaks (CL: 0x" + Integer.toHexString(System.identityHashCode(classLoaderLeakPreventor.getClassLoader())) + ")"); for(ServletContextListener listener : otherListeners) { try { listener.contextDestroyed(servletContextEvent); } catch(Exception e) { classLoaderLeakPreventor.error(e); } } classLoaderLeakPreventor.runCleanUps(); } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Utility methods ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** Create {@link ClassLoaderLeakPreventorFactory} to use. Subclasses may opt to override this method. */ protected ClassLoaderLeakPreventorFactory createClassLoaderLeakPreventorFactory() { return new ClassLoaderLeakPreventorFactory(); } /** Parse init parameter for integer value, returning default if not found or invalid */ protected static int getIntInitParameter(ServletContext servletContext, String parameterName, int defaultValue) { final String parameterString = servletContext.getInitParameter(parameterName); if(parameterString != null && parameterString.trim().length() > 0) { try { return Integer.parseInt(parameterString); } catch (NumberFormatException e) { // Do nothing, return default value } } return defaultValue; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Log methods ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /* * Since logging frameworks are part of the problem, we don't want to depend on any of them here. * Feel free however to subclass or fork and use a log framework, in case you think you know what you're doing. */ protected String getLogPrefix() { return ClassLoaderLeakPreventorListener.class.getSimpleName() + ": "; } /** * To "turn off" info logging override this method in a subclass and make that subclass method empty. */ protected void info(String s) { System.out.println(getLogPrefix() + s); } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-servlet3/pom.xml ================================================ 4.0.0 se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-parent 2.7.1-SNAPSHOT classloader-leak-prevention-servlet3 jar ClassLoader Leak Prevention library for servlet environments ServletContextListener that prevents ClassLoader leaks / java.lang.OutOfMemoryError: PermGen space https://github.com/mjiderhamn/classloader-leak-prevention se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-servlet ${project.parent.version} javax.servlet servlet-api javax.servlet javax.servlet-api 3.0.1 provided ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-servlet3/src/main/java/se/jiderhamn/classloader/leak/prevention/ClassLoaderLeakPreventionContainerInitializer.java ================================================ package se.jiderhamn.classloader.leak.prevention; import java.util.Set; import javax.servlet.*; /** * Servlet 3.0 {@link ServletContainerInitializer} that will run the {@link PreClassLoaderInitiator}s (delegated to * {@link ClassLoaderLeakPreventorListener} and register a {@link ServletContextListener} that will run * {@link ClassLoaderPreMortemCleanUp}s on {@link ServletContextListener#contextDestroyed(ServletContextEvent)} (delegated * to the same {@link ClassLoaderLeakPreventorListener}). * @author Mattias Jiderhamn */ public class ClassLoaderLeakPreventionContainerInitializer implements javax.servlet.ServletContainerInitializer { // TODO Order cannot be guaranteed https://java.net/jira/browse/SERVLET_SPEC-79 @Override public void onStartup(Set> set, ServletContext servletContext) throws ServletException { final ClassLoaderLeakPreventorListener classLoaderLeakPreventorListener = new ClassLoaderLeakPreventorListener(); try { classLoaderLeakPreventorListener.contextInitialized(servletContext); // Initialize immediately servletContext.addListener(new ServletContextListener() { @Override public void contextInitialized(ServletContextEvent servletContextEvent) { // Do nothing, already done above } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { classLoaderLeakPreventorListener.contextDestroyed(servletContextEvent); } }); } catch (Throwable t) { // (Shouldn't really be needed) t.printStackTrace(System.err); } for(ServletContextListener otherListener : ClassLoaderLeakPreventorListener.getDefaultOtherListeners()) { servletContext.addListener(otherListener); } } } ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-servlet3/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer ================================================ se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventionContainerInitializer ================================================ FILE: classloader-leak-prevention/classloader-leak-prevention-servlet3/src/main/resources/META-INF/web-fragment.xml ================================================ classloader_leak_prevention ================================================ FILE: classloader-leak-prevention/pom.xml ================================================ 4.0.0 se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-parent 2.7.1-SNAPSHOT pom ClassLoader Leak Prevention library parent Library that prevents ClassLoader leaks / java.lang.OutOfMemoryError: PermGen space https://github.com/mjiderhamn/classloader-leak-prevention scm:git:https://${GITHUB_USERNAME}:${GITHUB_TOKEN}@github.com/mjiderhamn/classloader-leak-prevention.git scm:git:https://${GITHUB_USERNAME}:${GITHUB_TOKEN}@github.com/mjiderhamn/classloader-leak-prevention.git https://github.com/mjiderhamn/classloader-leak-prevention.git HEAD sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-staging https://oss.sonatype.org/service/local/staging/deploy/maven2/ Apache 2 http://www.apache.org/licenses/LICENSE-2.0.txt manual classloader-leak-prevention-core classloader-leak-prevention-servlet classloader-leak-prevention-servlet3 mjiderhamn Mattias Jiderhamn -Xdoclint:none UTF-8 UTF-8 osgeo Open Source Geospatial Foundation Repository https://repo.osgeo.org/repository/release/ junit junit 4.13.2 test org.hamcrest hamcrest-library 1.3 test org.apache.maven.plugins maven-release-plugin 2.5.3 forked-path false -Prelease true true org.apache.maven.plugins maven-compiler-plugin 3.6.1 1.6 1.6 true -XDignore.symbol.file org.apache.maven.plugins maven-surefire-plugin 2.19.1 2 false release org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 2.10.4 attach-javadocs jar org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign ================================================ FILE: classloader-leak-test-framework/README.md ================================================ # Classloader Leak test framework Stand-alone test framework for detecting and/or verifying the existence or non-existence of Java ClassLoader leaks. It is also possible to test leak prevention mechanisms to confirm that the leak really is avoided. The [ClassLoader Leak Prevention library](../README.md) is supposed to be used to avoid the leaks causing problems during runtime. The ClassLoader Leak Prevention library uses this test framework to confirm the effectiveness of it's leak countermeasures. The framework is an built upon [JUnit](http://junit.org/) and supplies a `se.jiderhamn.classloader.leak.JUnitClassloaderRunner`, which is used to run each test in a separate classloader, that is then attempted to be garbage collected. The default assumption is that the test case will cause a classloader leak. A basic example would look like this ```java package se.jiderhamn.tests; import org.junit.Test; import org.junit.runner.RunWith; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; @RunWith(JUnitClassloaderRunner.class) public class LeakConfirmingTest { @Test public void triggerLeak() { // Do something that triggers the suspected leak } } ``` In case the test passes, it means the code inside the tested method does in fact cause classloader leaks. In order to confirm that a piece of code does *not* cause leaks, add the `se.jiderhamn.classloader.leak.Leaks` annotation, with a value of `false`. ```java @Test @Leaks(false) // Should not leak public void doesNotTriggerLeak() { // Do something that should not trigger any leaks } ``` In this case, the test passes only in case a leak isn't triggered. If you want to execute some code outside the per-test classloader, you can do that in an [`@Before`](http://junit.sourceforge.net/javadoc/org/junit/Before.html) annotated method. ## Heap dump In case you want a heap dump automatically generated when a leak is detected, you can use `@Leaks(dumpHeapOnError = true)` and then watch stdout for the name of the heap dump file. In the heap dump, you can look for instances of `se.jiderhamn.classloader.ZombieMarker` and track their path to GC root. ## Verifying prevention measures You can also confirm that a leak workaround has the expected effect, by annotating the class with `se.jiderhamn.classloader.leak.LeakPreventor`, and set its value to a `Runnable` that fixes the leak. ```java @RunWith(JUnitClassloaderRunner.class) @LeakPreventor(LeakThatCanBeFixedTest.Preventor.class) public class LeakThatCanBeFixedTest { @Test public void triggerLeak() { // Do something that triggers the suspected leak } public static class Preventor implements Runnable { public void run() { // Run cleanup code, that fixed the leak and allows the classloader to be GC:ed } } } ``` In this case, a successful test means two things: 1. the `@Test` method does cause a leak 2. the leak is prevented by the code in the `Preventor` That is, the test will fail if either there is no leak to begin with, or the leak is still present after executing the `Preventor`. NOTE: It is not yet determined whether multiple test cases in the same class works, so you should stick to one single `@Test` method per class for now. ## Debugging If you want the test framework to log (to stdout) when a class is being loaded, set the `ClassLoaderLeakTestFramework.debug` system property to `true` (i.e. `-DClassLoaderLeakTestFramework.debug=true`). ## Maven The test framework of the library is available in Maven Central with the following details: ```xml se.jiderhamn classloader-leak-test-framework 1.1.2 ``` ## License This project is licensed under the [Apache 2 license](http://www.apache.org/licenses/LICENSE-2.0), which allows you to include modified versions of the code in your distributed software, without having to release your source code. ================================================ FILE: classloader-leak-test-framework/pom.xml ================================================ 4.0.0 se.jiderhamn classloader-leak-test-framework 1.1.3-SNAPSHOT jar ClassLoader leak test framework Test framework to confirm suspected classloader leak, and create heap dumps to track the leaks https://github.com/mjiderhamn/classloader-leak-prevention scm:git:https://${GITHUB_USERNAME}:${GITHUB_TOKEN}@github.com/mjiderhamn/classloader-leak-prevention.git scm:git:https://${GITHUB_USERNAME}:${GITHUB_TOKEN}@github.com/mjiderhamn/classloader-leak-prevention.git https://github.com/mjiderhamn/classloader-leak-prevention.git HEAD sonatype-nexus-snapshots https://oss.sonatype.org/content/repositories/snapshots sonatype-nexus-staging https://oss.sonatype.org/service/local/staging/deploy/maven2/ Apache 2 http://www.apache.org/licenses/LICENSE-2.0.txt manual mjiderhamn Mattias Jiderhamn 1.6 1.6 -Xdoclint:none junit junit 4.13.2 org.apache.bcel bcel 6.6.0 javax.el el-api 2.2.1-b04 test com.sun.faces jsf-api 2.1.19 test com.sun.faces jsf-impl 2.1.19 test org.apache.maven.plugins maven-release-plugin 2.5.3 forked-path false -Prelease release org.apache.maven.plugins maven-source-plugin 3.0.1 attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin 2.10.4 attach-javadocs jar org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign ================================================ FILE: classloader-leak-test-framework/src/main/java/se/jiderhamn/HeapDumper.java ================================================ package se.jiderhamn; import java.io.File; import java.lang.management.ManagementFactory; import javax.management.MBeanServer; import com.sun.management.HotSpotDiagnosticMXBean; /** * Class that helps programatically dumping the heap. * Heavily inspired by https://blogs.oracle.com/sundararajan/entry/programmatically_dumping_heap_from_java * @author Mattias Jiderhamn */ public class HeapDumper { /** The name of the HotSpot Diagnostic MBean */ private static final String HOTSPOT_BEAN_NAME = "com.sun.management:type=HotSpotDiagnostic"; /** Filename extension for heap dumps */ public static final String HEAP_DUMP_EXTENSION = ".hprof"; /** HotSpot diagnostic MBean */ private static volatile HotSpotDiagnosticMXBean hotSpotDiagnosticMBean; /** * Dump the heap snapshot into a file. * @param file The file in which the dump will be stored * @param live Dump only live objects? */ public static void dumpHeap(File file, boolean live) throws ClassNotFoundException { if(file.exists()) { System.err.println("Cannot dump heap to '" + file + "' - file exists!"); return; } try { getHotSpotDiagnosticMBean().dumpHeap(file.getAbsolutePath(), live); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } /** Get HotSpot diagnostic MBean */ private static HotSpotDiagnosticMXBean getHotSpotDiagnosticMBean() { if (hotSpotDiagnosticMBean == null) { // synchronized (HeapDumper.class) { // if (hotspotMBean == null) { try { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); hotSpotDiagnosticMBean = ManagementFactory.newPlatformMXBeanProxy(server, HOTSPOT_BEAN_NAME, HotSpotDiagnosticMXBean.class); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } // } // } } return hotSpotDiagnosticMBean; } } ================================================ FILE: classloader-leak-test-framework/src/main/java/se/jiderhamn/classloader/PackagesLoadedOutsideClassLoader.java ================================================ package se.jiderhamn.classloader; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * Annotation that defines what packages packages to be ignored by {@link RedefiningClassLoader}, so that they will * be loaded by the parent/system {@link ClassLoader} */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface PackagesLoadedOutsideClassLoader { /** Packages to be ignored by {@link RedefiningClassLoader}, on the form "foo.bar." (note the ending dot!) */ String[] packages(); /** * Should the packages in {@link #packages()} be added to {@link RedefiningClassLoader#DEFAULT_IGNORED_PACKAGES}? * {@code false} means {@link #packages()} will instead replace, and {@link RedefiningClassLoader#DEFAULT_IGNORED_PACKAGES} * will be redefined by {@link RedefiningClassLoader} unless specified by {@link #packages()}. */ boolean addToDefaults() default false; } ================================================ FILE: classloader-leak-test-framework/src/main/java/se/jiderhamn/classloader/RedefiningClassLoader.java ================================================ package se.jiderhamn.classloader; import org.apache.bcel.classfile.ClassFormatException; import org.apache.bcel.classfile.JavaClass; /** Classloader that redefines classes even if existing in parent */ public class RedefiningClassLoader extends org.apache.bcel.util.ClassLoader { private static final String DEBUG_SYSTEM_PROPERTY = "ClassLoaderLeakTestFramework.debug"; /** Override parents default and include */ public static final String[] DEFAULT_IGNORED_PACKAGES = { "java.", "javax.", "jdk.", "com.sun.", "sun.", "org.w3c", "org.junit.", "junit.", "com.apple.eawt.", "com.apple.eio.", "com.apple.laf." // Apple OpenJDK }; /** Set to non-null to indicate it should be ready for garbage collection */ @SuppressWarnings({"unused", "FieldCanBeLocal"}) private ZombieMarker zombieMarker = null; private final String name; private final boolean logRedefinitions; public RedefiningClassLoader(ClassLoader parent) { this(parent, null); } public RedefiningClassLoader() { this((String) null); } public RedefiningClassLoader(ClassLoader parent, String name) { this(parent, name, DEFAULT_IGNORED_PACKAGES); } RedefiningClassLoader(String name) { this(name, DEFAULT_IGNORED_PACKAGES); } public RedefiningClassLoader(ClassLoader parent, String name, String[] ignoredPackages) { super(parent, ignoredPackages); this.name = name; this.logRedefinitions = isDebugLoggingEnabled(); } RedefiningClassLoader(String name, String[] ignoredPackages) { super(ignoredPackages); this.name = name; this.logRedefinitions = isDebugLoggingEnabled(); } public static boolean isDebugLoggingEnabled() { return "true".equals(System.getProperty(DEBUG_SYSTEM_PROPERTY)); } @Override protected JavaClass modifyClass(JavaClass clazz) { if (logRedefinitions) { System.out.println("Loading " + clazz.getClassName() + " in " + this); } return super.modifyClass(clazz); } /** Mark this class loader as being ready for garbage collection */ public void markAsZombie() { this.zombieMarker = new ZombieMarker(); } @Override public String toString() { return (name != null) ? (this.getClass().getName() + '[' + name + "]@" + Integer.toHexString(System.identityHashCode(this))) : super.toString(); } @Override protected Class loadClass(String class_name, boolean resolve) throws ClassNotFoundException { try { int i = class_name.lastIndexOf('.'); if (i != -1) { String pkgName = class_name.substring(0, i); if (getPackage(pkgName) == null) { super.definePackage(pkgName, null, null, null, null, null, null, null); } } return super.loadClass(class_name, resolve); } catch (ClassFormatException e) { throw new RuntimeException("Unable to load class " + class_name, e); } } } ================================================ FILE: classloader-leak-test-framework/src/main/java/se/jiderhamn/classloader/ZombieMarker.java ================================================ package se.jiderhamn.classloader; /** * Class used to help identify leaked class loaders in a heap dump. * Inspired by Caucho's Resin * @author Mattias Jiderhamn */ public class ZombieMarker { } ================================================ FILE: classloader-leak-test-framework/src/main/java/se/jiderhamn/classloader/leak/JUnitClassloaderRunner.java ================================================ package se.jiderhamn.classloader.leak; import java.io.File; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.net.URL; import org.junit.Assert; import org.junit.internal.runners.statements.InvokeMethod; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; import se.jiderhamn.HeapDumper; import se.jiderhamn.classloader.PackagesLoadedOutsideClassLoader; import se.jiderhamn.classloader.RedefiningClassLoader; import se.jiderhamn.classloader.ZombieMarker; import static org.junit.Assert.fail; import static se.jiderhamn.HeapDumper.HEAP_DUMP_EXTENSION; /** * @author Mattias Jiderhamn */ public class JUnitClassloaderRunner extends BlockJUnit4ClassRunner { /** Number of seconds to halt to allow for heap dump aquirement, if that option is enabled */ private static final int HALT_TIME_S = 10; public JUnitClassloaderRunner(Class klass) throws InitializationError { super(klass); // TODO: Replace testclass here to support @Before, @After - alt throw exception if used } @Override protected Statement methodInvoker(FrameworkMethod method, Object test) { final LeakPreventor leakPreventorAnn = method.getMethod().getDeclaringClass().getAnnotation(LeakPreventor.class); Class preventorClass = (leakPreventorAnn != null) ? leakPreventorAnn.value() : null; return new SeparateClassLoaderInvokeMethod(method, test, preventorClass, test.getClass().getAnnotation(PackagesLoadedOutsideClassLoader.class)); } private class SeparateClassLoaderInvokeMethod extends InvokeMethod { /** The method to run for triggering potential leak, or verify non-leak */ private final Method originalMethod; /** Is the test method expeced to leak? */ private final boolean expectedLeak; /** * Should the thread pause for a couple of seconds before throwing the test failed error? * Set this to true to allow some time to aquire a heap dump to track down leaks. */ private final boolean haltBeforeError; /** Automatically generate a heap dump of classloader could not be garbage collected? */ private final boolean dumpHeapOnError; /** Class that can be used to remove the leak */ private Class preventorClass; /** Packages to be ignored by {@link RedefiningClassLoader}. If null, will use defaults. */ private final String[] ignoredPackages; private SeparateClassLoaderInvokeMethod(FrameworkMethod testMethod, Object target) { this(testMethod, target, null, null); } private SeparateClassLoaderInvokeMethod(FrameworkMethod testMethod, Object target, Class preventorClass, PackagesLoadedOutsideClassLoader packagesLoadedOutsideClassLoader) { super(testMethod, target); originalMethod = testMethod.getMethod(); final Leaks leakAnn = testMethod.getAnnotation(Leaks.class); this.expectedLeak = (leakAnn == null || leakAnn.value()); // Default to true this.haltBeforeError = (leakAnn != null && leakAnn.haltBeforeError()); // Default to false this.dumpHeapOnError = (leakAnn != null && leakAnn.dumpHeapOnError()); // Default to false this.preventorClass = preventorClass; this.ignoredPackages = (packagesLoadedOutsideClassLoader == null) ? null : packagesLoadedOutsideClassLoader.addToDefaults() ? appendArrays(RedefiningClassLoader.DEFAULT_IGNORED_PACKAGES, packagesLoadedOutsideClassLoader.packages()) : packagesLoadedOutsideClassLoader.packages(); } @SuppressWarnings("UnusedAssignment") @Override public void evaluate() throws Throwable { final ClassLoader clBefore = Thread.currentThread().getContextClassLoader(); final String testName = getTestClass().getName() + '.' + originalMethod.getName(); RedefiningClassLoader myClassLoader = (ignoredPackages != null) ? new RedefiningClassLoader(clBefore, testName, ignoredPackages): new RedefiningClassLoader(clBefore, testName); try { Thread.currentThread().setContextClassLoader(myClassLoader); // Load test class in our RedefiningClassLoader TestClass myTestClass = new TestClass(myClassLoader.loadClass(getTestClass().getName())); // Get test method in our RedefiningClassLoader (NOTE! can be in base class to test class) Method myMethod = myClassLoader.loadClass(originalMethod.getDeclaringClass().getName()) .getDeclaredMethod(originalMethod.getName(), originalMethod.getParameterTypes()); if (RedefiningClassLoader.isDebugLoggingEnabled()) { System.out.println("JUnit used " + getTestClass().getJavaClass().getClassLoader()); System.out.println("SeparateClassLoaderInvokeMethod used " + myTestClass.getJavaClass().getClassLoader()); } // super.evaluate(); = new FrameworkMethod(myMethod).invokeExplosively(myTestClass.getOnlyConstructor().newInstance()); // Make available to Garbage Collector myTestClass = null; myMethod = null; } catch (Throwable e) { e.printStackTrace(System.err); // Print here in case other exception is thrown in finally block // Loose the original throwable as it or its backtrace may itself cause a leak throw new RuntimeException(e.getClass().getName() + ": " + e.getMessage()); } finally { Thread.currentThread().setContextClassLoader(clBefore); // TODO: It's possible that an exception, loaded by the redefining classloader, has been thrown... final WeakReference weak = new WeakReference(myClassLoader); myClassLoader.markAsZombie(); myClassLoader = null; // Make available to garbage collector forceGc(3); if(expectedLeak) { // We expect this test to leak classloaders RedefiningClassLoader redefiningClassLoader = weak.get(); Assert.assertNotNull("ClassLoader has been garbage collected, while test is expected to leak", redefiningClassLoader); if(redefiningClassLoader != null && // Always true, otherwise assertion failure above preventorClass != null) { try { Thread.currentThread().setContextClassLoader(redefiningClassLoader); Class preventorInLeakedLoader = (Class) redefiningClassLoader.loadClass(preventorClass.getName()); Runnable leakPreventor = preventorInLeakedLoader.newInstance(); final String leakPreventorName = leakPreventor.toString(); leakPreventor.run(); // Try to prevent leak // Make available for Garbage Collection leakPreventor = null; preventorInLeakedLoader = null; redefiningClassLoader = null; Thread.currentThread().setContextClassLoader(clBefore); forceGc(3); final boolean leak = (weak.get() != null); // Still not garbage collected if(leak) { final String message = "ClassLoader (" + weak.get() + ") has not been garbage collected, " + "despite running the leak preventor " + leakPreventorName; weak.clear(); // Avoid including this reference in the heap dump performErrorActions(testName); fail(message); } } catch (Exception e) { throw new RuntimeException("Leak prevention class " + preventorClass.getName() + " could not be used!", e); } finally { redefiningClassLoader = null; Thread.currentThread().setContextClassLoader(clBefore); // Make sure it is reset, even if there is an error } } else { // Leak was expected, but we had no prevention mechanism final boolean leak = (weak.get() != null); // Still not garbage collected if(leak) { redefiningClassLoader = null; // Avoid including this reference in the heap dump weak.clear(); // Avoid including this reference in the heap dump performErrorActions(testName); } } } else { // We did not expect a leak final boolean leak = (weak.get() != null); // Still not garbage collected if(leak) { final String message = "ClassLoader has not been garbage collected " + weak.get(); weak.clear(); // Avoid including this reference in the heap dump performErrorActions(testName); fail(message); } } } } /** Call only if there is a leak */ private void performErrorActions(String testName) throws InterruptedException { if(dumpHeapOnError) { dumpHeap(testName); } if(haltBeforeError) { waitForHeapDump(); } } } /** Make sure Garbage Collection has been run N no of times */ public static void forceGc(int n) { for(int i = 0; i < n; i++) { forceGc(); } } /** Make sure Garbage Collection has been run */ public static void forceGc() { WeakReference ref = new WeakReference(new Object()); while(ref.get() != null) { // Until garbage collection has actually been run System.gc(); } } private static void waitForHeapDump() throws InterruptedException { System.out.println("Waiting " + HALT_TIME_S + " seconds to allow for heap dump acquirement"); System.out.println("Tip: You can search for " + ZombieMarker.class.getName() + " in the dump"); Thread.sleep(HALT_TIME_S * 1000); } /** Create heap dump in file with same name as the test */ private void dumpHeap(String testName) { final File surefireReports = getSurefireReportsDirectory(); try { File heapDump = (surefireReports != null) ? new File(surefireReports, testName + HEAP_DUMP_EXTENSION) : new File(testName + HEAP_DUMP_EXTENSION); HeapDumper.dumpHeap(heapDump, false); System.out.println("Heaped dumped to " + heapDump.getAbsolutePath()); } catch (ClassNotFoundException e) { System.out.println("Unable to dump heap - not Sun/Oracle JVM?"); } } /** * Try to find "target/surefire-reports" directory, assuming this is a Maven build. Returns null it not found, * not writable or other error. */ private File getSurefireReportsDirectory() { return getSurefireReportsDirectory(getTestClass().getJavaClass()); } /** * Try to find "target/surefire-reports" directory, assuming this is a Maven build. Returns null it not found, * not writable or other error. */ private static File getSurefireReportsDirectory(final Class clazz) { try { final String absolutePath = clazz.getResource(clazz.getSimpleName() + ".class").toString(); final String relativePath = clazz.getName().replace('.', '/') + ".class"; final String classPath = absolutePath.substring(0, absolutePath.length() - relativePath.length()); // Handle JAR files if(classPath.startsWith("jar:")) { return null; } final File dir = new File(new URL(classPath).toURI()); final File sureFireReports = new File(dir.getParent(), "surefire-reports"); if(! sureFireReports.exists() && "test-classes".equals(dir.getName()) && "target".equals(dir.getParentFile().getName())) { // Seems likely this is a Maven build, but surefire-reports have not been created yet (probably first test case) try { //noinspection ResultOfMethodCallIgnored sureFireReports.mkdirs(); } catch (Exception ignored) { // Do nothing } } return sureFireReports.exists() && sureFireReports.isDirectory() && sureFireReports.canWrite() ? sureFireReports : null; } catch (Exception e) { return null; } } /** Append two arrays */ private T[] appendArrays(T[] arr1, T[] arr2) { T[] output = (T[]) Array.newInstance(arr1.getClass().getComponentType(), arr1.length + arr2.length); System.arraycopy(arr1, 0, output, 0, arr1.length); System.arraycopy(arr2, 0, output, arr1.length, arr2.length); return output; } } ================================================ FILE: classloader-leak-test-framework/src/main/java/se/jiderhamn/classloader/leak/LeakPreventor.java ================================================ package se.jiderhamn.classloader.leak; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** Configure the Runnable that can be used to prevent the leak */ @Retention(RetentionPolicy.RUNTIME) public @interface LeakPreventor { Class value(); } ================================================ FILE: classloader-leak-test-framework/src/main/java/se/jiderhamn/classloader/leak/Leaks.java ================================================ package se.jiderhamn.classloader.leak; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** Annotation to indicate whether test case is expected to leak classloaders or not */ @Retention(RetentionPolicy.RUNTIME) public @interface Leaks { /** Is this test expected to leak classloaders? */ boolean value() default true; /** * Should the thread pause for a couple of seconds before throwing the test failed error? * Set this to true to allow some time to aquire a heap dump to track down leaks. */ boolean haltBeforeError() default false; /** * Set this to true to automatically generate a heap dump of classloader could not be garbage collected. Only works * on Sun/Oracle JVM. */ boolean dumpHeapOnError() default false; } ================================================ FILE: classloader-leak-test-framework/src/test/java/com/classloader/test/CustomClass.java ================================================ package com.classloader.test; public class CustomClass { } ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/RedefiningClassLoaderTest.java ================================================ package se.jiderhamn.classloader; import org.junit.Test; import org.junit.runner.RunWith; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; import se.jiderhamn.classloader.leak.Leaks; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @RunWith(JUnitClassloaderRunner.class) public class RedefiningClassLoaderTest { @Test @Leaks(false) public void getPackage() { Package aPackage = com.classloader.test.CustomClass.class.getPackage(); assertNotNull("Class should have non-null package", aPackage); assertEquals("com.classloader.test", aPackage.getName()); } } ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/leak/JUnitClassloaderRunnerTest.java ================================================ package se.jiderhamn.classloader.leak; import java.io.FileNotFoundException; import org.junit.Test; import org.junit.runner.RunWith; /** * This test makes sure that the {@link JUnitClassloaderRunner} does not keep a reference to the classloader in case * an exception is thrown * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) public class JUnitClassloaderRunnerTest { @Test(expected = RuntimeException.class) // FileNotFoundException replaced by RuntimeException @Leaks(false) public void throwException() throws Exception { throw new FileNotFoundException("foo.txt"); } @Test(expected = RuntimeException.class) // CustomError replaced by RuntimeException @Leaks(false) public void throwAssertionError() { throw new CustomError(); } public static class CustomError extends Error { } } ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/leak/NonLeakingTest.java ================================================ package se.jiderhamn.classloader.leak; import org.junit.Test; import org.junit.runner.RunWith; /** * Test that isn't supposed to leak, used to test the utility classes. * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) public class NonLeakingTest { @Test @Leaks(false) // Should not leak public void nonLeakingMethod() { System.out.println("Hello world!"); } } ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/leak/accused/CustomThreadLocalTest.java ================================================ package se.jiderhamn.classloader.leak.accused; import org.junit.Test; import org.junit.runner.RunWith; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; import se.jiderhamn.classloader.leak.Leaks; /** * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) public class CustomThreadLocalTest { private static final ThreadLocal customThreadLocal = new ThreadLocal() { /** Override method to create an anonymous subclass loaded by our classloader */ @Override protected Integer initialValue() { return Integer.MAX_VALUE; } }; /** * Having a custom ThreadLocal with at non-custom value does not leak, since the key in the ThreadLocalMap is weak */ @Test @Leaks(false) public void setValueOfCustomThreadLocal() { customThreadLocal.set(1); } } ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/leak/accused/package-info.java ================================================ package se.jiderhamn.classloader.leak.accused; /* Test cases in this package are used to confirm that code accused of causing leaks, does in fact not cause leaks */ ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/leak/known/CustomThreadLocalCustomValueTest.java ================================================ package se.jiderhamn.classloader.leak.known; import org.junit.Test; import org.junit.runner.RunWith; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; /** * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) public class CustomThreadLocalCustomValueTest { private static final ThreadLocal customThreadLocal = new ThreadLocal() { /** Override method to create an anonymous subclass loaded by our classloader */ @Override protected Value initialValue() { return new Value(); } }; @Test public void setCustomThreadLocalValue() { customThreadLocal.set(new Value()); } /** Custom value class, that will prevent garbage collection */ private static class Value { } // TODO: Preventor } ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/leak/known/JEditorPaneTest.java ================================================ package se.jiderhamn.classloader.leak.known; import javax.swing.*; import org.junit.Test; import org.junit.runner.RunWith; import se.jiderhamn.classloader.leak.JUnitClassloaderRunner; /** * Confirm leak caused by JEditorPane * @author Mattias Jiderhamn */ @RunWith(JUnitClassloaderRunner.class) public class JEditorPaneTest { @Test public void triggerJEditorPaneLeak() throws Exception { new JEditorPane("text/plain", "dummy"); } } ================================================ FILE: classloader-leak-test-framework/src/test/java/se/jiderhamn/classloader/leak/known/package-info.java ================================================ package se.jiderhamn.classloader.leak.known; /** Test cases in this package are used to confirm the existence of known leaks */ ================================================ FILE: mvnw ================================================ #!/bin/sh # ---------------------------------------------------------------------------- # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- # Apache Maven Wrapper startup batch script, version 3.1.1 # # Required ENV vars: # ------------------ # JAVA_HOME - location of a JDK home dir # # Optional ENV vars # ----------------- # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 # MAVEN_SKIP_RC - flag to disable loading of mavenrc files # ---------------------------------------------------------------------------- if [ -z "$MAVEN_SKIP_RC" ] ; then if [ -f /usr/local/etc/mavenrc ] ; then . /usr/local/etc/mavenrc fi if [ -f /etc/mavenrc ] ; then . /etc/mavenrc fi if [ -f "$HOME/.mavenrc" ] ; then . "$HOME/.mavenrc" fi fi # OS specific support. $var _must_ be set to either true or false. cygwin=false; darwin=false; mingw=false case "`uname`" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME else JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then JAVA_HOME=`java-config --jre-home` fi fi # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" fi if [ -z "$JAVA_HOME" ]; then javaExecutable="`which javac`" if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. readLink=`which readlink` if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then if $darwin ; then javaHome="`dirname \"$javaExecutable\"`" javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" else javaExecutable="`readlink -f \"$javaExecutable\"`" fi javaHome="`dirname \"$javaExecutable\"`" javaHome=`expr "$javaHome" : '\(.*\)/bin'` JAVA_HOME="$javaHome" export JAVA_HOME fi fi fi if [ -z "$JAVACMD" ] ; then if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi else JAVACMD="`\\unset -f command; \\command -v java`" fi fi if [ ! -x "$JAVACMD" ] ; then echo "Error: JAVA_HOME is not defined correctly." >&2 echo " We cannot execute $JAVACMD" >&2 exit 1 fi if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" return 1 fi basedir="$1" wdir="$1" while [ "$wdir" != '/' ] ; do if [ -d "$wdir"/.mvn ] ; then basedir=$wdir break fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then wdir=`cd "$wdir/.."; pwd` fi # end of workaround done printf '%s' "$(cd "$basedir"; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then echo "$(tr -s '\n' ' ' < "$1")" fi } BASE_DIR=$(find_maven_basedir "$(dirname $0)") if [ -z "$BASE_DIR" ]; then exit 1; fi MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR if [ "$MVNW_VERBOSE" = true ]; then echo $MAVEN_PROJECTBASEDIR fi ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found .mvn/wrapper/maven-wrapper.jar" fi else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi if [ -n "$MVNW_REPOURL" ]; then wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" else wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" fi while IFS="=" read key value; do case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; esac done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" if [ "$MVNW_VERBOSE" = true ]; then echo "Downloading from: $wrapperUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" if $cygwin; then wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` fi if command -v wget > /dev/null; then QUIET="--quiet" if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" QUIET="" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" else wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" fi [ $? -eq 0 ] || rm -f "$wrapperJarPath" elif command -v curl > /dev/null; then QUIET="--silent" if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" QUIET="" fi if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L else curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L fi [ $? -eq 0 ] || rm -f "$wrapperJarPath" else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then javaSource=`cygpath --path --windows "$javaSource"` javaClass=`cygpath --path --windows "$javaClass"` fi if [ -e "$javaSource" ]; then if [ ! -e "$javaClass" ]; then if [ "$MVNW_VERBOSE" = true ]; then echo " - Compiling MavenWrapperDownloader.java ..." fi # Compiling the Java class ("$JAVA_HOME/bin/javac" "$javaSource") fi if [ -e "$javaClass" ]; then # Running the downloader if [ "$MVNW_VERBOSE" = true ]; then echo " - Running MavenWrapperDownloader.java ..." fi ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") fi fi fi fi ########################################################################################## # End of extension ########################################################################################## MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` [ -n "$CLASSPATH" ] && CLASSPATH=`cygpath --path --windows "$CLASSPATH"` [ -n "$MAVEN_PROJECTBASEDIR" ] && MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" ================================================ FILE: mvnw.cmd ================================================ @REM ---------------------------------------------------------------------------- @REM Licensed to the Apache Software Foundation (ASF) under one @REM or more contributor license agreements. See the NOTICE file @REM distributed with this work for additional information @REM regarding copyright ownership. The ASF licenses this file @REM to you under the Apache License, Version 2.0 (the @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM @REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY @REM KIND, either express or implied. See the License for the @REM specific language governing permissions and limitations @REM under the License. @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- @REM Apache Maven Wrapper startup batch script, version 3.1.1 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files @REM ---------------------------------------------------------------------------- @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off @REM set title of command window title %0 @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") @REM Execute a user defined script before this one if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre @REM check for pre script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* :skipRcPre @setlocal set ERROR_CODE=0 @REM To isolate internal variables from possible post scripts, we use another setlocal @setlocal @REM ==== START VALIDATION ==== if not "%JAVA_HOME%" == "" goto OkJHome echo. echo Error: JAVA_HOME not found in your environment. >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error :OkJHome if exist "%JAVA_HOME%\bin\java.exe" goto init echo. echo Error: JAVA_HOME is set to an invalid directory. >&2 echo JAVA_HOME = "%JAVA_HOME%" >&2 echo Please set the JAVA_HOME variable in your environment to match the >&2 echo location of your Java installation. >&2 echo. goto error @REM ==== END VALIDATION ==== :init @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". @REM Fallback to current working directory if not found. set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir set EXEC_DIR=%CD% set WDIR=%EXEC_DIR% :findBaseDir IF EXIST "%WDIR%"\.mvn goto baseDirFound cd .. IF "%WDIR%"=="%CD%" goto baseDirNotFound set WDIR=%CD% goto findBaseDir :baseDirFound set MAVEN_PROJECTBASEDIR=%WDIR% cd "%EXEC_DIR%" goto endDetectBaseDir :baseDirNotFound set MAVEN_PROJECTBASEDIR=%EXEC_DIR% cd "%EXEC_DIR%" :endDetectBaseDir IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig @setlocal EnableExtensions EnableDelayedExpansion for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% :endReadAdditionalConfig SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( if "%MVNW_VERBOSE%" == "true" ( echo Found %WRAPPER_JAR% ) ) else ( if not "%MVNW_REPOURL%" == "" ( SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ "$webclient = new-object System.Net.WebClient;"^ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% ) ) @REM End of extension @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* %MAVEN_JAVA_EXE% ^ %JVM_CONFIG_MAVEN_PROPS% ^ %MAVEN_OPTS% ^ %MAVEN_DEBUG_OPTS% ^ -classpath %WRAPPER_JAR% ^ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end :error set ERROR_CODE=1 :end @endlocal & set ERROR_CODE=%ERROR_CODE% if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost @REM check for post script, once with legacy .bat ending and once with .cmd ending if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" :skipRcPost @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' if "%MAVEN_BATCH_PAUSE%"=="on" pause if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% cmd /C exit /B %ERROR_CODE% ================================================ FILE: pom.xml ================================================ 4.0.0 se.jiderhamn.classloader-leak-prevention classloader-leak-prevention-launcher 1.0.0-SNAPSHOT pom ClassLoader Leak Prevention Launcher Launcher POM for ClassLoader Leak Prevention https://github.com/mjiderhamn/classloader-leak-prevention classloader-leak-prevention classloader-leak-test-framework