Jak uzyskać zrzut wątku i sterty procesu Java w systemie Windows, który nie jest uruchomiony w konsoli

232

Mam aplikację Java, którą uruchamiam z konsoli, która z kolei wykonuje inny proces Java. Chcę uzyskać zrzut wątku / sterty tego procesu potomnego.

Na Uniksie mogłem zrobić kill -3 <pid>AFAIK, ale na Windows AFAIK jedynym sposobem na zrzucenie wątku jest Ctrl-Break w konsoli. Ale to daje mi tylko zrzut procesu rodzica, a nie dziecka.

Czy istnieje inny sposób na zrzucenie stosu?

Kasun Siyambalapitiya
źródło

Odpowiedzi:

376

Możesz użyć, jmapaby uzyskać zrzut dowolnego uruchomionego procesu, zakładając, że znasz pid.

Użyj Menedżera zadań lub Monitora zasobów, aby uzyskać pid. Następnie

jmap -dump:format=b,file=cheap.hprof <pid>

aby uzyskać stos dla tego procesu.

rkaganda
źródło
jmap nie jest dostępny dla JDK5 w systemie Windows. Czy jest jakiś sposób na zrzut z JDK5 na Windowsie?
Santron Manibharathi
173
Wątek ten stał się tak popularny, że właśnie słyszałem, jak ktoś nazywa wysypisko hałdy „tanim.bin”
mjaggard,
7
Prostsza nazwa pliku: „heap.hprof”, ponieważ jest w formacie HPROF.
MGM
1
Upewnij się, że używasz poprawnego użytkownika, który rozpoczął proces Java. W moim przypadku było to tomcat8 ps -C java -o pid sudo -u tomcat8 jmap -dump: format = b, plik = <nazwa_pliku> <pid>
bitsabhi
115

Mylisz dwa różne zrzuty Java. kill -3generuje zrzut wątku, a nie zrzut stosu.

Zrzut wątku = ślady stosu dla każdego wątku w wyjściu JVM na standardowe wyjście jako tekst.

Zrzut hałdy = zawartość pamięci dla wyniku procesu JVM w pliku binarnym.

Aby wykonać zrzut wątku w systemie Windows, CTRL+ BREAKjeśli JVM jest procesem pierwszego planu, jest najprostszym sposobem. Jeśli masz uniksopodobną powłokę w systemie Windows, taką jak Cygwin lub MobaXterm, możesz używać jej kill -3 {pid}tak, jak w Uniksie.

Aby wykonać zrzut wątku w Uniksie, CTRL+ Cjeśli JVM jest procesem na pierwszym planie lub kill -3 {pid}będzie działał, dopóki dostaniesz odpowiedni PID dla JVM.

Na obu platformach Java jest dostarczana z kilkoma narzędziami, które mogą pomóc. W przypadku zrzutów wątków jstack {pid}jest to najlepszy wybór. http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstack.html

Aby zakończyć pytanie o zrzut: zrzuty hałdy nie są powszechnie używane, ponieważ są trudne do interpretacji. Ale zawierają wiele przydatnych informacji, jeśli wiesz, gdzie / jak na nie spojrzeć. Najczęstszym zastosowaniem jest zlokalizowanie wycieków pamięci. Dobrą praktyką jest ustawienie -Dwiersza polecenia java, aby zrzut sterty był generowany automatycznie po wystąpieniu błędu OutOfMemoryError, -XX:+HeapDumpOnOutOfMemoryError ale można również ręcznie wywołać zrzut sterty. Najczęstszym sposobem jest użycie narzędzia Java jmap.

UWAGA: to narzędzie nie jest dostępne na wszystkich platformach. Od wersji JDK 1.6 jmapjest dostępny w systemie Windows.

Przykładowy wiersz poleceń wyglądałby podobnie

jmap -dump:file=myheap.bin {pid of the JVM}

Wyjściowy plik „myheap.bin” nie jest czytelny dla człowieka (dla większości z nas) i będziesz potrzebować narzędzia do jego analizy. Moje preferencje to MAT. http://www.eclipse.org/mat/

Derek
źródło
3
Na moim linuksie Ctrl-C przerywa (kończy) to, robię Ctrl- \
nafg
Rozważ to i jego ogólny wpływ na „Aby wykonać zrzut wątku w systemie Windows, CTRL + BREAK”. To zależy od decyzji inżynieryjnej producenta. FE, Lenova, IIRC, to cntrl + fn + p.
ChiefTwoPencils
30

Myślę, że najlepszym sposobem na utworzenie pliku .hprof w procesie Linux jest komenda jmap . Na przykład:jmap -dump:format=b,file=filename.hprof {PID}

Roberto Flores
źródło
19

Oprócz korzystania ze wspomnianej jconsole / visualvm, możesz używać jstack -l <vm-id>w innym oknie wiersza poleceń i przechwytywać dane wyjściowe.

<vm-id> można znaleźć za pomocą menedżera zadań (jest to identyfikator procesu w systemach Windows i Unix) lub za pomocą jps.

Zarówno jstacki jpssą uwzględnione w Sun JDK w wersji 6 i wyższych.

ankon
źródło
Te narzędzia nie są obsługiwane w Javie 1.6. Java 1.6 ma tylko jconsole.
Vanchinathan Chandrasekaran
7
Możesz mieszać JDK i JRE, wyraźnie wspomniałem o JDK. Zobacz dokumentację narzędzi: download.oracle.com/javase/6/docs/technotes/tools/share/… i download.oracle.com/javase/6/docs/technotes/tools/share/…
ankon
17

Polecam Java VisualVM rozpowszechniany z JDK (jvisualvm.exe). Może łączyć się dynamicznie i uzyskiwać dostęp do wątków i sterty. Znalazłem bezcenne dla niektórych problemów.

Lawrence Dol
źródło
2
Jest to w większości przypadków niewykonalne, ponieważ ma do niego przymocowane narzuty, a zrzuty wątków są zwykle pobierane z maszyn produkcyjnych.
Hammad Dar
oryginalne pytanie dotyczy procesu „nie uruchomionego”. Prawdopodobnie jvisualvm nie może się połączyć.
Jaberino
3
@Jaberino: Nie, chodzi o aktualnie uruchomiony proces Java, w systemie Windows, bez powiązanej z nim konsoli.
Lawrence Dol
W najnowszych wersjach Java Java VisualVM została zastąpiona przez JMC / JFR . Zobacz także Jakie są różnice między JVisualVM a Java Mission Control?
Vadzim,
16

Jeśli korzystasz z serwera w wersji 8 lub nowszej, możesz użyć tego:

jcmd PID GC.heap_dump /tmp/dump
Atul Soman
źródło
1
W większości systemów produkcyjnych mamy tylko jre, a nie jdk. To pomaga.
Pragalathan M,
15

Wypróbuj jedną z poniższych opcji.

  1. W przypadku 32-bitowej maszyny JVM:

    jmap -dump:format=b,file=<heap_dump_filename> <pid>
  2. W przypadku 64-bitowej maszyny JVM (wyraźnie cytując):

    jmap -J-d64 -dump:format=b,file=<heap_dump_filename> <pid>
  3. W przypadku 64-bitowej maszyny JVM z algorytmem G1GC w parametrach maszyny wirtualnej (tylko algorytm obiektów aktywnych jest generowany za pomocą algorytmu G1GC):

    jmap -J-d64 -dump:live,format=b,file=<heap_dump_filename> <pid>

Powiązane pytanie SE: Błąd zrzutu stosu Java przy użyciu polecenia jmap: Przedwczesny EOF

Zapoznaj się z różnymi opcjami jmaptego artykułu

Ravindra babu
źródło
13

Jeśli chcesz uzyskać zrzut pamięci z braku pamięci, możesz uruchomić Javę z tą opcją -XX:-HeapDumpOnOutOfMemoryError

patrz strona referencyjna Opcje JVM

Daniel Winterstein
źródło
Dzięki Daniel. Gdzie ten plik jest tworzony na komputerze z systemem Windows? Czy istnieje domyślna ścieżka?
lawa
1
@lava Ścieżkę można ustawić za pomocą opcji -XX: HeapDumpPath, zgodnie z opisem na stronie opcji VM Oracle .
kamczak
Niesamowite. Chciałem przeprowadzić test na noc w nadziei na ujawnienie wycieku pamięci, ale martwiłem się o OOM i awarię, gdy mnie nie ma. To jest doskonałe.
Basil
7

Możesz uruchomić jconsole(dołączony do SDK Java 6), a następnie połączyć się z aplikacją Java. Pokaże każdy działający wątek i ślad stosu.

Steve Kuo
źródło
jak dotąd najlepsza odpowiedź! Do tej pory o tym nie wiedziałem i jest to naprawdę praktyczne!
Xerus,
7

Możesz wysłać kill -3 <pid>z Cygwin. Musisz użyć psopcji Cygwin, aby znaleźć procesy systemu Windows, a następnie po prostu wysłać sygnał do tego procesu.

krosenvold
źródło
3

Jeśli używasz JDK 1.6 lub nowszego, możesz użyć jmappolecenia, aby wykonać zrzut zrzutu procesu Java, pod warunkiem, że powinieneś znać ProcessID.

Jeśli korzystasz z komputera z systemem Windows, możesz użyć Menedżera zadań, aby uzyskać PID. W przypadku komputera z systemem Linux można używać różnych poleceń, takich jak ps -A | grep javalub netstat -tupln | grep javalub top | grep java, w zależności od aplikacji.

Następnie możesz użyć polecenia, np. jmap -dump:format=b,file=sample_heap_dump.hprof 1234Gdzie 1234 to PID.

Dostępne są różne narzędzia do interpretacji pliku hprof. Polecę narzędzie Visualvm Oracle, które jest proste w użyciu.

Badal
źródło
3

Jeśli z jakiegoś powodu nie możesz (lub nie chcesz) korzystać z konsoli / terminala, istnieje alternatywne rozwiązanie. Możesz sprawić, że aplikacja Java wydrukuje dla ciebie zrzut wątku. Kod, który zbiera dane śledzenia stosu, jest dość prosty i można go dołączyć do przycisku lub interfejsu internetowego.

private static String getThreadDump() {
    Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();

    StringBuilder out = new StringBuilder();
    for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet()) {
        Thread thread = entry.getKey();
        StackTraceElement[] elements = entry.getValue();
        out.append(String.format("%s | prio=%d | %s", thread.getName(), thread.getPriority(), thread.getState()));
        out.append('\n');

        for (StackTraceElement element : elements) {
            out.append(element.toString()).append('\n');
        }
        out.append('\n');
    }
    return out.toString();
}

Ta metoda zwróci ciąg, który wygląda następująco:

main | prio=5 | RUNNABLE
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getAllStackTraces(Thread.java:1607)
Main.getThreadDump(Main.java:8)
Main.main(Main.java:36)

Monitor Ctrl-Break | prio=5 | RUNNABLE
java.net.PlainSocketImpl.initProto(Native Method)
java.net.PlainSocketImpl.<clinit>(PlainSocketImpl.java:45)
java.net.Socket.setImpl(Socket.java:503)
java.net.Socket.<init>(Socket.java:424)
java.net.Socket.<init>(Socket.java:211)
com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:59)

Finalizer | prio=8 | WAITING
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

Reference Handler | prio=10 | WAITING
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:502)
java.lang.ref.Reference.tryHandlePending(Reference.java:191)
java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

Dla osób zainteresowanych wersją Java 8 ze strumieniami kod jest jeszcze bardziej kompaktowy:

private static String getThreadDump() {
    Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
    StringBuilder out = new StringBuilder();
    allStackTraces.forEach((thread, elements) -> {
        out.append(String.format("%s | prio=%d | %s", thread.getName(), thread.getPriority(), thread.getState()));
        out.append('\n');

        Arrays.stream(elements).forEach(element -> out.append(element.toString()).append('\n'));
        out.append('\n');
    });
    return out.toString();
}

Możesz łatwo przetestować ten kod za pomocą:

System.out.print(getThreadDump());
HugoTeixeira
źródło
3

Poniższy skrypt używa PsExec do łączenia się z inną sesją systemu Windows, dzięki czemu działa nawet w przypadku połączenia za pośrednictwem usługi pulpitu zdalnego.

Napisałem mały skrypt wsadowy dla Java 8 (używając PsExeci jcmd) o nazwie jvmdump.bat, który zrzuca wątki, stertę, właściwości systemowe i argumenty JVM.

:: set the paths for your environment
set PsExec=C:\Apps\SysInternals\PsExec.exe
set JAVA_HOME=C:\Apps\Java\jdk1.8.0_121
set DUMP_DIR=C:\temp

@echo off

set PID=%1

if "%PID%"=="" (
    echo usage: jvmdump.bat {pid}
    exit /b
)

for /f "tokens=2,3,4 delims=/ " %%f in ('date /t') do set timestamp_d=%%h%%g%%f
for /f "tokens=1,2 delims=: " %%f in ('time /t') do set timestamp_t=%%f%%g
set timestamp=%timestamp_d%%timestamp_t%
echo datetime is: %timestamp%

echo ### Version >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%\bin\jcmd.exe %PID% VM.version >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"

echo. >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
echo ### Uptime >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%\bin\jcmd.exe %PID% VM.uptime >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"

echo. >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
echo ### Command >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%\bin\jcmd.exe %PID% VM.command_line >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"

echo. >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
echo ### Flags >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%\bin\jcmd.exe %PID% VM.flags >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"

echo. >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
echo ### Properties >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%\bin\jcmd.exe %PID% VM.system_properties >>"%DUMP_DIR%\%PID%-%timestamp%-jvm.log"

%PsExec% -s %JAVA_HOME%\bin\jcmd.exe %PID% Thread.print -l >"%DUMP_DIR%\%PID%-%timestamp%-threads.log"

%PsExec% -s %JAVA_HOME%\bin\jcmd.exe %PID% GC.heap_dump "%DUMP_DIR%\%PID%-%timestamp%-heap.hprof"

echo Dumped to %DUMP_DIR%

Musi być uruchamiany w tej samej sesji Windows użytkownika, który uruchomił JVM, więc jeśli łączysz się przez Pulpit zdalny, może być konieczne uruchomienie wiersza polecenia Session 0i uruchomienie go stamtąd. na przykład

%PsExec% -s -h -d -i 0 cmd.exe

Spowoduje to wyświetlenie monitu (kliknij ikonę paska zadań u dołu) View the messagew sesji interaktywnej, która przeniesie Cię do nowej konsoli w drugiej sesji, z której możesz uruchomić jvmdump.batskrypt.

isapir
źródło
2

Jak uzyskać identyfikator procesu aplikacji Java?

Wykonaj polecenie „jcmd”, aby uzyskać identyfikator procesu aplikacji Java.

Jak uzyskać zrzut wątku?

jcmd PID Thread.print> thread.dump

Link referencyjny

Możesz nawet użyć jstack, aby uzyskać zrzut wątku (jstack PID> thread.dump). Link referencyjny

Jak uzyskać zrzut hałdy?

Użyj narzędzia jmap, aby uzyskać zrzut sterty. jmap -F -dump: live, format = b, file = heap.bin PID

PID oznacza identyfikator procesu aplikacji. Link referencyjny

Hari Krishna
źródło
1

Może jcmd ?

Narzędzie Jcmd służy do wysyłania diagnostycznych żądań poleceń do JVM, gdzie są one przydatne do kontrolowania zapisów lotów Java, rozwiązywania problemów i diagnozowania JVM i aplikacji Java.

Narzędzie jcmd zostało wprowadzone w Javie Oracle 7 i jest szczególnie przydatne w rozwiązywaniu problemów z aplikacjami JVM, wykorzystując je do identyfikacji identyfikatorów procesów Java (podobnie jak jps), pozyskiwania zrzutów sterty (podobnie jak jmap), pobierania zrzutów wątków (podobnie jak jstack ), przeglądanie właściwości maszyny wirtualnej, takich jak właściwości systemu i flagi wiersza polecenia (podobnie do jinfo), oraz uzyskiwanie statystyk dotyczących czyszczenia pamięci (podobnie do jstat). Narzędzie jcmd nazwano „szwajcarskim scyzorykiem do badania i rozwiązywania problemów z aplikacją JVM” oraz „ukrytym klejnotem”.

Oto proces, którego musisz użyć, aby wywołać jcmd:

  1. Iść do jcmd <pid> GC.heap_dump <file-path>
  2. W którym
  3. pid: jest identyfikatorem procesu Java, dla którego przechwycony zostanie zrzut sterty. Ponadto,
  4. file-path: ścieżka do pliku, w której drukowany jest zrzut stosu.

Sprawdź to, aby uzyskać więcej informacji na temat zrzutu zrzutu Java .

Jasio
źródło
0

Kontynuacja Visualvm:

Jeśli „nie możesz się połączyć” z uruchomioną maszyną JVM z jvisualvm, ponieważ nie uruchomiłeś jej z odpowiednimi argumentami JVM (i jest to na zdalnej skrzynce), uruchom jstatdna zdalnej skrzynce, a następnie, zakładając, że masz bezpośrednie połączenie, dodaj jako „zdalny host” w visualvm, kliknij dwukrotnie nazwę hosta, a wszystkie inne maszyny JVM w tym polu magicznie pojawią się w visualvm.

Jeśli nie masz „bezpośredniego połączenia” z portami na tym urządzeniu, możesz to zrobić również przez serwer proxy .

Gdy zobaczysz pożądany proces, przejdź do niego w jvisualvm i użyj zakładki monitor -> przycisk „heapdump”.

rogerdpack
źródło
0

Poniższy kod Java jest używany do uzyskania zrzutu stosu procesu Java, podając PID. Program używa zdalnego połączenia JMX do zrzucenia sterty. Może to być pomocne dla kogoś.

import java.lang.management.ManagementFactory;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.lang.reflect.Method;

public class HeapDumper {

public static final String HOST = "192.168.11.177";
public static final String PORT = "1600";
public static final String FILE_NAME = "heapDump.hprof";
public static final String FOLDER_PATH = "C:/";
private static final String HOTSPOT_BEAN_NAME ="com.sun.management:type=HotSpotDiagnostic";

public static void main(String[] args) {
    if(args.length == 0) {
        System.out.println("Enter PID of the Java Process !!!");
        return;
    }

    String pidString = args[0];
    int pid = -1;
    if(pidString!=null && pidString.length() > 0) {
        try {
            pid = Integer.parseInt(pidString);
        }
        catch(Exception e) {
            System.out.println("PID is not Valid !!!");
            return;
        }
    }
    boolean isHeapDumpSuccess = false;
    boolean live = true;
    if(pid > 0) {
        MBeanServerConnection beanServerConn = getJMXConnection();

        if(beanServerConn!=null) {
            Class clazz = null;
            String dumpFile = FOLDER_PATH+"/"+FILE_NAME;
            try{
                clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
                Object hotspotMBean = ManagementFactory.newPlatformMXBeanProxy(beanServerConn, HOTSPOT_BEAN_NAME, clazz);
                Method method = clazz.getMethod("dumpHeap", new Class[]{String.class , boolean.class});
                method.setAccessible(true);
                method.invoke(hotspotMBean , new Object[] {dumpFile, new Boolean(live)});
                isHeapDumpSuccess = true;
            }
            catch(Exception e){
                e.printStackTrace();
                isHeapDumpSuccess = false;
            }
            finally{
                clazz = null;
            }
        }
    }

    if(isHeapDumpSuccess){
        System.out.println("HeapDump is Success !!!");
    }
    else{
        System.out.println("HeapDump is not Success !!!");
    }
}

private static MBeanServerConnection getJMXConnection() {
    MBeanServerConnection mbeanServerConnection = null;
    String urlString = "service:jmx:rmi:///jndi/rmi://" + HOST + ":" + PORT + "/jmxrmi";
    try {
        JMXServiceURL url = new JMXServiceURL(urlString);
        JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
        mbeanServerConnection = jmxConnector.getMBeanServerConnection();
        System.out.println("JMX Connection is Success for the URL :"+urlString);
    }
    catch(Exception e) {
        System.out.println("JMX Connection Failed !!!");
    }
    return mbeanServerConnection;
}

}

Ramesh Subramanian
źródło
0

Aby wykonać zrzut wątku / zrzut stosu z procesu potomnego Java w systemie Windows, musisz zidentyfikować identyfikator procesu potomnego jako pierwszy krok.

Wydając polecenie: jps , będziesz w stanie uzyskać wszystkie identyfikatory procesów Java, które są uruchomione na komputerze z systemem Windows. Z tej listy musisz wybrać identyfikator procesu potomnego. Po utworzeniu identyfikatora procesu potomnego dostępne są różne opcje przechwytywania zrzutu wątku i zrzutu stosu.

Przechwytywanie zrzutów wątków:

Istnieje 8 opcji przechwytywania zrzutów wątków:

  1. jstack
  2. zabij -3
  3. jvisualVM
  4. JMC
  5. Windows (Ctrl + Break)
  6. ThreadMXBean
  7. Narzędzia APM
  8. jcmd

Szczegółowe informacje na temat każdej opcji można znaleźć w tym artykule . Po przechwyceniu zrzutów wątków możesz używać narzędzi takich jak fastThread , Samuraito analizować zrzuty wątków.

Przechwytywanie zrzutów sterty:

Istnieje 7 opcji przechwytywania zrzutów sterty:

  1. jmap

  2. -XX: + HeapDumpOnOutOfMemoryError

  3. jcmd

  4. JVisualVM

  5. JMX

  6. Podejście programowe

  7. Konsole administracyjne

Szczegółowe informacje na temat każdej opcji można znaleźć w tym artykule . Po przechwyceniu zrzutu sterty możesz użyć narzędzi takich jak narzędzie Eclipse Memory Analysis , HeapHero do analizy przechwyconych zrzutów sterty.

Jim T.
źródło
-1

W Oracle JDK mamy polecenie o nazwie jmap (dostępne w folderze bin Java Home). użycie polecenia jest następujące

jmap (opcja) (pid)

Przykład: jmap -dump: live, format = b, file = heap.bin (pid)

Suresh Ram
źródło