2

I'm trying to figure out how to drop jcmd.exe on a windows server installed at a client site so that we can troubleshoot heap and thread issues. Don't really want to install the full JDK since it complicates the environment.

jcmd.exe definitely wants some components from the JDK to run, but I'm unable to determine which ones. If I can get it down to a small set that we unzip into a folder, use it to capture data, and then destroy, that would be perfect.

Anybody know what JDK components jcmd needs to run?

Tomas Hurka
  • 6,723
  • 29
  • 38
DaveyBob
  • 253
  • 1
  • 3
  • 8

4 Answers4

3

A quick examination of jcmd.exe reveals:

    ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7fff01820000)
    KERNEL32.DLL => /c/WINDOWS/system32/KERNEL32.DLL (0x7ffeff180000)
    KERNELBASE.dll => /c/WINDOWS/system32/KERNELBASE.dll (0x7ffefe810000)
    SYSFER.DLL => /c/WINDOWS/System32/SYSFER.DLL (0x54f10000)
    jli.dll => /c/apps/jdk1.8.0_121/bin/jli.dll (0x51ec0000)
    MSVCR100.dll => /c/apps/jdk1.8.0_121/bin/MSVCR100.dll (0x51c40000)
    ADVAPI32.dll => /c/WINDOWS/system32/ADVAPI32.dll (0x7ffefeeb0000)
    msvcrt.dll => /c/WINDOWS/system32/msvcrt.dll (0x7fff01720000)
    sechost.dll => /c/WINDOWS/system32/sechost.dll (0x7ffeff0c0000)
    RPCRT4.dll => /c/WINDOWS/system32/RPCRT4.dll (0x7ffefec20000)
    USER32.dll => /c/WINDOWS/system32/USER32.dll (0x7ffefef60000)
    COMCTL32.dll => /c/WINDOWS/WinSxS/amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.10586.672_none_a2d6b3cea53ff843/COMCTL32.dll (0x7ffef8460000)
    GDI32.dll => /c/WINDOWS/system32/GDI32.dll (0x7ffeff230000)
    combase.dll => /c/WINDOWS/system32/combase.dll (0x7ffeff3c0000)
    bcryptPrimitives.dll => /c/WINDOWS/system32/bcryptPrimitives.dll (0x7ffefe540000)

It therefore appears that msvcr100.dll and jli.dll would be the only requisite components from the JDK. A super quick test seems to indicate that these three files will suffice, but I will admit my test situation may not have been perfect.

EDIT: Upon further examination, here is the minimum configuration I found that works. It may be possible to modify the directory structure somewhat and set things such as CLASSPATH, JAVA_HOME, and PATH. I did not explore all of the permutations.

.:
bin/  COPYRIGHT*  jre/  lib/  LICENSE*

./bin:
jcmd.exe*  jli.dll*  msvcr100.dll*

./jre:
bin/  COPYRIGHT*  lib/  LICENSE*

./jre/bin:
attach.dll*  java.dll*  jli.dll*  net.dll*  nio.dll*  server/  unpack.dll*  
verify.dll*  zip.dll*

./jre/bin/server:
classes.jsa*  jvm.dll*  Xusage.txt*

./jre/lib:
amd64/  rt.jar*

./jre/lib/amd64:
jvm.cfg*

./lib:
jvm.lib*  tools.jar*

I also left the COPYRIGHT and LICENSE files as I felt they were important.

Test:

bin\jcmd.exe 16696 Thread.print
16696:
2017-04-27 18:01:49
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.121-b13 mixed mode):

"Worker-32" #84 prio=5 os_prio=0 tid=0x000000001bcaf800 nid=0x416c in 
Object.wait() [0x00000000335ef000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)
    - locked <0x00000000c21120a8> (a org.eclipse.core.internal.jobs.WorkerPool)
    at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)
    at org.eclipse.core.internal.jobs.Worker.run(Worker.java:52)

"Worker-31" #83 prio=5 os_prio=0 tid=0x000000001bcb7800 nid=0x315c in 
Object.wait() [0x00000000312ef000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    at org.eclipse.core.internal.jobs.WorkerPool.sleep(WorkerPool.java:188)
    - locked <0x00000000c21120a8> (a org.eclipse.core.internal.jobs.WorkerPool)
    at org.eclipse.core.internal.jobs.WorkerPool.startJob(WorkerPool.java:220)
    at org.eclipse.core.internal.jobs.Worker.run(Worker.java:52)

<snip>...


F:\tmp\t1>bin\jcmd 16696 VM.flags
16696:
-XX:CICompilerCount=4 -XX:InitialHeapSize=268435456 
-XX:MaxHeapSize=1073741824 -XX:MaxNewSize=357564416 
-XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
-XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation 
-XX:+UseParallelGC
Nemo
  • 2,441
  • 2
  • 29
  • 63
KevinO
  • 4,303
  • 4
  • 27
  • 36
  • Thanks KevinO...that got me further down the road, but not quite there. I put jcmd.exe, jli.dll, and MSVCR100.dll into a temp folder, modified JAVA_HOME to point to the jre, and ensured the JDK was _not_ in the system path. When I run jcmd, I get "Could not find or load main class sun.tools.jcmd.JCmd". So I dropped tools.jar in there (since that contains this class). Same error. So I set CLASSPATH variable to point here. Same error. Other ideas? – DaveyBob Apr 27 '17 at 12:51
  • @DaveyBob, please see the edits. I believe the minimum configuration is shown in the edits. – KevinO Apr 28 '17 at 15:21
  • Ooooh. Thanks a million KevinO! I'll give that a shot on my end. Hope some other folks find this useful! – DaveyBob Apr 28 '17 at 19:02
2

The jcmd tool is written in Java, so a JDK is needed.

https://github.com/openjdk/jdk/blob/master/src/jdk.jcmd/share/classes/sun/tools/jcmd/JCmd.java

From JDK 9 or later, you can use jlink to create a custom JDK image that only contains modules needed for the jcmd tool.

$ cd %JAVA_HOME%
$ bin\jlink --module-path jmods --add-modules jdk.jcmd --output c:\custom-jdk
$ cd c:\custom-jdk\bin
$ jcmd 

It's about 40 MB on my machine and it can operate against earlier releases. Not sure how much it helps, but it is the clean way to do it.

Kire Haglin
  • 6,569
  • 22
  • 27
1

Here is the smallest debian based docker image with jcmd ca 114MB

Dockerfile

FROM adoptopenjdk/openjdk11-openj9:slim as base
ENV    JCMD=/tmp/jcmd
RUN  jlink --module-path jmods --add-modules jdk.jcmd --output ${JCMD}

FROM debian:stretch-slim
COPY --from=base /tmp/jcmd /tmp/jcmd
ENV PATH="/tmp/jcmd/bin:${PATH}"
0

I was also looking for a way to run jcmd with JRE (on production). I could not use the other existing answer from @KevinO because I'm running on K8S and my docker image is based on Alpine Linux.

Eventually, I ended up with copying the minimal artifacts from JDK into the docker image and I was able to run jcmd and start Flight Recorder. I'm using Azul JRE zulu11.41.24-sa-jre11.0.8-linux_musl_x64.

Here is what worked for me (snippet from my dockerfile):

RUN  tar zxf /tmp/${JDK}.tar.gz -C /tmp && \
        mkdir -p /jcmd && \
        mkdir -p /jcmd/bin && \
        mkdir -p /jcmd/lib && \
        cp /tmp/${JDK}/bin/jcmd /jcmd/bin/jcmd && \
        cp -r /tmp/${JDK}/lib/libattach.so /tmp/${JDK}/lib/jvm.cfg /tmp/${JDK}/lib/libjava.so /tmp/${JDK}/lib/libjimage.so /tmp/${JDK}/lib/libnet.so /tmp/${JDK}/lib/libnio.so /tmp/${JDK}/lib/libverify.so /tmp/${JDK}/lib/libzip.so /tmp/${JDK}/lib/modules /tmp/${JDK}/lib/server /tmp/${JDK}/lib/jli /jcmd/lib && \
        rm -rf /tmp/${JDK} 
orid
  • 200
  • 3
  • 10
  • can you please detail what /tmp/${JDK}.tar.gz exactly is and where to get it? – mrq Jul 20 '21 at 08:14
  • @mrq - the ${JDK}.tar.gz is the JDK distribution you use. I used Azul JDK, but it can be any other. For example : https://cdn.azul.com/zulu/bin/zulu11.50.19-ca-jdk11.0.12-linux_x64.tar.gz – orid Jul 21 '21 at 12:34