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

Reply via email to