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

Nick Williams nicholas+openjdk at nicholaswilliams.net
Mon Jul 29 19:03:08 UTC 2013


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