How do I attach VisualVM to a simple Java process running in a Docker container
Asked Answered
K

7

109

Actually I wanted a solution working for JEE containers, specifically for Glassfish, but after I tried many combinations of settings and did not succeed, I reduced the setup to the simplest possible case.

Here is my Hello World daemon started in a Docker container. I want to attach jconsole or VisulaVM to it. Everything is on the same machine.

public class Main {
  public static void main(String[] args) {
    while (true) {
      try {
        Thread.sleep(3000);
        System.out.println("Hello, World");
      } catch (InterruptedException e) {
        break;
      }
    }
  }
}

Dockerfile

FROM java:8
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
CMD ["java", "Main"]

Building: docker build -t hello-world-daemon .

Running: docker run -it --rm --name hwd hello-world-daemon

Questions:

  • what JVM parameters should be added to CMD command line?
  • what ports should be exposed and published?
  • what network mode should Docker container be using?

I do not show my failed attempts here so that correct answers will not be biased. This should be a pretty common problem, yet I could not find a working solution.

Update. Worked solution

This Dockerfile works

FROM java:8
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
CMD ["java", \
"-Dcom.sun.management.jmxremote", \
"-Dcom.sun.management.jmxremote.port=9010", \
"-Dcom.sun.management.jmxremote.local.only=false", \
"-Dcom.sun.management.jmxremote.authenticate=false", \
"-Dcom.sun.management.jmxremote.ssl=false", "Main"]
EXPOSE 9010

in combination with the docker run command

docker run -it --rm --name hwd -p 9010:9010 hello-world-daemon

VisualVM connects via right click Local->Add JMX Connection, and then entering localhost:9010, or through adding a remote host.

JConsole connects via selecting a Remote process with localhost:9010.

When defining the connection as remote, any interface listed by ifconfig can be used. For instance, docker0 interface with address 172.17.0.1 works. The container's address 172.17.0.2 works too.

Kaden answered 31/1, 2016 at 0:20 Comment(0)
L
68

At first you should run you application with these JVM params:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.local.only=false
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false

Then you should expose port for docker:

EXPOSE 9010

Also specify port binding with docker run command:

docker run -p 9010:9010 -it --rm --name hwd hello-world-daemon

After that you can connect with Jconsole to local 9010 port and manage application run in Docker.

Lipchitz answered 31/1, 2016 at 0:37 Comment(14)
Nope.. VisualVM: Cannot connect to localhost:9010 using service:jmx:rmi:///jndi/rmi://localhost:9010/jmxrmi. Jconsole: Connection failed: error during JRMP connection establishment; nested exception is: java.net.SocketException: Connection resetKaden
why do you expose same port twice?Kaden
Connect not to localhost, to your network interface.Lipchitz
You expose docker port to port on your network interface.Lipchitz
To which interface? If I connect on docker0 interface 172.17.0.1, or on wlan1 interface 192.168.0.103, I get Connection faild: Connection refusedKaden
Understood. So, when run container specify this bind with -p 9010:9010 option and all will be fine.Lipchitz
Here's my command line docker run -it --rm --name hwd -p 9010:9010 hello-world-daemon The java process: root 4398 2673 0 03:09 pts/29 00:00:00 java Main -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false Does not workKaden
Add this option --bind 127.0.0.1 and try to connect to localhost.Lipchitz
I'm sorry, I don't understand. Docker run does not have such option.Kaden
It worked finally. My mistake was that I appended JVM options after the Main class name in the command line. All -D options were silently ignored by java.Kaden
Same applies when using -jar. For some reason the -Dcom.sun.management parameters must be passed before the -jar option. Weird in my eyes.Reticulum
@EthanLeroy any arguments after -jar foo.jar are sent to the main function of the main class (defined as Main-Class in the JAR manifest); basically, arguments before -jar are for the JVM, arguments after -jar are for the program that's being runBayless
Beware: both jmxremote.rmi.port AND jmxremote.port need to be set.Thereabout
I was also missing =true for -Dcom.sun.management.jmxremote as suggests this article.Faison
N
17

I followed an other SO response to a similar question and it worked.

I started my Java process inside the container by adding those JVM params:

-Dcom.sun.management.jmxremote.port=<port> \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.rmi.port=<port> \
-Djava.rmi.server.hostname=$HOST_HOSTNAME

and started the Docker container specifying -e HOST_HOSTNAME=$HOSTNAME -p <port> to the docker run command.

Then I've been able to access to this remote Java app from my local JVisualVm by adding a remote JMX connection ("File" > "Add a JMX Connection...") and specifying <dockerhostname>:<port> in the "Connection" input, and checking "Do not require SSL connection".

Nitroso answered 2/11, 2016 at 15:30 Comment(3)
What is $HOST_HOSTNAME exactly? Is it host of machine running docker or something else?Planimetry
Yes, it is the hostname of the host running docker. It can be the result of the hostname command, so you could pass it to docker while launching your container like this: -e HOST_HOSTNAME=`hostname`Nitroso
Thank you, -Djava.rmi.server.hostname was the missing part for meBomb
S
17

FWIW, this is how I was able to attach VisualVM to a Java process inside a Docker container running on macOS:

Main.java:

public class Main {
    public static void main(String args[]) throws Exception {
        while (true) {
            System.out.print("Hello ");
            System.out.println("world");
            Thread.sleep(1000);
        }
    }
}

Dockerfile:

FROM openjdk:11.0.2-slim
COPY Main.class /
WORKDIR /
ENTRYPOINT ["java", \
"-Dcom.sun.management.jmxremote=true", \
"-Dcom.sun.management.jmxremote.port=9010", \
"-Dcom.sun.management.jmxremote.local.only=false", \
"-Dcom.sun.management.jmxremote.authenticate=false", \
"-Dcom.sun.management.jmxremote.ssl=false", \
"-Dcom.sun.management.jmxremote.rmi.port=9010", \
"-Djava.rmi.server.hostname=localhost", \
"Main"]

Compile the Java code, build the image and run the container like this:

$ javac Main.java
$ docker build -t main .
$ docker run -p 9010:9010 -it main

Then attach VisualVM using JMX to localhost:9010

Singleness answered 17/12, 2019 at 20:36 Comment(2)
This answer worked perfectly for me, and giving the example Dockerfile made it easy.Cubital
thank you for the example of DockerFile,this example save my lifeTreen
I
7

You can also use docker-compose to set up your container. Steps:

Create your image (Dockerfile)

FROM openjdk:11
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp

Build your image

docker build -t app .

Create a tag

docker tag app:latest app:staging

Set up your docker-compose

app:
    image: app:staging
    ports:
      - 8050:8050
      - 8051:8051
    volumes:
      - ./target/app.jar:/usr/src/myapp/app.jar
    entrypoint:
      - java 
      - -Dspring.profiles.active=local 
      - -Dcom.sun.management.jmxremote=true
      - -Dcom.sun.management.jmxremote.port=8051
      - -Dcom.sun.management.jmxremote.local.only=false 
      - -Dcom.sun.management.jmxremote.authenticate=false
      - -Dcom.sun.management.jmxremote.ssl=false
      - -Dcom.sun.management.jmxremote.rmi.port=8051
      - -Djava.rmi.server.hostname=localhost
      - -jar 
      - ./app.jar

Port 8050 is the one I am using to run the JVM and the 8051 makes the remote connection. I have tested using VisualVM to see if I can connect to the JVM inside the container and it worked. You just need to Add a JMX connection:

Add Jmx connection

Then it will appear the process:

Jvm running inside docker

Ivanna answered 21/2, 2022 at 16:10 Comment(0)
E
4

As answered by Anthony. I had to use the -Djava.rmi.server.hostname java option on my Windows machine.

Just be sure not to use the CMD in JSON format in your Dockerfile as this doesn't support shell expansion.

Dockerfile example:

FROM java:8
COPY . /usr/src/myapp
WORKDIR /usr/src/myapp
RUN javac Main.java
#Do not use CMD in JSON format here because shell expansion doesn't work in JSON format
#Shell expansion is needed for the ${HOST} variable.
CMD java -Dcom.sun.management.jmxremote=true \
-Dcom.sun.management.jmxremote.rmi.port=9010 \
-Dcom.sun.management.jmxremote.port=9010 \
-Dcom.sun.management.jmxremote.ssl=false \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.local.only=false \
-Djava.rmi.server.hostname=${HOST} \
Main
Eisele answered 14/11, 2017 at 14:58 Comment(0)
G
3

Thanks to all of you for routing me to the right direction. Finally I got it working in more complex config: Kubernetes via Docker Desktop under Windows 10 on local machine.

My app's config:

-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.port=30491
-Dcom.sun.management.jmxremote.rmi.port=30491
-Djava.rmi.server.hostname=localhost

Pod's port:

ports:
- name: jmx
  containerPort: 30491
  protocol: TCP

Service's port:

ports:
- name: jmx
  nodePort: 30491
  port: 9010
  protocol: TCP
  targetPort: jmx
Government answered 13/5, 2020 at 20:41 Comment(0)
G
2

To all of you that still suffer from an error like the below:

enter image description here

In my case it was that i used in my Docker YML different port mappings for the ports:

e.g:

15100:9090

but apparently in your port bindings you must assign the SAME port for external port and internal port !

Reference: https://forums.docker.com/t/exposing-mapped-jmx-ports-from-multiple-containers/5287/5

Gustavo answered 12/12, 2019 at 9:48 Comment(1)
Have you found a workaround for this issue since Dec 2019? Or does it still persist?Burra

© 2022 - 2024 — McMap. All rights reserved.