In a static method (Annotated with #CallerSensitive) I try to get the name of the calling class:
#CallerSensitive
public static void someMethod() {
String name = sun.reflect.Reflection.getCallerClass().getName();
...
}
I get the error:
java.lang.InternalError: CallerSensitive annotation expected at frame 1
What is wrong here?
References
http://www.infoq.com/news/2013/07/Oracle-Removes-getCallerClass
http://openjdk.java.net/jeps/176
UPDATE
I am using java 8 (u25) and the method getCallerClass() is not deprecated (getCallerClass(int) is deprecated) as can be seen when disassembling the bytecode:
$ /usr/lib/jvm/java-8-oracle/bin/javap -cp /usr/lib/jvm/java-8-oracle/jre/lib/rt.jar -verbose sun.reflect.Reflection > bytecode
Output (only relevant lines are shown)
Classfile jar:file:/usr/lib/jvm/jdk1.8.0_25/jre/lib/rt.jar!/sun/reflect/Reflection.class
Last modified Sep 17, 2014; size 6476 bytes
Compiled from "Reflection.java"
public class sun.reflect.Reflection
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#78 = Utf8 Lsun/reflect/CallerSensitive;
#80 = Utf8 Deprecated
#82 = Utf8 Ljava/lang/Deprecated;
{
public sun.reflect.Reflection();
descriptor: ()V
flags: ACC_PUBLIC
public static native java.lang.Class<?> getCallerClass();
descriptor: ()Ljava/lang/Class;
flags: ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
Signature: #76 // ()Ljava/lang/Class<*>;
RuntimeVisibleAnnotations:
0: #78()
public static native java.lang.Class<?> getCallerClass(int);
descriptor: (I)Ljava/lang/Class;
flags: ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
Deprecated: true
Signature: #81 // (I)Ljava/lang/Class<*>;
RuntimeVisibleAnnotations:
0: #82()
Only privileged code can use this annotation.
the code is privileged if it is loaded via bootstrap class loader or extension class loader.
excerpt from Open JDK source file classFileParser.cpp
// Privileged code can use all annotations. Other code silently drops some.
const bool privileged = loader_data->is_the_null_class_loader_data() ||
loader_data->is_ext_class_loader_data() ||
loader_data->is_anonymous();
To make java load your classes via bootstrap class loader you can use the -Xbootclasspath/a option to add your classes to the bootstrap class path when running java:
java -Xbootclasspath/a:classes_dir_or_jar_file fully.qualified.mainClassName
To have your classes loaded via extension class loader instead, you need to put your jar file in $JAVA_HOME/jre/lib/ext directory or any other directories set in java.ext.dirs system property.
getCallerClass()is removed from Java8. When I run this example in Java 8, I get the same error. Running with Java 7 (1.7.0_55) I get the name of the calling class. Anyway I would refrain from using anything directly from the sun.* package hierarchy.
To get the name of the calling class you can do the following (I just used the instance initializer to get the name, you should derive from SecurityManager and provide a getCallerClass() method in your class):
public static void someMethod() {
new SecurityManager() {
{
String name = getClassContext()[1].getSimpleName();
System.err.println(name == null ? "null" : name);
}
};
}
Related
I had a weird bug in an application code, which is an annotation processor and I could find that the root cause of the bug was that the class com.sun.tools.javac.tree.JCTree$JCClassDecl contains the method getSimpleName() twice when I query the class using the reflective method getMethods(). The two versions differ only in the return type. This is legal in JVM code, but not legal in Java. This is not method overloading, because it is only the return type that differs and the return type is not part of the method signature.
The issue can be demonstrated with the simple code:
Method[] methods = com.sun.tools.javac.tree.JCTree.JCClassDecl.class.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
It will print
...
public javax.lang.model.element.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
public com.sun.tools.javac.util.Name com.sun.tools.javac.tree.JCTree$JCClassDecl.getSimpleName()
...
(The ellipsis stands for more output lines showing the various other methods that are not interesting for us now.)
The Java version I used to test this is
$ java -version
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)
on a Windows 10 machine.
QUESTION: How was this class code created? My understanding is that this part of the code is written in Java, but in Java this is not possible. Also: what is the aim to have two same-signature versions of a method? Any hint?
If you look at the source code1 you'll see there's only one method with a name of getSimpleName(). This method returns com.sun.tools.javac.util.Name. There's two critical things to note about this:
That method is actually overriding com.sun.source.tree.ClassTree#getSimpleName() which is declared to return javax.lang.model.element.Name.
The com.sun.tools.javac.util.Name abstract class implements the javax.lang.model.element.Name interface, and since the overridden method returns the former it is taking advantage of covariant return types.
According to this Oracle blog, a method which overrides another but declares a covariant return type is implemented using bridge methods.
How is this implemented?
Although the return type based overloading is not allowed by java language, JVM always allowed return type based overloading. JVM uses full signature of a method for lookup/resolution. Full signature includes return type in addition to argument types. i.e., a class can have two or more methods differing only by return type. javac uses this fact to implement covariant return types. In the above, CircleFactory example, javac generates code which is equivalent to the following:
class CircleFactory extends ShapeFactory {
public Circle newShape() {
// your code from the source file
return new Circle();
}
// javac generated method in the .class file
public Shape newShape() {
// call the other newShape method here -- invokevirtual newShape:()LCircle;
}
}
We can use javap with -c option on the class to verify this. Note that we still can't use return type based overloading in source language. But, this is used by javac to support covariant return types. This way, there is no change needed in the JVM to support covariant return types.
And in fact, if you run the following command:
javap -v com.sun.tools.javac.tree.JCTree$JCClassDecl
The following will be output (only including the relevant methods):
public com.sun.tools.javac.util.Name getSimpleName();
descriptor: ()Lcom/sun/tools/javac/util/Name;
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #13 // Field name:Lcom/sun/tools/javac/util/Name;
4: areturn
LineNumberTable:
line 801: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
And:
public javax.lang.model.element.Name getSimpleName();
descriptor: ()Ljavax/lang/model/element/Name;
flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokevirtual #96 // Method getSimpleName:()Lcom/sun/tools/javac/util/Name;
4: areturn
LineNumberTable:
line 752: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/sun/tools/javac/tree/JCTree$JCClassDecl;
As you can see, the second method, the one which returns javax.lang.model.element.Name, is both synthetic and a bridge. In other words, the method is generated by the compiler as part of the implementation of covariant return types. It also simply delegates to the "real" method, the one actually present in the source code which returns com.sun.tools.javac.util.Name.
1. The source code link is for JDK 13.
The problem I am facing can be distilled down to this simple example. Compile this with Oracle java 7 jdk, and then attempt to run it with IBM jre 7 or jre 8. It fails with NoClassDefFoundError on java.lang.AbstractStringBuilder.
package org.example;
public class Example {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("got it") ;
System.out.println(" works "+sb);
StringBuilder sb2 = new StringBuilder("other") ;
sb2.append("killer") ;
sb2.length() ;
System.out.println("builder "+sb2);
System.out.println( sb == null ? sb2 : sb);
}
}
Related to http://chrononsystems.com/blog/java-7-design-flaw-leads-to-huge-backward-step-for-the-jvm the java 7 compiler adds stack frames to the byte code which include type verification for the stack operands.
Byte code for this example compiled with Oracle jdk includes:
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/StringBuffer, class java/lang/StringBuilder ]
stack = [ class java/io/PrintStream, class java/lang/AbstractStringBuilder ]
While bytecode from IBM jdk compiler is:
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/StringBuffer, class java/lang/StringBuilder ]
stack = [ class java/io/PrintStream, class java/io/Serializable ]
The stack frame from Oracle resolves to the base class AbstractStringBuilder class. That class does not exist in IBM JRE rt.jar. We use Oracle JDK in our build process & I’m reluctant to change that. This java 7 type verification seems to create non-portable bytecode.
Is there a way to disable this type verification checking at either compile time or runtime?
Thanks
Why would you try to compile and run Java in different Vendors. I see imports are different. Something might have gone wrong somewhere. Oracle JDK has StringBuilder as well
Try using java.lang.StringBuilder instead of StringBuilder and share what output you are getting.
java.lang.StringBuffer sb = new java.lang.StringBuffer("got it") ;
System.out.println(" works "+sb);
java.lang.StringBuilder sb2 = new java.lang.StringBuilder("other") ;
Thanks for the advice & help. After posting this, we found the root cause. we were using an older version of aspect-j to instrument the classes before packaging them in our application. Sorry, I never found why the aspect j instrumentation added AbstractStringBuilder to the altered class file.
We were no longer requiring aspect-j. So removing that solved the problem.
Thanks for the help
I ran into a very unexpected error today and while I was able to find a way to fix the problem as a whole I'm not sure I completely understand why it did what it did.
The code I'm working with was originally written with a JDK 7 environment of course targeting JRE 7. In the code I was using a ConcurrentHashMap and needed to iterate over the keys in the map. For this I was using the map.keySet() which according to the JavaDocs should return a Set<K>. This worked fine until our build environment switched to JDK8.
When we moved to JDK8 I ensured that I was calling a target/source for 1.7 when calling the javac. So I was pretty surprised when the code started failing right when it wanted to iterate through the keys of the map. No error was thrown, no exception, the thread just simply stopped. After doing some research I found that Java8's implementation for ConcurrentHashMap the .keySet() method returns a KeySetView<K,V>.
I fixed the problem by switching from using the map.keySet() to getting an Enumeration<K> using map.keys().
Now my guess as to the problem is that although the project was compiled targeting Java7 since the JDK8 was used the Java8 libraries were included, but why didn't it thrown an error or an exception when it hit the mismatch?
As asked here is a code snippet:
class MapProcessing
{
private ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<String, Object>();
public MapProcessing()
{
map.put("First",new Object());
map.put("Second",new Object());
map.put("Third",new Object());
}
public void processing()
{
// when calling this type of loop causes a freeze on our system.
for(String key : map.keySet())
{
System.out.println(key);
}
}
public void working()
{
// This is what I had to do to fix the problem.
Enumeration<String> keys = map.keys();
while(keys.hasMoreElements())
{
String key = keys.nextElement();
System.out.println(key);
}
}
}
We are compiling using Oracle JDK 8 build 40 using a target for 1.7 and source 1.7 in the javac on a Windows 2012 server.
The code is running using Oracle JVM 7 build 25 running on Windows 2012 server.
If i compile your code with Java 8 and javac -source 1.7 -target 1.8 and then run it with Java 7 i get an
Exception in thread "main" java.lang.NoSuchMethodError:
java.util.concurrent.ConcurrentHashMap.keySet()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
at stackoverflowt.Test.processing(Test.java:20)
at stackoverflowt.Test.main(Test.java:27)
This is because the the byte code looks like
public void processing();
Code:
0: aload_0
1: getfield #4 // Field map:Ljava/util/concurrent/ConcurrentHashMap;
4: invokevirtual #10 // Method java/util/concurrent/ConcurrentHashMap.keySet:()Ljava/util/concurrent/ConcurrentHashMap$KeySetView;
7: invokevirtual #11 // Method java/util/concurrent/ConcurrentHashMap$KeySetView.iterator:()Ljava/util/Iterator;
10: astore_1
and referring explicitly to ConcurrentHashMap$KeySetView which is not present in Java 7. I am on Mac with Java 1.7.0_79 and 1.8.0_45
If you change the code to (only use the Map Interface):
private Map<String, Object> map = new ConcurrentHashMap<String, Object>();
then it work's for me. Bytecode then looks like
public void processing();
Code:
0: aload_0
1: getfield #4 // Field map:Ljava/util/Map;
4: invokeinterface #10, 1 // InterfaceMethod java/util/Map.keySet:()Ljava/util/Set;
9: invokeinterface #11, 1 // InterfaceMethod java/util/Set.iterator:()Ljava/util/Iterator;
14: astore_1
Whenever you build a project using a newer JDK using the -source argument targeting an older version, you'll get this compiler warning:
warning: [options] bootstrap class path not set in conjunction with -source 1.7
This blog entry talks about what it means.
Basically, you get this warning because Java is compiling it using older language rules but against the newer class library... and there are some compatibility issues with the Java 8 versions as Oracle moved some of the internal classes around.
The fix is to use the -bootclasspath argument to point it at the rt.jar from the older version while compiling.
I am trying to access Java DAO methods from Python using Py4J, and I am running into a strange problem. I have the following MyApplication.java file. I can compile correctly, and it seems to sort of work from Python using Py4J:
import py4j.GatewayServer;
import company.common.dao.DAOFactory;
import company.sys.dao.ABCartDAO;
public class MyApplication {
public String foo (int n) {
ShoppingCart bar = DAOFactory.getDAO(ABCartDAO.class);
String identifier bar.findit(n)
return identifier;
}
public static void main(String[] args) {
MyApplication app = new MyApplication();
GatewayServer server = new GatewayServer(app);
server.start();
}
}
I can start the server with java MyApplication. However, when I run the following in Python:
from py4j.java_gateway import JavaGateway
gateway = JavaGateway() # connect to the JVM
# Testing that it works
random = gateway.jvm.java.util.Random() # create a java.util.Random instance
number1 = random.nextInt(10) # call the Random.nextInt method
number2 = random.nextInt(10)
print(number1,number2)
my_application = gateway.entry_point
my_application.foo(4)
Getting the random numbers works (proof that Py4J is doing its job), but the call to operation in it fails with:
---------------------------------------------------------------------------
Py4JJavaError Traceback (most recent call last)
<ipython-input-14-d2345f6205bf> in <module>()
----> 1 my_application.foo(4)
/Users/josh/anaconda/envs/py27/lib/python2.7/site-packages/py4j/java_gateway.pyc in __call__(self, *args)
535 answer = self.gateway_client.send_command(command)
536 return_value = get_return_value(answer, self.gateway_client,
--> 537 self.target_id, self.name)
538
539 for temp_arg in temp_args:
/Users/josh/anaconda/envs/py27/lib/python2.7/site-packages/py4j/protocol.pyc in get_return_value(answer, gateway_client, target_id, name)
298 raise Py4JJavaError(
299 'An error occurred while calling {0}{1}{2}.\n'.
--> 300 format(target_id, '.', name), value)
301 else:
302 raise Py4JError(
Py4JJavaError: An error occurred while calling t.foo.
: java.lang.NoClassDefFoundError: company/sys/dao/ABCartDAO
at MyApplication.foo(MyApplication.java:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:231)
at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:379)
at py4j.Gateway.invoke(Gateway.java:259)
at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)
at py4j.commands.CallCommand.execute(CallCommand.java:79)
at py4j.GatewayConnection.run(GatewayConnection.java:207)
at java.lang.Thread.run(Thread.java:744)
Why?
In case it matters, I am building my java file with the following in my pom.xml
<dependency>
<groupId>net.sf.py4j</groupId>
<artifactId>py4j</artifactId>
<type>jar</type>
<version>0.8.1</version>
</dependency>
Am I a supposed to include some additional dependencies in the compilation of my .class file? Is this a bug of Py4J? Perhaps an unsupported feature?
You have a lot of unrelated questions here. Answering the first one:
Say I define a class called MyClass What does MyClass.class hold? is it a string? (if so, what would it be for MyClass?) Something else?
It holds a reference to a Class object, which you can use for various things, most notable getting runtime information about the class for debugging and reflection. It is also the same thing returned by the non-static method getClass():
Class<?> clz = MyClass.class;
Class<?> clz = new MyClass().getClass();
I am not completely familiar with Py4J, but you may be able to use the latter to access the Class of a given object instance. Note that getClass() is a base method of java.lang.Object.
I try to print out the readable java bytecode to see the monitorenter and monitorexit to study about the deadlock and synchronization instruction set but I don't know what the java command or binary that I should use to get the readable java bytecode.
Use the javap command, for example:
javap -v SomeClass.class
Example output:
19:23:56 (brettw) [dev] hikari$ javap -v HikariPool.class
Classfile /Users/brettw/Documents/dev/HikariCP/core/target/classes/com/zaxxer/hikari/HikariPool.class
Last modified Dec 19, 2013; size 11754 bytes
MD5 checksum 00e0441d0aad3bad1f4e7a67f6043b9c
Compiled from "HikariPool.java"
public final class com.zaxxer.hikari.HikariPool implements com.zaxxer.hikari.HikariPoolMBean
SourceFile: "HikariPool.java"
InnerClasses:
#384; //class com/zaxxer/hikari/HikariPool$1
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
#1 = Class #2 // com/zaxxer/hikari/HikariPool
#2 = Utf8 com/zaxxer/hikari/HikariPool
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Class #6 // com/zaxxer/hikari/HikariPoolMBean
#6 = Utf8 com/zaxxer/hikari/HikariPoolMBean
...