Re: Classes on the stack trace (was: getElementClass/StackTraceElement, was: @CallerSensitive public API, was: sun.reflect.Reflection.getCallerClass)

Jörn Huxhorn jhuxhorn at googlemail.com
Mon Jul 29 20:57:31 UTC 2013


I second all of that.

It was my understanding that the whole purpose of EA and DP was pinpointing problems like these before they hit consumers. What's the point of them if feedback can't cause the reverting of a breaking change?

If this is not rectified then the old "Java is slow"-mantra will once again be quite true.
That, and lots of applications will break since not everyone updating to j7u40 will also update all libraries that currently depend on that method - if a timely fix will exist at all at that point. Throwing UnsupportedOperationException all of a sudden isn't exactly the most subtle way of handling this.

And please don't argue that everyone should just add that runtime property. This isn't realistic.
On 29. Juli 2013 at 21:03:10, Nick Williams (nicholas+openjdk at nicholaswilliams.net) wrote:

Some interesting things to note:

1) Someone has been asking for a public API replacement for getCallerClass() since Java 4 (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4851444). I /would/ have been asking for this for that long, except I haven't needed it until recently and assumed it already existed. .NET has had an API similar to what I proposed below since .NET 1.1.

2) http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8014925 discusses the change to getCallerClass(int) and says it will be completely removed (with no system property to re-enable) as early as 7u55. The bug then talks about how feedback needs to be solicited on how the community is using it so that a public API, if needed, can be created. This is that feedback, and the answer appears to be a resounding YES, a public API needs to replace it because this change is disastrous for many projects. As Paul says, at the very least getCallerClass(int) should keep working like it does in <7u25 (with or without a system property) through the rest of Java 7 and Java 8 if a replacement public API is not adopted.

3) The performance test numbers below don't reflect the fact that some use cases (like Log4j) have to use Thread/Throwable#getStackTrace() AND Reflection/SecurityManager. These use cases need the method name/line number AND the Class instance. If Reflection#getCallerClass() is taken away from us and SecurityManager#getClassContext() is unavailable (possible if an existing SecurityManager prohibits instantiating the SecurityManager class), these use cases have to call getStackTrace() and THEN Class#forName() for each StackTraceElement. I'm sure you can see where performance becomes a serious issue at that point. The proposed API below was designed to solve all of the uses cases that have been discussed in a well-performing way.

Nick

On Jul 29, 2013, at 10:47 AM, Jörn Huxhorn wrote:

The numbers are from this link: http://stackoverflow.com/questions/421280/in-java-how-do-i-find-the-caller-of-a-method-using-stacktrace-or-reflection

Even if this benchmark suffers from micro-benchmark issues:
a slow-down of 10x would be bad, a slow-down of 100x would be a catastrophe.

I'd suggest to at least postpone the UnsupportedOperationException change until we find a suitable replacement. This change will also break existing Groovy scripts. See http://jira.codehaus.org/browse/GROOVY-6279 - but there are other issues as well.

Cheers,
Jörn.

On 29. Juli 2013 at 16:49:02, Nicholas Williams (nicholas+openjdk at nicholaswilliams.net) wrote:

I wasn't the one who ran the test, so I don't know for sure. My theory  
was that getCallerClass() returns a single frame, but the  
SecurityManager must allocate an array of appropriate size (which  
involves some overhead) and then return all of the frames. I chalked  
the difference up to that. My conclusion from the data was: If you  
need a whole stack, SecurityManager is clearly the best option. If you  
need a single frame, getCallerClass() is the only option that makes  
any sense.  

On Mon, Jul 29, 2013 at 8:21 AM, David M. Lloyd <david.lloyd at redhat.com> wrote:  
I find it very interesting that reflection is no less than two orders of  
magnitude faster than the security manager solution. How big was the stack  
in these tests? It makes me wonder if maybe the implementation of the  
security manager's getContext() method should be reevaluated a bit.  


On 07/29/2013 07:53 AM, Nick Williams wrote:  

Just so that everyone understands how important this subject is, this  
change to getCallerClass(...) is being labeled a "disaster" for logging  
frameworks everywhere. Here's a benchmark for getting Classes from the  
following methods:  

1,000,000 calls of all alternatives were measured as follows :  
Reflection: 10.195 ms.  
Current Thread StackTrace: 5886.964 ms.  
Throwable StackTrace: 4700.073 ms.  
SecurityManager: 1046.804 ms.  


My goal here is to get the entire list engaged in coming up with the right  
solution. We (the community) can't afford for Java 8 not to have an  
equivalent replacement for getCallerClass().  

Nick  

On Jul 27, 2013, at 2:01 PM, Nick Williams wrote:  

All,  

In the last two months, there have been a number of discussions  
surrounding stack traces, Classes on the stack trace, and caller classes  
[1], [2], [3]. These are all related discussions and the solution to them is  
equally related, so I wanted to consolidate it all into this one discussion  
where I hope we can finalize on a solution and get it implemented for Java  
8.  

In a nut shell, here are the underlying needs that I have seen expressed  
through many, many messages:  

- Some code needs to get the Class of the caller of the current method,  
skipping any reflection methods.  
- Some code needs to get the Class of the caller /n/ stack frames before  
the current method, skipping any reflection methods.  
- Some code needs to get the current stack trace, populated with Classes,  
Executables, file names, line numbers, and native flags instead of the  
String class names and String method names in StackTraceElement. This  
/should/ include any reflection methods, just like StackTraceElement[]s.  
- Some code needs to get the stack trace from when a Throwable was  
created, populated with Classes, Executables, file names, line numbers, and  
native flags instead of the String class names and String method names in  
StackTraceElement. This /should/ include any reflection methods, just like  
StackTraceElement[]s.  
- There needs to be a reflection way to achieve all of this since some  
libraries (e.g., Log4j) need to be compiled against Java 6 but run on 7 and  
8 (and thus can't use @CallerSensitive).  

I believe the solutions to these needs are all related. Importantly, I  
think it is very important that action be taken in Java 8 due to the changes  
made to sun.reflect.Reflection#getCallerClass(...). While we all understand  
that relying on private sun.* APIs is not safe, the fact is that many people  
have relied on sun.reflect.Reflection#getCallerClass(...) due to the fact  
that there is simply no other way to do this in the standard API. This  
includes Log4j 2, Logback, SLF4j, and Groovy, some features of which will  
stop working correctly in Java 7 >= u25.  

I would point out that this could all easily be solved simply by adding a  
getElementClass() method to StackTraceElement, but there was strong  
opposition to this, largely due to serialization issues. Since that is  
apparently not an option, I propose the following API, based on the various  
discussions in the last two months, StackTraceElement, and the API that .NET  
provides to achieve the same needs as listed above:  

CallerSensitive.java:  
package java.lang;  

/** Previously private API, now public */  
public @interface CallerSensitive {  
...  
}  

StackTraceFrame.java:  
package java.lang;  

import java.util.Objects.  

public final class StackTraceFrame {  
private final Class<?> declaringClass;  
private final Executable executable;  
private final String fileName;  
private final int lineNumber;  

public StackTraceFrame(Class<?> declaringClass, Executable  
executable, String fileName, int lineNumber) {  
this.declaringClass = Objects.requireNonNull(declaringClass,  
"Declaring class is null");  
this.executable = Objects.requireNonNull(executable, "Executable  
is null");  
this.fileName = fileName;  
this.lineNumber = lineNumber;  
}  

public Class<?> getDeclaringClass() {  
return this.declaringClass;  
}  

public Executable getExecutable() {  
return this.executable;  
}  

public String getFileName() {  
return this.fileName;  
}  

public int getLineNumber() {  
return this.lineNumber;  
}  

public boolean isNative() {  
return this.lineNumber == -2;  
}  

public String toString() { /* Same as StackTraceElement */ }  
public boolean equals() { /* Ditto */ }  
public int hashCode() { /* Ditto */ }  

/** Uses @CallerSensitive */  
public static native StackTraceFrame getCallerFrame();  

/** Works like Java < 7u25 sun.reflect.Reflection#getCallerClass()  
*/  
public static native StackTraceFrame getCallerFrame(int skipFrames);  

public static native StackTraceFrame[] getCurrentStackTrace();  
}  

Throwable.java:  
package java.lang;  

...  

public class Throwable {  
...  
public synchronized Throwable fillInStackTraceFrames() { ... }  

private native Throwable fillInStackTraceFrames(int dummy);  

public StackTraceFrame[] getStackTraceFrames() {  
return this.getOurStackTraceFrames().clone();  
}  

private synchronized StackTraceFrame[] getOurStackTraceFrames() {  
... }  
...  
}  

Furthermore, I propose that we restore the behavior of  
sun.reflect.Reflection#getCallerClass(int) /just for Java 7/ since the  
proposed above solution cannot be added to Java 7.  

I would love if we could quickly coalesce around this solution or a  
derivative thereof so that it can be implemented before Feature Complete.  
The absence of any replacement or alternative for  
sun.reflect.Reflection#getCallerClass(int) will be a serious issue in Java 8  
that will cause hardships for many projects.  

[1]  
http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-June/018049.html  
[2]  
http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-June/018349.html,  
http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-July/019098.html  
[3]  
http://mail.openjdk.java.net/pipermail/core-libs-dev/2013-July/018855.html  




--  
- DML



More information about the core-libs-dev mailing list