Access com.sun.* classes from openjdk11 - java

I have a Spring Boot application and I want to use the class com.sun.management.ThreadMXBean, and the method getThreadAllocatedBytes to collect information about allocated bytes in my application. I dockerized it and used OpenJDK 11 in the dockerfile, because Oracle JDK cannot be dockerized. I'm using docker image jboss/base-jdk:11 and deploy the application in a Wildfly 16.
Unfortunately OpenJDK does not support the com.sun.* packages. Is there any way I can work around this problem and use com.sun.management.ThreadMXBean.getThreadAllocatedBytes in an OpenJDK?

So, this worked for me with AdoptJdk 11 (which is a build of OpenJdk):
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.lang.management.ThreadMXBean;
public class Test {
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
try {
Method getBytes = threadMXBean.getClass().getMethod("getThreadAllocatedBytes", long.class);
getBytes.setAccessible(true);
long threadId = Thread.currentThread().getId();
long bytes = (long)getBytes.invoke(threadMXBean, threadId);
System.out.println(bytes);
} catch (Throwable e) {
System.out.println(e);
}
}
}
Invoke with
C:\workspaces\devtools\jdks\adoptjdk\jdk-11.0.2+9\bin\javac Test.java
C:\workspaces\devtools\jdks\adoptjdk\jdk-11.0.2+9\bin\java --add-exports jdk.management/com.sun.management.internal=ALL-UNNAMED Test
Also, in that docker image it works
FROM jboss/base-jdk:11
COPY . /app/
WORKDIR /app
CMD java --add-exports jdk.management/com.sun.management.internal=ALL-UNNAMED Test
And run it in docker:
docker build -t openjdktest .
docker run -it openjdktest
EDIT
Oh, there seems to be an even simpler alternative. Just cast your ThreadMXBean object directly to com.sun.management.ThreadMXBean:
import java.lang.management.ManagementFactory;
import com.sun.management.ThreadMXBean;
public class Test {
public static void main(String[] args) {
ThreadMXBean threadMXBean = (ThreadMXBean)ManagementFactory.getThreadMXBean();
long bytes = threadMXBean.getThreadAllocatedBytes(Thread.currentThread().getId());
System.out.println(bytes);
}
}
This can be run, even without the --add-exports JVM argument.

So, I just downloaded OpenJDK 11 and extracted its sources.
Inside I found ThreadImpl, which implements java.lang.management.ThreadMXBean
And infact you can find it under
ThreadMXBean has indeed a getThreadAllocatedBytes method
protected long[] getThreadAllocatedBytes(long[] ids) {
boolean verified = verifyThreadAllocatedMemory(ids);
long[] sizes = new long[ids.length];
java.util.Arrays.fill(sizes, -1);
if (verified) {
getThreadAllocatedMemory1(ids, sizes);
}
return sizes;
}

Related

how to set the environment variables in junit 5 for test case

i am working on Junit5 . My java code uses the System.getenv("demoVar") to access environment variable . so how do i set up this environment variable in the jUnit5 test class , so that my code can access the value of this environment variable during the test.
From this other SO answer https://stackoverflow.com/a/59635733/2185719:
There is JUnit Pioneer, a "JUnit 5 extension pack".
jUnit Pioneer offers an annotation that sets environment variables for a test. For example:
#Test
#SetEnvironmentVariable(key = "PATH", value = "")
void testPath_isEmpty() {
assertThat(System.getenv("PATH")).isEmpty();
}
You can't within the actual java process because these environmental values using getenv are immutable.
One way would be to start another vm or another process where you could introduce your new environment value.
Another way would be to switch to System.getProperty, but be sure you understand the differences.
https://www.baeldung.com/java-system-get-property-vs-system-getenv
Here is a little testcode:
public class EnvironmentVarsTest {
private static int counter = 0;
#BeforeEach
public void setUp() {
counter = counter + 1;
System.setProperty("simple_test_env_property", String.valueOf(counter));
}
#Test
public void testFirst() {
printOutValues();
}
#Test
public void testSecond() {
printOutValues();
}
private void printOutValues() {
System.out.println("--------------------");
System.out.println("val=" + counter);
System.out.println("envval=" + System.getProperty("simple_test_env_property"));
}
}
This can be achieved with https://github.com/webcompere/system-stubs/tree/master/system-stubs-jupiter
#ExtendWith(SystemStubsExtension.class)
class TestClass {
#SystemStub
private EnvironmentVariables environmentVariables =
new EnvironmentVariables("demoVar", "val");
#Test
void test() {
// can use the environment
assertThat(System.getenv("demoVar")).isEqualTo("val");
// can also change the environment
environmentVariables.set("foo", "bar");
// environment variables restored to previous state when
// test ends
}
}
The common practice is to use System properties instead of environment variables.
In this case you will run your java/maven/gradle command or whatever you use to run your tests with option -D demoVar="{your_value}".
for maven goal:
maven clean install -DdemoVar="test"
for java jar:
java -jar xxx.jar -DdemoVar="test"
You will be able to get it from code with System.getProperty("demoVar").
If you really need to use environment variable, use OS functionality.
For linux:
demoVar="test" mvn clean install
For windows PowerShell:
$env:demoVar = 'test'; mvn clean install
Simple solution if you use Gradle, you can add following to your build.gradle
test {
environment "ENV_VAR_NAME", "ENV_VAR_VALUE"
}
Link to Gradle doc: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.testing.Test.html#org.gradle.api.tasks.testing.Test:environment

JNR UnsatisfiedLinkError

This question might be related to this and a ton other UnsatisfiedLinkError questions.
I'm trying to run the following code.
import jnr.ffi.LibraryLoader;
import jnr.ffi.types.pid_t;
/**
* Gets the process ID of the current process, and that of its parent.
*/
public class Getpid {
public interface LibC {
public #pid_t long getpid();
public #pid_t long getppid();
}
public static void main(String[] args) {
LibC libc = (LibC) LibraryLoader.create(LibC.class).load("c");
System.out.println("pid=" + libc.getpid() + " parent pid=" + libc.getppid());
}
}
The code compiles correctly but refuses to run,
(compilation step)
javac -cp /usr/share/java/jnr-ffi.jar:. Getpid.java
(running step)
java -cp /usr/share/java/jnr-ffi.jar:. Getpid
While running I get this error.
You need to install objective-web's asm.jar
and jjfi.jar
add those to your classpath and voila!
Compile with this:
javac -cp /usr/share/java/jnr-ffi.jar:.:/usr/lib/java/jffi.jar:/usr/lib/java/jffi-native.jar:/usr/share/java/objectweb-asm/asm.jar Getpid.java
and run with this:
java -cp /usr/share/java/jnr-ffi.jar:.:/usr/lib/java/jffi.jar:/usr/lib/java/jffi-native.jar:/usr/share/java/objectweb-asm/asm.jar Getpid

Can different Java applications use the same javaagent?

I hava a javaagent Jar simpleAgent.jar. I used it to redifine classes in it and I cached some classes to avoid redifine
public class Premain {
private static Instrumentation instrumentation;
private static final Map<String, Class> allLoadClassesMap = new ConcurrentHashMap<>();
public static void premain(String agentArgs, Instrumentation inst) {
instrumentation = inst;
cacheAllLoadedClasses("com.example");
}
public static void cacheAllLoadedClasses(String prfixName) {
try {
Class[] allLoadClasses = instrumentation.getAllLoadedClasses();
for (Class loadedClass : allLoadClasses) {
if (loadedClass.getName().startsWith(prfixName)) {
allLoadClassesMap.put(loadedClass.getName(), loadedClass);
}
}
logger.warn("Loaded Class Count " + allLoadClassesMap.size());
} catch (Exception e) {
logger.error("", e);
}
}
}
I have three different application app1.jar, app2.jar, app3.jar, so when I start the three application can I use the same agent jar? Eg.:
java -javaagent:simpleAgent.jar -jar app1.jar
java -javaagent:simpleAgent.jar -jar app2.jar
java -javaagent:simpleAgent.jar -jar app3.jar
I don't know the javaagent's implementation, so I was scared that using the same javaagent can trigger in app1 or app2 or app3 crash.
Each JVM instance is separate and does not "know" about other JVMs unless you do something in application level. So, generally the answer is "yes, you can use the same jar either javaagent or not for as many JVM instances as you want."
A Javaaget is treated by the VM similarly to jar files on the class path. Those files are read only, all state is contained in the running VM such that they are safely shared among multiple processes.

How to call java from python using PY4J

I want to call java from python with Py4J library,
from py4j.java_gateway import JavaGateway
gateway = JavaGateway() # connect to the JVM
gateway.jvm.java.lang.System.out.println('Hello World!')
I've got the following error: "Py4JNetworkError: An error occurred while trying to connect to the Java server". It's seems that no JVM is running, how to fix that?
Minimal working example:
//AdditionApplication.java
import py4j.GatewayServer;
public class AdditionApplication {
public static void main(String[] args) {
AdditionApplication app = new AdditionApplication();
// app is now the gateway.entry_point
GatewayServer server = new GatewayServer(app);
server.start();
}
}
Compile (make sure that the -cp path to the py4j is valid, otherwise adjust it such that it points to the right place):
javac -cp /usr/local/share/py4j/py4j0.9.jar AdditionApplication.java
Run it:
java -cp .:/usr/local/share/py4j/py4j0.9.jar AdditionApplication
Now, if you run your python script, in the terminal where the java AdditionApplication is running you should see something like:
>>> Hello World!
package test.test;
import py4j.GatewayServer;
public class AdditionApplication {
public int addition(int first, int second) {
return first + second;
}
public static void main(String[] args) {
AdditionApplication app = new AdditionApplication();
// app is now the gateway.entry_point
GatewayServer server = new GatewayServer(app);
server.start();
}
}
create a new class and run it(import py4j0.8.jar at 'py4j-0.8\py4j-0.8\py4j-java' first),then run python program
You should first start the java program, and then invoke java method from python.
py4j doesn't start jvm, it just connects to the already started java process.

Do I need all classes on the client, server and registry for RMI to work?

I'm doing my first steps with RMI, and I have a simple question.
I have a .jar file which has the implementation of several methods from a library.
I want to call this methods in the .jar file using RMI.
What I'm trying is to create a kind of a wrapper to do it.
So, I'm working on something like this:
Interface class: This interface has the methods to be implemented by the remote object.
Implementation class: This class, has the implementation of the interface methods, each implementation calls the corresponding method in the .jar file. E.g., the jar file has a method called getDetails(), and it returns a "ResponseDetail" object. ResponseDetail is a response class I have in the .jar.
Server class: it binds the methods to the rmiregistry
Client class: it will consume the methods implemented in implementation.
So far so good? :)
Now, I have a lib folder where resides the .jar file.
In the server machine I have deployed the Interface, Implementation and Server classes. I've generated the stub, and I ran the rmiregistry successfully, but, with these details:
To start the rmiregistry, I had to set the classpath in the command line to reference the .jar files, otherwise I get the java.lang.NoClassDefFoundError. I did it with this .sh file:
THE_CLASSPATH=
for i in `ls ./lib/*.jar`
do
THE_CLASSPATH=${THE_CLASSPATH}:${i}
done
rmiregistry -J-classpath -J".:${THE_CLASSPATH}"
To start the server, I had to set the classpath as well to reference the .jar files, otherwise, I get the java.lang.NoClassDefFoundError. I've used something like this:
THE_CLASSPATH=
for i in `ls ./lib/*.jar` do
THE_CLASSPATH=${THE_CLASSPATH}:${i}
done
java -classpath ".:${THE_CLASSPATH}" Server
Client machine:
To run the Client.class file from the client machine, I had to copy the .jar files to it, and make reference to them in the classpath, because otherwise, it does not run and I get the java.lang.NoClassDefFoundError. I had to use this on the client machine:
THE_CLASSPATH=
for i in `ls ./lib/*.jar`
do
THE_CLASSPATH=${THE_CLASSPATH}:${i}
done
java -classpath ".:${THE_CLASSPATH}" HelloClient
Is this ok? I mean, do I have to copy the .jar files to the client machine to execute methods through RMI?
Prior to JDK v5 one had to generate the RMI stubc using the rmic (RMI Compiler). This is done automatically from JDK v5 on. Moreover, you can start the RMI Registry from within the Java code as well. To start with a simple RMI application you may want to follow the following steps:
Create the interface:
import java.rmi.*;
public interface SomeInterface extends Remote {
public String someMethod1() throws RemoteException;
public int someMethod2(float someParameter) throws RemoteException;
public SomeStruct someStructTest(SomeStruct someStruct) throws RemoteException;
}
Implement the interface:
import java.rmi.*;
import java.rmi.server.*;
public class SomeImpl extends UnicastRemoteObject implements SomeInterface {
public SomeImpl() throws RemoteException {
super();
}
public String someMethod1() throws RemoteException {
return "Hello World!";
}
public int someMethod2( float f ) throws RemoteException {
return (int)f + 1;
}
public SomeStruct someStructTest(SomeStruct someStruct) throws RemoteException {
int i = someStruct.getInt();
float f = someStruct.getFloat();
someStruct.setInt(i + 1);
someStruct.setFloat(f + 1.0F);
return someStruct;
}
}
Implement a non-primitive serializable object that is to be passed between a client and the server:
import java.io.*;
public class SomeStruct implements Serializable {
private int i = 0;
private float f = 0.0F;
public SomeStruct(int i, float f) {
this.i = i;
this.f = f;
}
public int getInt() {
return i;
}
public float getFloat() {
return f;
}
public void setInt(int i) {
this.i = i;
}
public void setFloat(float f) {
this.f = f;
}
}
Implement the server:
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
import java.net.*;
import java.io.*;
public class SomeServer {
public static void main(String args[]) {
String portNum = "1234", registryURL;
try{
SomeImpl exportedObj = new SomeImpl();
startRegistry( Integer.parseInt(portNum) );
// register the object under the name "some"
registryURL = "rmi://localhost:" + portNum + "/some";
Naming.rebind(registryURL, exportedObj);
System.out.println("Some Server ready.");
} catch (Exception re) {
System.out.println("Exception in SomeServer.main: " + re);
}
}
// This method starts a RMI registry on the local host, if it
// does not already exist at the specified port number.
private static void startRegistry(int rmiPortNum) throws RemoteException{
try {
Registry registry = LocateRegistry.getRegistry(rmiPortNum);
registry.list( );
// The above call will throw an exception
// if the registry does not already exist
} catch (RemoteException ex) {
// No valid registry at that port.
System.out.println("RMI registry is not located at port " + rmiPortNum);
Registry registry = LocateRegistry.createRegistry(rmiPortNum);
System.out.println("RMI registry created at port " + rmiPortNum);
}
}
}
Implement the client:
import java.io.*;
import java.rmi.*;
import java.rmi.registry.Registry;
import java.rmi.registry.LocateRegistry;
public class SomeClient {
public static void main(String args[]) {
try {
String hostName;
String portNum = "1234";
String registryURL = "rmi://localhost:" + portNum + "/some";
SomeInterface h = (SomeInterface)Naming.lookup(registryURL);
// invoke the remote method(s)
String message = h.someMethod1();
System.out.println(message);
int i = h.someMethod2(12344);
System.out.println(i);
SomeStruct someStructOut = new SomeStruct(10, 100.0F);
SomeStruct someStructIn = new SomeStruct(0, 0.0F);
someStructIn = h.someStructTest(someStructOut);
System.out.println( someStructIn.getInt() );
System.out.println( someStructIn.getFloat() );
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
A larger client-server application should be divided into three modules:client, server, and common (for classes shared between the server and client code, i.e. the remote interface and the non-primitive object in this example). The client application will then be created from client + common modules on the classpath and the server from server + common modules on the classpath.
I used this example many years ago to learn basics of RMI and it still works. However it is far from being perfect (default Java package used, incorrect exception handling, hostname and port parameters are hard-coded and not configurable, etc.)
Nevertheless, it is good for starters. All the files can be placed in one directory and compiled using the simple javac *.java command. The server application can then be started using the java SomeServer and the client one by launching the java SomeClient command.
I hope this helps to understand the Java RMI which is, in fact, far more complicated than just this.
You shouldn't be generating stubs (if you are following a tutorial, it is way old). you can run the client without necessarily having the jars locally (using remote classloading), but it's way easier to do it this with the jars available locally (i've personally done a fair bit of RMI and never actually deployed a system with remote classloading). typically, you want 2 jars, a "client" jar with just the remote interfaces (and any Serializable classes used by those interfaces) and a "server" jar which includes the implementation classes. you would then run the server with the server jar, and the rmiregistry/client with the client jars.
This is a pretty good (up to date and simple) getting started guide.
To say it in short what the other answers elaborated:
The client needs only the common interfaces (and the client classes), not the server implementation.
The server needs interfaces and implementation (and your server main class).
The rmiregistry needs only the interfaces.
(Actually, you can start your own registry inside the server process - then you don't need the rmiregistry at all. Have a look at the createRegistry methods in the java.rmi.registry.LocateRegistry class.)
"Interfaces" here means both the remote interfaces and any (serializable) classes used by these as parameter or argument types.
How you distribute these classes to jar files is independent of this.

Categories