Skip to content

Commit

Permalink
Merge pull request #464 from grgrzybek/jdk11-vm-attach-api
Browse files Browse the repository at this point in the history
[#394] Provide non-reflective access to JVM Attach API on JDK9+
  • Loading branch information
rhuss committed Jul 18, 2021
2 parents b978152 + e04e6d1 commit b0f17ce
Show file tree
Hide file tree
Showing 21 changed files with 516 additions and 171 deletions.
15 changes: 15 additions & 0 deletions agent/jmx/src/main/java/org/jolokia/jmx/JolokiaMBeanServer.java
Expand Up @@ -108,6 +108,21 @@ private JsonMBean extractJsonMBeanAnnotation(Object object) {
}
} catch (IllegalAccessException e) {
// Ignored silently, but we tried it at least
} catch (RuntimeException e) {
// See https://openjdk.java.net/jeps/403
// On JDK9-JDK15 (with --illegal-access=deny) or on JDK-16+ (by default),
// java.lang.reflect.InaccessibleObjectException is thrown.
// --illegal-access=permit|warn|debug always ends with at least one WARNING message. The only
// way to handle the reflection gently is by using:
// --illegal-access=deny \
// --add-opens java.management/javax.management.modelmbean=ALL-UNNAMED
// without deny, we can't detect up front (using JDK8 API) if we can safely call setAccessible()...
if (e.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) {
// ignore
isAccessible = null;
} else {
throw e;
}
} finally {
if (isAccessible != null) {
field.setAccessible(isAccessible);
Expand Down
4 changes: 2 additions & 2 deletions agent/jvm/pom.xml
Expand Up @@ -105,7 +105,6 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
Expand Down Expand Up @@ -153,7 +152,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkMode>always</forkMode>
<forkCount>1</forkCount>
<reuseForks>false</reuseForks>
<!--<argLine>-Djava.security.debug=certpath,Provider</argLine>-->
</configuration>
</plugin>
Expand Down
Expand Up @@ -50,7 +50,7 @@ public static void main(String... args) {
OptionsAndArgs options;
try {
options = new OptionsAndArgs(CommandDispatcher.getAvailableCommands(),args);
VirtualMachineHandler vmHandler = new VirtualMachineHandler(options);
VirtualMachineHandlerOperations vmHandler = PlatformUtils.createVMAccess(options);
CommandDispatcher dispatcher = new CommandDispatcher(options);

// Attach a VirtualMachine to a given PID (if PID is given)
Expand Down
Expand Up @@ -17,12 +17,11 @@
*/

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

import org.jolokia.jvmagent.JvmAgent;
import org.jolokia.jvmagent.client.util.OptionsAndArgs;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;
import org.jolokia.jvmagent.client.util.VirtualMachineHandlerOperations;

/**
* Stateless Base command providing helper functions
Expand Down Expand Up @@ -51,25 +50,24 @@ public abstract class AbstractBaseCommand {
* @throws InvocationTargetException exception occured during startup of the agent. You probably need to examine
* the stdout of the instrumented process as well for error messages.
*/
abstract int execute(OptionsAndArgs pOpts, Object pVm,VirtualMachineHandler pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException;
abstract int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandlerOperations pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException;

// =======================================================================================================

/**
* Execute {@link com.sun.tools.attach.VirtualMachine#loadAgent(String, String)} via reflection
* Execute {@code com.sun.tools.attach.VirtualMachine#loadAgent(String, String)}
*
* @param pVm the VirtualMachine object, typeless
* @param pHandler platform-specific way to invoke operations on VM
* @param pOpts options from where to extract the agent path and options
* @param pAdditionalOpts optional additional options to be appended to the agent options. Must be a CSV string.
*/
protected void loadAgent(Object pVm, OptionsAndArgs pOpts,String ... pAdditionalOpts) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = pVm.getClass();
Method method = clazz.getMethod("loadAgent",String.class, String.class);
protected void loadAgent(Object pVm, VirtualMachineHandlerOperations pHandler, OptionsAndArgs pOpts, String ... pAdditionalOpts) {
String args = pOpts.toAgentArg();
if (pAdditionalOpts.length > 0) {
args = args.length() != 0 ? args + "," + pAdditionalOpts[0] : pAdditionalOpts[0];
}
method.invoke(pVm, pOpts.getJarFilePath(),args.length() > 0 ? args : null);
pHandler.loadAgent(pVm, pOpts.getJarFilePath(), args.length() > 0 ? args : null);
}

/**
Expand All @@ -78,10 +76,11 @@ protected void loadAgent(Object pVm, OptionsAndArgs pOpts,String ... pAdditional
* has been already attached and started. ("start" will set this property, "stop" will remove it).
*
* @param pVm the {@link com.sun.tools.attach.VirtualMachine}, but typeless
* @param pHandler platform-specific way to invoke operations on VM
* @return the agent URL if it is was set by a previous 'start' command.
*/
protected String checkAgentUrl(Object pVm) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
return checkAgentUrl(pVm, 0);
protected String checkAgentUrl(Object pVm, VirtualMachineHandlerOperations pHandler) {
return checkAgentUrl(pVm, pHandler, 0);
}

/**
Expand All @@ -90,58 +89,50 @@ protected String checkAgentUrl(Object pVm) throws NoSuchMethodException, Invocat
* has been already attached and started. ("start" will set this property, "stop" will remove it).
*
* @param pVm the {@link com.sun.tools.attach.VirtualMachine}, but typeless
* @param pHandler platform-specific way to invoke operations on VM
* @param delayInMs wait that many ms before fetching the properties
** @return the agent URL if it is was set by a previous 'start' command.
*/
protected String checkAgentUrl(Object pVm, int delayInMs) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
protected String checkAgentUrl(Object pVm, VirtualMachineHandlerOperations pHandler, int delayInMs) {
if (delayInMs != 0) {
try {
Thread.sleep(delayInMs);
} catch (InterruptedException e) {
// just continue
}
}
Properties systemProperties = getAgentSystemProperties(pVm);
Properties systemProperties = getAgentSystemProperties(pVm, pHandler);
return systemProperties.getProperty(JvmAgent.JOLOKIA_AGENT_URL);
}

/**
* Execute {@link com.sun.tools.attach.VirtualMachine#getSystemProperties()} via reflection
* @param pVm the VirtualMachine object, typeless
* @param pHandler platform-specific way to invoke operations on VM
* @return the system properties
*/
protected Properties getAgentSystemProperties(Object pVm) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class clazz = pVm.getClass();
Method method = clazz.getMethod("getSystemProperties");
return (Properties) method.invoke(pVm);
protected Properties getAgentSystemProperties(Object pVm, VirtualMachineHandlerOperations pHandler) {
return pHandler.getSystemProperties(pVm);
}

/**
* Get a description of the process attached, either the numeric id only or, if a pattern is given,
* the pattern and the associated PID
*
* @param pOpts options from where to take the PID or pattern
* @param pHandler handler for looking up the process in case of a pattern lookup
* @param pOpts options from where to take the PID or pattern
* @return a description of the process
*/
protected String getProcessDescription(OptionsAndArgs pOpts, VirtualMachineHandler pHandler) {
protected String getProcessDescription(VirtualMachineHandlerOperations pHandler, OptionsAndArgs pOpts) {
if (pOpts.getPid() != null) {
return "PID " + pOpts.getPid();
} else if (pOpts.getProcessPattern() != null) {
StringBuffer desc = new StringBuffer("process matching \"")
.append(pOpts.getProcessPattern().pattern())
.append("\"");
try {
desc.append(" (PID: ")
.append(pHandler.findProcess(pOpts.getProcessPattern()).getId())
.append(")");
} catch (InvocationTargetException e) {
// ignored
} catch (NoSuchMethodException e) {
// ignored
} catch (IllegalAccessException e) {
// ignored
}
desc.append(" (PID: ")
.append(pHandler.findProcess(pOpts.getProcessPattern()).getId())
.append(")");
return desc.toString();
} else {
return "(null)";
Expand Down
Expand Up @@ -20,7 +20,7 @@
import java.util.*;

import org.jolokia.jvmagent.client.util.OptionsAndArgs;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;
import org.jolokia.jvmagent.client.util.VirtualMachineHandlerOperations;

/**
* Dispatch for various attach commands
Expand Down Expand Up @@ -69,7 +69,7 @@ public CommandDispatcher(OptionsAndArgs pOptions) {
* @param pHandler handler for listing processes
* @return the return code (0 or 1)
*/
public int dispatchCommand(Object pVm,VirtualMachineHandler pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
public int dispatchCommand(Object pVm, VirtualMachineHandlerOperations pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
String commandName = options.getCommand();
AbstractBaseCommand command = COMMANDS.get(commandName);
if (command == null) {
Expand Down
@@ -1,7 +1,7 @@
package org.jolokia.jvmagent.client.command;

import org.jolokia.jvmagent.client.util.OptionsAndArgs;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;
import org.jolokia.jvmagent.client.util.VirtualMachineHandlerOperations;
import org.jolokia.util.*;

import java.io.*;
Expand Down Expand Up @@ -34,8 +34,8 @@ String getName() {
}

@Override
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandler pHandler)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandlerOperations pHandler)
throws InvocationTargetException {
try {
List<String> args = pOpts.getExtraArgs();
String password = args.size() == 0 ?
Expand Down
Expand Up @@ -21,7 +21,7 @@
import org.jolokia.Version;
import org.jolokia.config.ConfigKey;
import org.jolokia.jvmagent.client.util.OptionsAndArgs;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;
import org.jolokia.jvmagent.client.util.VirtualMachineHandlerOperations;

/**
* Print out usage information
Expand All @@ -40,7 +40,7 @@ String getName() {

/** {@inheritDoc} */
@Override
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandler pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandlerOperations pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
printUsage();
return 0;
}
Expand Down
Expand Up @@ -21,7 +21,6 @@
import java.util.List;

import org.jolokia.jvmagent.client.util.*;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;

/**
* List all available Java processes
Expand All @@ -40,7 +39,7 @@ String getName() {
/** {@inheritDoc} */
@Override
@SuppressWarnings("PMD.SystemPrintln")
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandler pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandlerOperations pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
List<ProcessDescription> vmDescriptors = pHandler.listProcesses();
for (ProcessDescription descriptor : vmDescriptors) {
Formatter formatter = new Formatter().format("%7.7s %-100.100s",
Expand Down
Expand Up @@ -16,10 +16,8 @@
* limitations under the License.
*/

import java.lang.reflect.InvocationTargetException;

import org.jolokia.jvmagent.client.util.OptionsAndArgs;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;
import org.jolokia.jvmagent.client.util.VirtualMachineHandlerOperations;

/**
* Load a Jolokia Agent and start it. Whether an agent is started is decided by the existence of the
Expand All @@ -30,41 +28,40 @@
*/
public class StartCommand extends AbstractBaseCommand {

@Override
/** {@inheritDoc} */
@Override
String getName() {
return "start";
}

/** {@inheritDoc} */
@Override
@SuppressWarnings("PMD.SystemPrintln")
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandler pHandler)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandlerOperations pHandler) {
String agentUrl;
agentUrl = checkAgentUrl(pVm);
agentUrl = checkAgentUrl(pVm, pHandler);
boolean quiet = pOpts.isQuiet();
if (agentUrl == null) {
loadAgent(pVm, pOpts);
agentUrl = checkAgentUrl(pVm);
loadAgent(pVm, pHandler, pOpts);
agentUrl = checkAgentUrl(pVm, pHandler);
if (agentUrl == null) {
// Wait a bit and try again
agentUrl = checkAgentUrl(pVm, 500);
agentUrl = checkAgentUrl(pVm, pHandler, 500);
if (agentUrl == null) {
System.err.println("Couldn't start agent for " + getProcessDescription(pOpts, pHandler));
System.err.println("Couldn't start agent for " + getProcessDescription(pHandler, pOpts));
System.err.println("Possible reason could be that port '" + pOpts.getPort() + "' is already occupied.");
System.err.println("Please check the standard output of the target process for a detailed error message.");
return 1;
}
}
if (!quiet) {
System.out.println("Started Jolokia for " + getProcessDescription(pOpts,pHandler));
System.out.println("Started Jolokia for " + getProcessDescription(pHandler, pOpts));
System.out.println(agentUrl);
}
return 0;
} else {
if (!quiet) {
System.out.println("Jolokia is already attached to " + getProcessDescription(pOpts,pHandler));
System.out.println("Jolokia is already attached to " + getProcessDescription(pHandler, pOpts));
System.out.println(agentUrl);
}
return 1;
Expand Down
Expand Up @@ -19,7 +19,7 @@
import java.lang.reflect.InvocationTargetException;

import org.jolokia.jvmagent.client.util.OptionsAndArgs;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;
import org.jolokia.jvmagent.client.util.VirtualMachineHandlerOperations;

/**
* Check the status of an agent on the target process. Prints out the information
Expand All @@ -39,23 +39,24 @@ String getName() {
/**
* Checkt the status and print it out (except for <code>--quiet</code>
* @param pVm the virtual machine
* @param pHandler platform-specific way to invoke operations on VM
* @return the exit code (0: agent is attached, 1: agent is not attached.)
*/
@Override
@SuppressWarnings("PMD.SystemPrintln")
int execute(OptionsAndArgs pOptions, Object pVm, VirtualMachineHandler pHandler)
int execute(OptionsAndArgs pOptions, Object pVm, VirtualMachineHandlerOperations pHandler)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
String agentUrl = checkAgentUrl(pVm);
String agentUrl = checkAgentUrl(pVm, pHandler);
boolean quiet = pOptions.isQuiet();
if (agentUrl != null) {
if (!quiet) {
System.out.println("Jolokia started for " + getProcessDescription(pOptions,pHandler));
System.out.println("Jolokia started for " + getProcessDescription(pHandler, pOptions));
System.out.println(agentUrl);
}
return 0;
} else {
if (!quiet) {
System.out.println("No Jolokia agent attached to " + getProcessDescription(pOptions,pHandler));
System.out.println("No Jolokia agent attached to " + getProcessDescription(pHandler, pOptions));
}
return 1;
}
Expand Down
Expand Up @@ -19,7 +19,7 @@
import java.lang.reflect.InvocationTargetException;

import org.jolokia.jvmagent.client.util.OptionsAndArgs;
import org.jolokia.jvmagent.client.util.VirtualMachineHandler;
import org.jolokia.jvmagent.client.util.VirtualMachineHandlerOperations;

/**
* Stop a Jolokia Agent, but only if it is already running (started with 'start').
Expand All @@ -40,18 +40,18 @@ String getName() {
/** {@inheritDoc} */
@Override
@SuppressWarnings("PMD.SystemPrintln")
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandler pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
String agentUrl = checkAgentUrl(pVm);
int execute(OptionsAndArgs pOpts, Object pVm, VirtualMachineHandlerOperations pHandler) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
String agentUrl = checkAgentUrl(pVm, pHandler);
boolean quiet = pOpts.isQuiet();
if (agentUrl != null) {
loadAgent(pVm,pOpts,"mode=stop");
loadAgent(pVm, pHandler, pOpts, "mode=stop");
if (!quiet) {
System.out.println("Stopped Jolokia for " + getProcessDescription(pOpts,pHandler));
System.out.println("Stopped Jolokia for " + getProcessDescription(pHandler, pOpts));
}
return 0;
} else {
if (!quiet) {
System.out.println("Jolokia is not attached to " + getProcessDescription(pOpts,pHandler));
System.out.println("Jolokia is not attached to " + getProcessDescription(pHandler, pOpts));
}
return 1;
}
Expand Down

0 comments on commit b0f17ce

Please sign in to comment.