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
[](http://travis-ci.org/mjiderhamn/classloader-leak-prevention)
[](https://maven-badges.herokuapp.com/maven-central/se.jiderhamn.classloader-leak-prevention/classloader-leak-prevention-servlet3/)
[](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 extends ClassLoaderPreMortemCleanUp>[] existingMustBeAfter =
((MustBeAfter)entry.getValue()).mustBeBeforeMe();
for(Class extends ClassLoaderPreMortemCleanUp> 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 extends I>[] 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, List/**/> nameToLevels = preventor.getStaticFieldValue(knownLevelClass, "nameToLevels");
final Map, List/**/> 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, List/**/> 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 extends ClassLoaderPreMortemCleanUp>[] 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 extends PlatformManagedObject> 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 /* NotificationEmitterSupport.ListenerInfo */> 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 /*javax.management.NotificationBroadcasterSupport.ListenerInfo*/> 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 extends ClassLoaderPreMortemCleanUp>[] 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 extends ClassLoaderPreMortemCleanUp>[] mustBeBeforeMe() {
return new Class[] {Foo.class};
}
}
private static class AfterBar extends RecordingCleanUp implements MustBeAfter {
AfterBar(List cleanUps) {
super(cleanUps);
}
@Override
public Class extends ClassLoaderPreMortemCleanUp>[] mustBeBeforeMe() {
return new Class[] {Bar.class};
}
}
private static class AfterFooAndBar extends RecordingCleanUp implements MustBeAfter {
AfterFooAndBar(List cleanUps) {
super(cleanUps);
}
@Override
public Class extends ClassLoaderPreMortemCleanUp>[] mustBeBeforeMe() {
return new Class[] {Foo.class, Bar.class};
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private static class Circle1 extends RecordingCleanUp implements MustBeAfter {
Circle1() {
super(null);
}
@Override
public Class extends ClassLoaderPreMortemCleanUp>[] mustBeBeforeMe() {
return new Class[] {Circle2.class};
}
}
private static class Circle2 extends RecordingCleanUp implements MustBeAfter {
Circle2() {
super(null);
}
@Override
public Class extends ClassLoaderPreMortemCleanUp>[] 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 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.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.
*
*
*
*
*
* 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 extends Runnable> 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 extends Runnable> 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 extends Runnable> 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 extends Runnable> 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 extends Runnable> 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