Introducción a Arthas: Diagnóstico Avanzado para Aplicaciones Java

¿Qué es Arthas y Para Qué Sirve?

Arthas es una potente herramienta de diagnóstico de código abierto desarrollada por Alibaba. Diseñada específicamente para aplicaciones Java, Arthas permite a los desarrolladores solucionar problemas en tiempo real en entornos de producción, proporcionando capacidades de depuración avanzadas sin necesidad de reiniciar la aplicación ni modificar el código fuente.

Esta herramienta resulta invaluable en diversas situaciones, tales como:

  • Identificar el JAR de origen de una clase o resolver excepciones relacionadas con la carga de clases.
  • Verificar la ejecución de cambios de código en un entorno en vivo sin necesidad de un redepliegue.
  • Depurar problemas complejos en producción donde la depuración remota tradicional no es factible.
  • Investigar datos específicos de un usuario o escenarios de errores que solo se manifiestan en el entorno productivo.
  • Obtener una visión general del estado del sistema, incluyendo el monitoreo en tiempo real del JVM.
  • Localizar cuellos de botella de rendimiento y generar perfiles de llama para optimización.

Arthas es compatible con JDK 6+ y puede ejecutarse en Linux, macOS y Windows. Su interfaz de línea de comandos ofrece una experiencia de usuario eficiente, complementada con autocompletado de comandos para facilitar la navegación y el diagnóstico.

Instalación de Arthas

Instalación Rápida con arthas-boot (Recomendado)

La manera más sencilla y recomendada de comenzar a usar Arthas es a través de arthas-boot.jar. Este script de inicio descarga y ejecuta automáticamente la última versión de Arthas.

curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

Para ver todas las opciones disponibles con arthas-boot, puedes usar el flag -h:

java -jar arthas-boot.jar -h

Si experimentas problemas de velocidad al descargar el archivo debido a la ubicación geográfica, puedes utilizar el espejo de Aliyun:

java -jar arthas-boot.jar --repo-mirror aliyun --use-http

Instalación Completa

Para una instalación manual, descarga la última versión completa de Arthas desde su repositorio oficial o página de lanzamientos. Una vez descargado, descomprime el archivo y encontrarás el ejecutable arthas-boot.jar.

java -jar arthas-boot.jar

Es fundamental que el usuario que ejecuta Arthas tenga los mismos permisos que el proceso Java objetivo al que se intentará adjuntar. Por ejemplo, si tu aplicación se ejecuta bajo el usuario 'admin', deberías iniciar Arthas de la siguiente manera: sudo su admin && java -jar arthas-boot.jar o sudo -u admin -EH java -jar arthas-boot.jar. Si encuentras dificultades para adjuntar Arthas a un proceso, consulta los archivos de registro en ~/logs/arthas/ para obtener información detallada.

Desinstalación de Arthas

Para remover completamente Arthas de tu sistema, simplemente elimina los archivos de configuración y los logs generados:

En sistemas Linux/Unix/macOS

rm -rf ~/.arthas/
rm -rf ~/logs/arthas

En Windows

Elimina los directorios .arthas y logs/arthas que se encuentran en tu directorio de usuario (user home).

Uso Básico de Arthas

Iniciar Arthas y Conectarse a un Proceso Java

Una vez que ejecutes java -jar arthas-boot.jar, Arthas te presentará una lista de procesos Java en ejecución. Deberás seleccionar el número correspondiente al proceso al que deseas adjuntar Arthas. Por ejemplo, si tu aplicación de demostración es la segunda en la lista:

$ java -jar arthas-boot.jar
* [1]: 35542
  [2]: 71560 arthas-demo.jar

Introduce 2 y presiona Enter. Arthas se conectará al proceso especificado y mostrará un mensaje de éxito junto con su interfaz de línea de comandos:

[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
  ,---.  ,------. ,--------.,--.  ,--.  ,---.   ,---.
 /  O  \ |  .--. ''--.  .--'|  '--'  | /  O  \ '   .-'
|  .-.  ||  '--'.'   |  |   |  .--.  ||  .-.  |`.  `-.
|  | |  ||  |\  \    |  |   |  |  |  ||  | |  |.-'    |
`--' `--'`--' '--'   `--'   `--'  `--'`--' `--'`-----'
 
wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
$

Visualizar el Dashboard del Sistema

El comando dashboard ofrece una vista en tiempo real y resumida del estado de la aplicación y del JVM. Incluye información sobre los hilos, uso de memoria, actividad del recolector de basura (GC) y detalles del entorno de ejecución. Para salir de la vista del dashboard, simplemente presiona Ctrl+C.

$ dashboard
ID     NAME                   GROUP          PRIORI STATE  %CPU    TIME   INTERRU DAEMON
17     pool-2-thread-1        system         5      WAITIN 67      0:0    false   false
27     Timer-for-arthas-dashb system         10     RUNNAB 32      0:0    false   true
11     AsyncAppender-Worker-a system         9      WAITIN 0       0:0    false   true
9      Attach Listener        system         9      RUNNAB 0       0:0    false   true
3      Finalizer              system         8      WAITIN 0       0:0    false   true
2      Reference Handler      system         10     WAITIN 0       0:0    false   true
4      Signal Dispatcher      system         9      RUNNAB 0       0:0    false   true
26     as-command-execute-dae system         10     TIMED_ 0       0:0    false   true
13     job-timeout            system         9      TIMED_ 0       0:0    false   true
1      main                   main           5      TIMED_ 0       0:0    false   false
14     nioEventLoopGroup-2-1  system         10     RUNNAB 0       0:0    false   false
18     nioEventLoopGroup-2-2  system         10     RUNNAB 0       0:0    false   false
23     nioEventLoopGroup-2-3  system         10     RUNNAB 0       0:0    false   false
15     nioEventLoopGroup-3-1  system         10     RUNNAB 0       0:0    false   false
Memory             used   total max    usage GC
heap               32M    155M  1820M  1.77% gc.ps_scavenge.count  4
ps_eden_space      14M    65M   672M   2.21% gc.ps_scavenge.time(m 166
ps_survivor_space  4M     5M    5M           s)
ps_old_gen         12M    85M   1365M  0.91% gc.ps_marksweep.count 0
nonheap            20M    23M   -1           gc.ps_marksweep.time( 0
code_cache         3M     5M    240M   1.32% ms)
Runtime
os.name                Mac OS X
os.version             10.13.4
java.version           1.8.0_162
java.home              /Library/Java/JavaVir
                       tualMachines/jdk1.8.0
                       _162.jdk/Contents/Hom
                       e/jre

Identificar la Clase Principal con el Comando thread

Para determinar la clase principle (Main Class) de tu aplicación, puedes usar el comando thread. El hilo con ID 1 es comúnmente el hilo principal, que ejecuta el método main(). Puedes filtrar la salida para localizar rápidamente la clase:

$ thread 1 | grep 'main('
    at demo.MathGame.main(MathGame.java:17)

Descompilar la Clase Principal con jad

Una vez que tienes el nombre de la clase principal, el comando jad te permite descompilar el bytecode de esa clase en código fuente Java legible. Esto es particularmente útil para comprender el funcionamiento de una aplicación sin tener acceso directo a su código fuente o para verificar la versión exacta de una clase cargada en el JVM.

$ jad demo.MathGame

La salida mostrará el código fuente descompilado:


ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
  +-sun.misc.Launcher$ExtClassLoader@66350f69
 
Location:
/tmp/arthas-demo.jar
 
/*
 * Decompiled with CFR 0_132.
 */
package demo;
 
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
 
public class MathGame {
    private static Random random = new Random();
    private int illegalArgumentCount = 0;
 
    public static void main(String[] args) throws InterruptedException {
        MathGame game = new MathGame();
        do {
            game.run();
            TimeUnit.SECONDS.sleep(1L);
        } while (true);
    }
 
    public void run() throws InterruptedException {
        try {
            int number = random.nextInt();
            List<Integer> primeFactors = this.primeFactors(number);
            MathGame.print(number, primeFactors);
        }
        catch (Exception e) {
            System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
        }
    }
 
    public static void print(int number, List<Integer> primeFactors) {
        StringBuffer sb = new StringBuffer("" + number + "=");
        Iterator<Integer> iterator = primeFactors.iterator();
        while (iterator.hasNext()) {
            int factor = iterator.next();
            sb.append(factor).append('*');
        }
        if (sb.charAt(sb.length() - 1) == '*') {
            sb.deleteCharAt(sb.length() - 1);
        }
        System.out.println(sb);
    }
 
    public List<Integer> primeFactors(int number) {
        if (number < 2) {
            ++this.illegalArgumentCount;
            throw new IllegalArgumentException("number is: " + number + ", need >= 2");
        }
        ArrayList<Integer> result = new ArrayList<Integer>();
        int i = 2;
        while (i <= number) {
            if (number % i == 0) {
                result.add(i);
                number /= i;
                i = 2;
                continue;
            }
            ++i;
        }
        return result;
    }
}
 
Affect(row-cnt:1) cost in 970 ms.

Observar el Valor de Retorno de Métodos con watch

El comando watch es una herramienta poderosa para observar la ejecución de métodos en tiempo real. Permite enspeccionar los parámetros, el valor de retorno o cualquier excepción lanzada por un método sin modificar el código fuente. A continuación, un ejemplo de cómo observar el valor de retorno del método primeFactors de la clase demo.MathGame:

$ watch demo.MathGame primeFactors returnObj

La salida mostrará las invocaciones del método y sus valores de retorno a lo largo del tiempo:

Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
    @Integer[5],
    @Integer[47],
    @Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
    @Integer[2],
    @Integer[5],
    @Integer[317],
    @Integer[503],
    @Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
    @Integer[2],
    @Integer[2],
    @Integer[3],
    @Integer[3],
    @Integer[31],
    @Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
    @Integer[5],
    @Integer[29],
    @Integer[7651739],
]

Salir de Arthas

Para cerrar únicamente la conexión de la consola actual sin detener la instancia de Arthas que está adjunta al proceso Java, utiliza los comandos quit o exit. La instancia de Arthas continuará ejecutándose en segundo plano en el proceso objetivo, manteniendo el puerto abierto para futuras reconexiones.

Si deseas detaner completamente la instancia de Arthas y desvincularla del proceso Java objetivo, emplea el comando stop.

Etiquetas: java jvm Arthas diagnóstico troubleshooting

Publicado el 10-2 17:26