JVM TI: cómo hacer un complemento para una máquina virtual



¿Le gustaría agregar alguna característica útil a la JVM? Teóricamente, cada desarrollador puede contribuir a OpenJDK, sin embargo, en la práctica, cualquier cambio no trivial en HotSpot no es bienvenido desde el lado, e incluso con el ciclo de lanzamiento acortado actual, pueden pasar años antes de que los usuarios de JDK vean su función.

Sin embargo, en algunos casos es posible expandir la funcionalidad de una máquina virtual sin siquiera tocar su código. La interfaz de la herramienta JVM, la API estándar para interactuar con la JVM, ayuda.

En el artículo, mostraré con ejemplos concretos lo que se puede hacer con él, contaré lo que ha cambiado en Java 9 y 11, y honestamente advertiré sobre las dificultades (spoiler: tengo que lidiar con C ++).

También hablé sobre este material en JPoint. Si prefiere el video, puede ver el informe del video .

Entrada


La red social Odnoklassniki, donde trabajo como ingeniero líder, está casi completamente escrita en Java. Pero hoy les contaré sobre otra parte, que no está completamente en Java.

Como saben, el problema más popular entre los desarrolladores de Java es NullPointerException. Una vez, mientras estaba de servicio en el portal, también me encontré con NPE en producción. El error fue acompañado por algo como este seguimiento de pila:



Por supuesto, en el seguimiento de la pila, puede rastrear el lugar donde ocurrió la excepción hasta una línea específica en el código. Solo en este caso no me hizo sentir mejor, porque aquí NPE puede reunirse mucho donde:



Sería genial si la JVM sugiriera exactamente dónde estaba este error, por ejemplo, así:
java.lang.NullPointerException: Called 'getUsers()' method on null object

Pero, desafortunadamente, NPE ahora no contiene nada por el estilo. Aunque han estado pidiendo esto durante mucho tiempo, al menos con Java 1.4: este error tiene 16 años. Periódicamente, se abrían más y más errores sobre este tema, pero siempre se cerraban como "No se solucionará":



Esto no sucede en todas partes. Volker Simonis de SAP contó cómo habían implementado esta característica en SAP JVM durante mucho tiempo y la ayudó más de una vez. Otro empleado de SAP, una vez más, presentó un error en OpenJDK y se ofreció como voluntario para implementar un mecanismo similar al de la JVM de SAP. Y, he aquí, esta vez el error no se cerró; existe la posibilidad de que esta característica ingrese a JDK 14.

Pero, ¿cuándo se lanzará JDK 14 y cuándo cambiaremos a él? ¿Qué hacer si quieres investigar el problema aquí y ahora?

Por supuesto, puede mantener su bifurcación de OpenJDK. La función de informes de NPE en sí no es tan complicada que podríamos haberla implementado. Pero al mismo tiempo surgirán todos los problemas de apoyar su propio ensamblaje. Sería genial implementar la función una vez y luego simplemente conectarla a cualquier versión de la JVM como un complemento. ¡Y esto es realmente posible! JVM tiene una API especial (desarrollada originalmente para todo tipo de depuradores y perfiladores): Interfaz de herramientas JVM.

Lo más importante, esta API es estándar. Tiene una especificación estricta, y al implementar una función de acuerdo con ella, puede estar seguro de que funcionará en nuevas versiones de la JVM.

Para usar esta interfaz, debe escribir un programa pequeño (o grande, según cuáles sean sus tareas). Nativo: generalmente se escribe en C o C ++. La jdk/include/jvmti.h JDK estándar tiene un archivo de encabezado jdk/include/jvmti.h que desea incluir.

El programa se compila en una biblioteca dinámica y se conecta mediante el parámetro -agentpath durante el inicio de la JVM. Es importante no confundirlo con otro parámetro similar: -javaagent . De hecho, los agentes Java son un caso especial de los agentes JVM TI. Además en el texto bajo la palabra "agente" se entiende precisamente el agente nativo.

Por donde empezar


Veamos en la práctica cómo escribir el agente JVM TI más simple, una especie de "hola mundo".

 #include <jvmti.h> #include <stdio.h> JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { jvmtiEnv* jvmti; vm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_0); char* vm_name = NULL; jvmti->GetSystemProperty("java.vm.name", &vm_name); printf("Agent loaded. JVM name = %s\n", vm_name); fflush(stdout); return 0; } 

La primera línea incluyo el mismo archivo de encabezado. Luego viene la función principal que debe implementarse en el agente: Agent_OnLoad() . La máquina virtual en sí misma lo llama cuando el agente arranca, pasando un puntero al objeto JavaVM* .

Al jvmtiEnv* , puede obtener un puntero al entorno JVM TI: jvmtiEnv* . Y a través de él, a su vez, ya llamamos a las funciones JVM TI. Por ejemplo, usando GetSystemProperty, lea el valor de una propiedad del sistema.

Si ahora ejecuto este "hola mundo", pasando el archivo dll compilado a -agentpath , la línea impresa por nuestro agente aparecerá en la consola antes de que el programa Java comience a ejecutarse:



Enriquecimiento NPE


Como hello world no es el ejemplo más interesante, volvamos a nuestras excepciones. El código de agente completo que complementa los informes de NPE está en GitHub .

Así es como se ve Agent_OnLoad() si quiero pedirle a la máquina virtual que nos notifique todas las excepciones:

 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { jvmtiEnv* jvmti; vm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_0); jvmtiCapabilities capabilities = {0}; capabilities.can_generate_exception_events = 1; jvmti->AddCapabilities(&capabilities); jvmtiEventCallbacks callbacks = {0}; callbacks.Exception = ExceptionCallback; jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, NULL); return 0; } 

Primero le pido a JVM TI la capacidad correspondiente (can_generate_exception_events). Hablaremos sobre la capacidad por separado.

El siguiente paso es suscribirse a los eventos de excepción. Siempre que la JVM arroje excepciones (sin importar si son capturadas o no), se llamará a nuestra función ExceptionCallback() .

El último paso es llamar a SetEventNotificationMode() para permitir la entrega de notificaciones.

En ExceptionCallback, la JVM pasa todo lo que necesitamos para manejar excepciones.
 void JNICALL ExceptionCallback(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) { jclass NullPointerException = env->FindClass("java/lang/NullPointerException"); if (!env->IsInstanceOf(exception, NullPointerException)) { return; } jclass Throwable = env->FindClass("java/lang/Throwable"); jfieldID detailMessage = env->GetFieldID(Throwable, "detailMessage", "Ljava/lang/String;"); if (env->GetObjectField(exception, detailMessage) != NULL) { return; } char buf[32]; sprintf(buf, "at location %id", (int) location); env->SetObjectField(exception, detailMessage, env->NewStringUTF(buf)); } 


Aquí hay tanto el objeto del subproceso que arrojó la excepción (subproceso) como el lugar donde sucedió esto (método, ubicación) y el objeto de la excepción (excepción), e incluso el lugar en el código que captura esta excepción (catch_method, catch_location).

Lo importante: en esta devolución de llamada, además del puntero al entorno JVM TI, también se pasa el entorno JNI (env). Esto significa que podemos usar todas las funciones JNI en él. Es decir, JVM TI y JNI coexisten perfectamente, se complementan entre sí.

En mi agente uso ambos. En particular, a través de JNI verifico que mi excepción es del tipo NullPointerException , y luego reemplazo el campo detailMessage un mensaje de error.

Como la propia JVM nos pasa la ubicación, el índice de código de bytes en el que ocurrió la excepción, entonces coloco esta ubicación aquí en el mensaje:



El número 66 indica el índice en código de bytes donde se produjo esta excepción. Pero analizar el código de bytes manualmente es triste: necesita descompilar el archivo de clase, buscar la instrucción número 66, intentar comprender lo que estaba haciendo ... Sería genial si nuestro propio agente pudiera mostrar algo más legible para los humanos.

Sin embargo, en este caso, la TI JVM tiene todo lo que necesita. Es cierto que debe solicitar características adicionales de JVM TI: obtenga el código de bytes y el método de agrupación constante.

 jvmtiCapabilities capabilities = {0}; capabilities.can_generate_exception_events = 1; capabilities.can_get_bytecodes = 1; capabilities.can_get_constant_pool = 1; jvmti->AddCapabilities(&capabilities); 

Ahora expandiré ExceptionCallback: a través de la función JVM TI GetBytecodes() obtendré el cuerpo del método para verificar lo que hay en él por índice de ubicación. Luego viene un gran interruptor de instrucciones de código de bytes: si este es un acceso a la matriz, habrá un mensaje de error, si el acceso al campo es otro mensaje, si la llamada al método es el tercero, y así sucesivamente.

Código de excepción de devolución de llamada
 jint bytecode_count; u1* bytecodes; if (jvmti->GetBytecodes(method, &bytecode_count, &bytecodes) != 0) { return; } if (location >= 0 && location < bytecode_count) { const char* message = get_exception_message(bytecodes[location]); if (message != NULL) { ... env->SetObjectField(exception, detailMessage, env->NewStringUTF(buf)); } } jvmti->Deallocate(bytecodes); 


Solo queda sustituir el nombre del campo o método. Puede obtenerlo del grupo constante , que está disponible nuevamente gracias a JVM TI.

 if (jvmti->GetConstantPool(holder, &cpool_count, &cpool_bytes, &cpool) != 0) { return strdup("<unknown>"); } 

Luego viene un poco de magia, pero en realidad nada complicado, solo de acuerdo con la especificación del formato de archivo de clase analizamos el grupo constante y a partir de ahí aislamos la línea, el nombre del método.

Análisis de grupo constante
 u1* ref = get_cpool_at(cpool, get_u2(bytecodes + 1)); // CONSTANT_Fieldref u1* name_and_type = get_cpool_at(cpool, get_u2(ref + 3)); // CONSTANT_NameAndType u1* name = get_cpool_at(cpool, get_u2(name_and_type + 1)); // CONSTANT_Utf8 size_t name_length = get_u2(name + 1); char* result = (char*) malloc(name_length + 1); memcpy(result, name + 3, name_length); result[name_length] = 0; 


Otro punto importante: algunas funciones JVM TI, por ejemplo GetConstantPool() o GetBytecodes() , asignan una determinada estructura en la memoria nativa, que debe liberarse cuando termine de trabajar con ella.

 jvmti->Deallocate(cpool); 

Ejecute el programa fuente con nuestro agente extendido, y aquí hay una descripción completamente diferente de la excepción: informa que llamamos al método longValue () en el objeto nulo.



Otras aplicaciones


En términos generales, los desarrolladores a menudo quieren manejar las excepciones a su manera. Por ejemplo, reinicie automáticamente la JVM si se StackOverflowError un StackOverflowError .

Este deseo se puede entender, ya que StackOverflowError es el mismo error fatal que OutOfMemoryError , después de su aparición ya no es posible garantizar el correcto funcionamiento del programa. O, por ejemplo, a veces para analizar el problema, quiero recibir un volcado de subprocesos o un volcado de almacenamiento dinámico cuando se produce una excepción.



Para ser justos, el IBM JDK tiene esa oportunidad fuera de la caja. Pero ahora ya sabemos que con el agente JVM TI, puede implementar lo mismo en HotSpot. Es suficiente suscribirse a la devolución de llamada de excepción y analizar la excepción. Pero, ¿cómo eliminar el volcado de hebras o el volcado de almacenamiento dinámico de nuestro agente? JVM TI tiene todo lo que necesita para este caso:



No es muy conveniente implementar todo el mecanismo de omitir el montón y crear un volcado. Pero compartiré el secreto de cómo hacerlo más fácil y rápido. Es cierto que esto ya no está incluido en el JVM TI estándar, sino que es una extensión privada de Hotspot.

Debe conectar el archivo de encabezado jmm.h desde las fuentes de HotSpot y llamar a la función JVM_GetManagement() :

 #include "jmm.h" JNIEXPORT void* JNICALL JVM_GetManagement(jint version); void JNICALL ExceptionCallback(jvmtiEnv* jvmti, JNIEnv* env, ...) { JmmInterface* jmm = (JmmInterface*) JVM_GetManagement(JMM_VERSION_1_0); jmm->DumpHeap0(env, env->NewStringUTF("dump.hprof"), JNI_FALSE); } 

Devolverá un puntero a la interfaz de administración de HotSpot, que en una sola llamada generará un volcado de montón o volcado de subprocesos. El código completo para el ejemplo se puede encontrar en mi respuesta a Stack Overflow.

Naturalmente, puede manejar no solo excepciones, sino también un montón de otros eventos relacionados con la operación JVM: iniciar / detener subprocesos, cargar clases, recolección de basura, métodos de compilación, métodos de entrada / salida, incluso acceder o modificar campos específicos de objetos Java.

Tengo un ejemplo de otro agente vmtrace que se suscribe a muchos eventos JVM TI estándar y los registra. Si ejecuto un programa simple con este agente, obtendré un registro detallado, que, cuando termine, con marcas de tiempo:



Como puede ver, para imprimir simplemente hello world, se cargan cientos de clases, se generan y compilan decenas y cientos de métodos. Queda claro por qué Java tarda tanto en ejecutarse. Todo sobre todo tomó más de doscientos milisegundos.

Lo que JVM TI puede hacer


Además del manejo de eventos, el JVM TI tiene muchas otras características. Se pueden dividir en dos grupos.

Uno es obligatorio, que cualquier JVM que admita JVM TI debe implementar. Estos incluyen las operaciones de análisis de métodos, campos, flujos, la capacidad de agregar nuevas clases al classpath, etc.

Hay características opcionales que requieren una solicitud de capacidades preliminares. No se requiere que JVM los admita a todos, sin embargo, HotSpot implementa la especificación completa en su totalidad. Las características opcionales se dividen en dos subgrupos: las que se pueden conectar solo al comienzo de la JVM (por ejemplo, la capacidad de establecer un punto de interrupción o analizar variables locales), y las que se pueden conectar en cualquier momento (en particular, código de bytes o grupo constante, que I usado arriba).



Puede notar que la lista de características es muy similar a las capacidades del depurador. De hecho, un depurador de Java no es más que un caso especial del agente JVM TI, que aprovecha todas estas capacidades y solicita todas las capacidades.

La separación de las capacidades en aquellas que se pueden habilitar en cualquier momento, y aquellas que solo están en el momento del arranque, se realiza a propósito. No todas las funciones son gratuitas, algunas llevan gastos generales.

Si todo está claro con los gastos indirectos directos que acompañan al uso de la función, entonces hay otros indirectos menos obvios que aparecen incluso si no usa la función, pero simplemente a través de las capacidades declara que será necesaria en algún momento en el futuro. Esto se debe al hecho de que la máquina virtual puede compilar el código de manera diferente o agregar comprobaciones adicionales al tiempo de ejecución.

Por ejemplo, la capacidad ya considerada para suscribirse a excepciones (can_generate_exception_events) lleva al hecho de que todas las excepciones de lanzamiento irán de manera lenta. En principio, esto no da tanto miedo, porque las excepciones son algo raro en un buen programa Java.

La situación con las variables locales es ligeramente peor. Para can_access_local_variables, que le permite obtener los valores de las variables locales en cualquier momento, debe deshabilitar algunas optimizaciones importantes. En particular, Escape Analysis deja de funcionar por completo, lo que puede generar una sobrecarga notable: dependiendo de la aplicación, 5-10%.

De ahí la conclusión: si ejecuta Java con el agente de depuración activado, sin siquiera usarlo, las aplicaciones se ejecutarán más lentamente. De todos modos, incluir un agente de depuración en la producción no es una buena idea.

Una serie de características, por ejemplo, establecer un punto de interrupción o rastrear todas las entradas / salidas de un método, conlleva una sobrecarga mucho más grave. En particular, algunos eventos JVM TI (FieldAccess, MethodEntry / Exit) solo funcionan en el intérprete.

Un agente es bueno y dos son mejores.


Puede conectar varios agentes a un solo proceso simplemente especificando varios parámetros de -agentpath . Todos tendrán su propio entorno JVM TI. Esto significa que todos pueden suscribirse a sus capacidades e interceptar sus eventos de forma independiente.

Y si dos agentes se suscribieron al evento de punto de interrupción, y en uno el punto de interrupción se establece en algún método, cuando se ejecute este método, ¿recibirá el segundo agente el evento?

En realidad, tal situación no puede ocurrir (al menos en HotSpot JVM). Porque hay algunas capacidades que solo uno de los agentes puede poseer en un momento dado. Estos incluyen breakpoint_events en particular. Por lo tanto, si el segundo agente solicita la misma capacidad, recibirá un error en respuesta.

Esta es una conclusión importante: el agente siempre debe verificar el resultado de la solicitud de capacidades, incluso si se está ejecutando en HotSpot y sabe que todos están disponibles. La especificación JVM TI no dice nada sobre capacidades exclusivas, pero HotSpot tiene una función de implementación de este tipo.

Es cierto que el aislamiento del agente no siempre funciona a la perfección. Durante el desarrollo de async-profiler, me encontré con este problema: cuando tenemos dos agentes y uno solicita la generación de eventos de compilación de métodos, todos los agentes reciben estos eventos. Por supuesto, presenté un error , pero debe tener en cuenta que los eventos que no espera pueden ocurrir en su agente.

Uso en un programa regular


JVM TI puede parecer algo muy específico para depuradores y perfiladores, pero también se puede usar en un programa Java normal. Considera un ejemplo.

El paradigma de programación reactiva ahora está muy extendido cuando todo es asíncrono, pero hay un problema con este paradigma.

 public class TaskRunner { private static void good() { CompletableFuture.runAsync(new AsyncTask(GOOD)); } private static void bad() { CompletableFuture.runAsync(new AsyncTask(BAD)); } public static void main(String[] args) throws Exception { good(); bad(); Thread.sleep(200); } } 

Ejecuto dos tareas asincrónicas que difieren solo en parámetros. Y si algo sale mal, se genera una excepción:



Desde el seguimiento de la pila, no está completamente claro cuál de estas tareas causó el problema. Porque la excepción ocurre en un hilo completamente diferente, donde no tenemos contexto. ¿Cómo entender en qué tarea?

Como una de las soluciones, puede agregar información sobre dónde la creamos al constructor de nuestra tarea asincrónica:

 public AsyncTask(String arg) { this.arg = arg; this.location = getLocation(); } 

Es decir, recuerde la ubicación: un lugar específico en el código, hasta la línea desde donde se llamó al constructor. Y en caso de una excepción para prometerlo:

 try { int n = Integer.parseInt(arg); } catch (Throwable e) { System.err.println("ParseTask failed at " + location); e.printStackTrace(); } 

Ahora, cuando ocurre una excepción, veremos que esto sucedió en la línea 14 en el TaskRunner (donde se crea la tarea con el parámetro BAD):



Pero, ¿cómo obtener el lugar en el código desde donde se llama al constructor? Antes de Java 9, había la única forma legal de hacer esto: obtener un seguimiento de la pila, omitir algunos marcos irrelevantes, y justo debajo de la pila estará el lugar al que llamó nuestro código.

 String getLocation() { StackTraceElement caller = Thread.currentThread().getStackTrace()[3]; return caller.getFileName() + ':' + caller.getLineNumber(); } 

Pero hay un problema. Obtener el StackTrace completo es bastante lento. Tengo un informe completo dedicado a esto.

Esto no sería un gran problema si ocurriera raramente. Pero, por ejemplo, tenemos un servicio web, una interfaz que acepta solicitudes HTTP. Esta es una gran aplicación, millones de líneas de código. Y para detectar errores de representación, utilizamos un mecanismo similar: en los componentes para la representación, recordamos el lugar donde se crean. Tenemos millones de estos componentes, por lo que obtener todos los rastros de la pila lleva un tiempo tangible para iniciar la aplicación, no solo un minuto. Por lo tanto, esta característica se desactivó anteriormente en producción, aunque para el análisis de problemas se necesita en producción.

Java 9 introdujo una nueva forma de evitar las pilas de flujo: StackWalker, que a través de Stream API puede hacer todo esto de forma perezosa, bajo demanda. Es decir, podemos omitir el número correcto de fotogramas y obtener solo uno que nos interese.

 String getLocation() { return StackWalker.getInstance().walk(s -> { StackWalker.StackFrame frame = s.skip(3).findFirst().get(); return frame.getFileName() + ':' + frame.getLineNumber(); }); } 

Funciona un poco mejor que obtener el seguimiento de la pila completa, pero no por un orden de magnitud o incluso muchas veces. En nuestro caso, resultó ser aproximadamente una vez y media más rápido:



Existe un problema conocido con la implementación subóptima de StackWalker, y lo más probable es que incluso se solucione en JDK 13. Pero, de nuevo, ¿qué debemos hacer ahora en Java 8, donde StackWalker ni siquiera es lento?

El JVM TI viene al rescate nuevamente. Hay una función GetStackTrace() que puede hacer todo lo que necesita: obtener un fragmento de un seguimiento de pila de una longitud determinada, comenzando desde el marco especificado, y no hacer nada más.

 GetStackTrace(jthread thread, jint start_depth, jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint* count_ptr) 

Solo queda una pregunta: ¿cómo llamar a la función JVM TI desde nuestro programa Java? Al igual que cualquier otro método nativo: cargue la biblioteca nativa con System.loadLibrary() , donde estará la implementación JNI de nuestro método.

 public class StackFrame { public static native String getLocation(int depth); static { System.loadLibrary("stackframe"); } } 

Se puede obtener un puntero al entorno JVM TI no solo de Agent_OnLoad (), sino también mientras el programa se está ejecutando, y continuar usándolo desde métodos JNI nativos comunes:

 JNIEXPORT jstring JNICALL Java_StackFrame_getLocation(JNIEnv* env, jclass unused, jint depth) { jvmtiFrameInfo frame; jint count; jvmti->GetStackTrace(NULL, depth, 1, &frame, &count); 

:



, JDK : - . -. , , , JDK. JDK 8u112, JVM TI-, (GetMethodName, GetMethodDeclaringClass ), .

, , : JVM TI- , , -. , C++, jvmtiEnter.xsl .

: HotSpot XSLT-. HotSpot.

? , . , - jmethodID , . , .


, JVM TI Java- , System.loadLibrary .

, , JVM TI- -agentpath JVM.

: (dynamic attach).

? , - , , JVM TI- .

JDK 9, jcmd:

 jcmd <pid> JVMTI.agent_load /path/to/agent.so [arguments] 

JDK jattach . , async-profiler , - JVM-, jattach.

JVM TI- , , Agent_OnLoad() , Agent_OnAttach() . : Agent_OnAttach() capabilities, .

, , Agent_OnAttach() .

. IntelliJ IDEA: Java-, , - .

process ID IDEA, jattach JVM TI- patcher.dll:
jattach 8648 load patcher.dll true

:



? Java- ( javax.swing.AbstractButton ) JNI setBackground() . .

Java 9


JVM TI , , , API, . Java 9.

, Java 9 , . , «» JDK, .

, JDK Direct ByteBuffer. API:



, Cassandra , MappedByteBuffer, , JVM .

JDK 9, IllegalAccessError:



Reflection: .

, Java Linux. - java.io.FileDescriptor JNI - . , JDK 9, :



, JVM, . , . , Cassandra Java 11, :

 --add-exports java.base/jdk.internal.misc=ALL-UNNAMED --add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED --add-exports java.management.rmi/com.sun.jmx.remote.internal.rmi=ALL-UNNAMED --add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED --add-exports java.rmi/sun.rmi.server=ALL-UNNAMED --add-exports java.sql/java.sql=ALL-UNNAMED --add-opens java.base/java.lang.module=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.base/jdk.internal.ref=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.math=ALL-UNNAMED --add-opens java.base/jdk.internal.module=ALL-UNNAMED --add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED 

JVM TI :

  • GetAllModules
  • AddModuleExports
  • AddModuleOpens
  • . .

, : JVM, , , .

Direct ByteBuffer:

 public static void main(String[] args) { ByteBuffer buf = ByteBuffer.allocateDirect(1024); ((sun.nio.ch.DirectBuffer) buf).cleaner().clean(); System.out.println("Buffer cleaned"); } 

, IllegalAccessError. agentpath antimodule , . .

Java 11


Java 11. , ! : SampledObjectAlloc , , .

callback , : , , , , . SetHeapSampingInterval , .



? , , . Java Flight Recorder.

, , , , .

Thread Local Allocation Buffer . TLAB , . , .



, TLAB, . JVM runtime .

, , — 5%.

, , JDK 7, Flight Recorder. API async-profiler. , JDK 11, API , JVM TI, . , YourKit . API, , .

. , , , , .



Conclusión


JVM TI — .

, ++, JVM . , JVM TI .

GitHub . , .

Source: https://habr.com/ru/post/458812/


All Articles