24

I am currently starting my Java VM with the com.sun.management.jmxremote.* properties so that I can connect to it via JConsole for management and monitoring. Unfortunately, it listens on all interfaces (IP addresses) on the machine.

In our environment, there are often cases where there is more than one Java VM running on a machine at the same time. While it's possible to tell JMX to listen on different TCP ports (using com.sun.management.jmxremote.port), it would be nice to instead have JMX use the standard JMX port and just bind to a specific IP address (rather than all of them).

This would make it much easier to figure out which VM we're connecting to via JConsole (since each VM effectively "owns" its own IP address). Has anyone figured out how to make JMX listen on a single IP address or hostname?

Marc Novakowski
  • 44,628
  • 11
  • 58
  • 63

7 Answers7

33

If anyone else will be losing his nerves with this ... After 10 years, they finally fixed it!

Since Java 8u102 -Dcom.sun.management.jmxremote.host binds to the selected IP

see: https://bugs.openjdk.java.net/browse/JDK-6425769

Gašper
  • 815
  • 8
  • 14
  • It should be noted that at least on openjdk 1.8.0_312 you also need to disable SSL with`-Dcom.sun.management.jmxremote.ssl=false` if you want to bind to a specific interface otherwise `-Dcom.sun.management.jmxremote.host=127.0.0.1` is ignored. – RubenLaguna Feb 28 '22 at 16:28
8

Fernando already provided a link to my blog post :) ..it's not trivial. You have to provide your own RMIServerSocketFactoryImpl that creates sockets on the wanted address.

If internal/external interfaces are the problem and you have local access setting up a local firewall might be easier.

tcurdt
  • 14,518
  • 10
  • 57
  • 72
0

I haven't tried this but this may help.

the main nuisance here was that there is no easy way to specify the host IP address for JMX to bind to, it would always bind to all interfaces. The 'java.rmi.server.hostname' property did not work and I didn't want to pick different ports for all the different instances on the same host.

Also, I didn't want to create my own RMIServerSocketFactory with all the complexities associated with it, I was after a simple patch to the existing code.

I've fixed this by patching the default JVM RMI socket factory that is responsible for creating this server socket. It now supports the new 'com.sun.management.jmxremote.host' property.

To get this to work, save the Java code below into a file named sun/rmi/transport/proxy/RMIDirectSocketFactory.java.

Compile and create jmx_patch.jar from it and place it into the tomcat lib/ folder.

You then need to add the following line to bin/setenv.sh:

CLASSPATH=$CLASSPATH:$CATALINA_HOME/lib/mx_patch.jar

add this option in tomcat instance start up

-Dcom.sun.management.jmxremote.host=192.168.100.100"

This will then bind the JMX service only to address 192.168.100.100. The 2 other random RMI listening ports will still bind to all interfaces, but that is fine as they always pick a free port anyway.

You can now run multiple tomcat instances on a single host with all the default ports intact (e.g. 8080 for JMX for all of them).

package sun.rmi.transport.proxy;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.server.RMISocketFactory;

public class RMIDirectSocketFactory extends RMISocketFactory {

    public Socket createSocket(String host, int port) throws IOException
    {
     return new Socket(host, port);
    }

   public ServerSocket createServerSocket(int port) throws IOException
   {
   String jmx_host = System.getProperty("com.sun.management.jmxremote.host");
   String jmx_port = System.getProperty("com.sun.management.jmxremote.port");

  // Allow JMX to bind to specific address
  if (jmx_host != null && jmx_port != null && port != 0 && integer.toString(port).equals(jmx_port)) {
    InetAddress[] inetAddresses = InetAddress.getAllByName(jmx_host);
    if (inetAddresses.length > 0) {
    return new ServerSocket(port, 50, inetAddresses[0]);
   }
}

 return new ServerSocket(port);
  }

}

Niranjan
  • 137
  • 3
0

When just using com.sun.management.jmxremote.host without changing com.sun.management.jmxremote.ssl (default is true) or com.sun.management.jmxremote.registry.ssl (default is false) it will still bind to all interfaces for com.sun.management.jmxremote.port, and will use com.sun.management.jmxremote.host only for com.sun.management.jmxremote.rmi.port (which defaults to random port). Not sure why it is the case, it look like a bug. For any other combination of these jmxremote.ssl and jmxremote.registry.ssl values, it will properly use the given jmxremote.host for both ports.

For example, with

  -Dcom.sun.management.jmxremote.authenticate=false
  -Dcom.sun.management.jmxremote.port=1234
  -Dcom.sun.management.jmxremote.host=interface1
  

it will still bind to all interfaces for the port 1234, and bind to interface1 only for the random jmxremote.rmi.port.

Note that a JMX client first connects to jmxremote.port, which is a RMI registry, over which it receives the actual JMX server port jmxremote.rmi.port, so the client would still be able to talk to the actual JMX server only over the jmxremote.host interface. However it is still undesirable for the RMI registry to listen on all interfaces, since it will lead to port conflicts when different VMs are started with the same jmxremote.port value, as is the scenario in this question.

Ivan
  • 1,552
  • 1
  • 16
  • 25
0

You will need to set at least com.sun.management.jmxremote.{host,port,ssl}, although com.sun.management.jmxremote.host is not documented it does work at least in the OpenJDK 1.8.0_312.

You will (most likely) need to set also java.rmi.server.hostname. Remember that the JMX client first connect to the jmxremot host and port, then gets an RMI host and port from there, and finally will connect via RMI to that host and port, if you don't set the java.rmi.server.hostname the host (the ip) that the jmx client will obtain will not have any listener for that port (since you are only listening at 127.0.0.1)

Here is an example that works, not that I also disabled authentication and forced IPv4:

/usr/lib/jvm/java-8-openjdk-amd64//bin/java \
-Dcom.sun.management.jmxremote.host=127.0.0.1  \
-Djava.rmi.server.hostname=127.0.0.1 \
-Dcom.sun.management.jmxremote.port=2222 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.registry.ssl=false  \
-Dcom.sun.management.jmxremote.authenticate=false \
-Djava.net.preferIPv4Stack=true \
-jar your-uber.jar
  

The output from netstat confirms that port 222 is bound to 127.0.0.1 only and not to 0.0.0.0:

netstat -plnt|grep 2222
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:2222          0.0.0.0:*               LISTEN      15390/java
RubenLaguna
  • 21,435
  • 13
  • 113
  • 151
-2

I just tried

-Dcom.sun.management.jmxremote.host=

with openjdk 1.8, and it works well. It binds to that addess (according to netstat) and all looks right (and works).

-3

The accepted answer is pretty old. There are some indications that Java now provides some options to enable this. For instance I have seen:

-Djava.rmi.server.hostname=<YOUR_IP>

...as well as...

-Dcom.sun.management.jmxremote.host=<YOUR_IP>

However, at least on my system under jdk 1.7, these do not seem to have any effect - the JMX connector still binds to *. An updated answer (with specific applicable versions) would be much appreciated. This should be simple.

sosiouxme
  • 1,226
  • 16
  • 26
  • Neither of these controls the bind-address. `java.rmi.server.hostname` is not new, it's been there for 17 years,l and I doubt the other is new either. – user207421 Sep 16 '14 at 00:46
  • I didn't say they were new. I said I had seen them and they didn't work. – sosiouxme Sep 16 '14 at 16:41
  • I was hoping there was some way that actually does work without being horrible. Haven't found any though. – sosiouxme Sep 16 '14 at 16:55
  • You said 'Java *now* provides'. Java has always provided java.rmi.server.hostname. They do work. They just don't do what you thought they did. You also said 'there are some indications' that these do what you thought, when there aren't. It's hard to see the point of this answer. – user207421 Sep 16 '14 at 21:22