RFR(M): 8203826: Chain class initialization exceptions into later NoClassDefFoundErrors

Peter Levart peter.levart at gmail.com
Wed Jul 4 10:39:20 UTC 2018


Hi Volker,

It occurred to me that getting rid of backtrace-s of cause(s)/suppressed 
exception(s) might not be enough to prevent ClassLoader leaks...

On 07/04/2018 10:21 AM, Lindenmaier, Goetz wrote:
>> dealing with backtrace and stackTrace. I have to wonder why nothing in
>> Throwable clears the backtrace today ?
> Maybe the concern about the backTraces is pointless and the
> conversion to stackTraces should be dropped.
> As you say, it's done nowhere else, and other backTraces should
> cause similar issues.
>

Exception objects are typically not retained for longer periods. They 
are normally caught, dumped to log and let gone. This change retains 
exception(s) so that they are reachable from a ClassLoader that loaded 
the failed class. It could happen that the chain of cause(s)/suppressed 
exception(s) of some ExceptionInInitializerError is an exception object 
of a class that is loaded by some child ClassLoader of the ClassLoader 
that loaded the failed class. Such child ClassLoader would have leaked.

The solution would be to replace the chain of cause(s)/suppressed 
exception(s) with a chain of replacement exception objects like this one 
(this would also take care of backtraces of original exceptions as it 
would not retain the original exceptions at all):


/**
  * A {@link RuntimeException} that acts as a substitute for the 
original exception
  * (checked or unchecked) and mimics the original exception in every 
aspect except it's type.
  */
public class ExceptionSubstitute extends RuntimeException {
     private static final long serialVersionUID = 1;

     private String originalExceptionClassName, localizedMessage;

     public ExceptionSubstitute(Throwable originalException) {
         super(originalException.getMessage());

         this.originalExceptionClassName = 
originalException.getClass().getName();
         this.localizedMessage = originalException.getLocalizedMessage();

         // substitute originalException's cause
         Throwable cause = originalException.getCause();
         initCause(cause == null ? null : new ExceptionSubstitute(cause));

         // substitute originalException's suppressed exceptions if any
         for (Throwable suppressed : originalException.getSuppressed()) {
             addSuppressed(new ExceptionSubstitute(suppressed));
         }

         // inherit stack trace elements from originalException
         setStackTrace(originalException.getStackTrace());
     }

     @Override
     public Throwable fillInStackTrace() {
         // don't need our backtrace - will inherit stack trace elements 
from originalException
         return this;
     }

     @Override
     public String getLocalizedMessage() {
         return localizedMessage;
     }

     /**
      * @return the class name of the original exception for which this 
exception is a substitute
      */
     public String getOriginalExceptionClassName() {
         return originalExceptionClassName;
     }

     /**
      * Emulate toString() method as if called upon originalException
      */
     @Override
     public String toString() {
         String message = getLocalizedMessage();
         return (message != null)
                ? (getOriginalExceptionClassName() + ": " + message)
                : getOriginalExceptionClassName();
     }
}


Regards, Peter



More information about the core-libs-dev mailing list