I am trying to understand the implementation of Throwable.fillInStackTrace():
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0];
[...]
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
[...]
/**
* Fills in the execution stack trace. This method records within this
* {@code Throwable} object information about the current state of
* the stack frames for the current thread.
*
* <p>If the stack trace of this {@code Throwable} {@linkplain
* Throwable#Throwable(String, Throwable, boolean, boolean) is not
* writable}, calling this method has no effect.
*
* @return a reference to this {@code Throwable} instance.
* @see java.lang.Throwable#printStackTrace()
*/
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}
private native Throwable fillInStackTrace(int dummy);
(source OpenJDK)
I would have guess that fillInStackTrace(int) collects the stack trace from VM internals, and then creates an array of java/lang/StackTraceElement and assigns it to the Throwable.stackTrace property.
However, the standard implementation seems to clear the value of Throwable.stackTrace directly after. When running a simple sample, the information is clearly stored in the Throwable. Sample:
Throwable th = new Throwable();
th.printStackTrace(); // prints the stack up to this point
So, where is the magic / what am I misunderstanding?
PS: what does "out of protocol state" mean in the source code?
CodePudding user response:
The native fillInStackTrace(int)
method does not create an array of StackTraceElement
instances. It captures a snapshot of the current stack trace in an implementation specific format and stores it in the backtrace
variable.
Only when the array of StackTraceElement
instances is requested, the method getOurStackTrace()
will generate the array:
private synchronized StackTraceElement[] getOurStackTrace() {
// Initialize stack trace field with information from
// backtrace if this is the first call to this method
if (stackTrace == UNASSIGNED_STACK ||
(stackTrace == null && backtrace != null) /* Out of protocol state */) {
int depth = getStackTraceDepth();
stackTrace = new StackTraceElement[depth];
for (int i=0; i < depth; i )
stackTrace[i] = getStackTraceElement(i);
} else if (stackTrace == null) {
return UNASSIGNED_STACK;
}
return stackTrace;
}
It’s using the helper method
native StackTraceElement getStackTraceElement(int index);
which knows how to decode the backtrace
.
So if the application never accesses the stacktrace, this might be cheaper than generating the full array in advance.
You have already posted the declaration of stackTrace
which initializes the field with UNASSIGNED_STACK
. But there is this special constructor which allows to suppress stack traces altogether.
protected Throwable(String message, Throwable cause,
boolean enableSuppression,
boolean writableStackTrace) {
if (writableStackTrace) {
fillInStackTrace();
} else {
stackTrace = null;
}
detailMessage = message;
this.cause = cause;
if (!enableSuppression)
suppressedExceptions = null;
}
So, normally stackTrace
should be either, UNASSIGNED_STACK
or an actual array created by getOurStackTrace()
or set using setStackTrace(StackTraceElement[])
.
If stackTrace
is null
, the backtrace
should also be null
, if stack traces are disabled. If this is not the case, the state is “out of protocol”, which means the throwable has not been generated using normal constructor invocation, but by native code.
CodePudding user response:
Your guess does not seem to be right if we follow fillInStackTrace
native method for a bit we can see that it sets Throwable#backtrace. The implementation looks compliucated but it basicallly just strips out fillInStackTrace and the contructor calls (with can be overriden).
For the next think keep in mind that Throwable
- can be serialized (no constructor call into
readObject
) - can be created directly in the vm
OutOfMemoryExceptions
are preloaded (because it can not be instanciated when oom)fillStackTrace
is commonly overriden and left empty in SubClasses (mostly for performance)
Then in getOurStackTrace()
the real stackTrace is written. This was made because after contructing the exception is in a immutable state (between backtrace set but stacktrace null).
Out of protocol state refers to Throwables created without calling a constructor.
If you have further questions just ask. I really like your question :)