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

Nicholas Williams nicholas+openjdk at nicholaswilliams.net
Mon Jul 29 14:48:08 UTC 2013


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