CodeFest 2019. Андрей Паньгин (Одноклассники) — JVM TI: как сделать «плагин» для виртуальной машины

CodeFest 2019. Андрей Паньгин (Одноклассники) — JVM TI: как сделать «плагин» для виртуальной машины

JVM Tool Interface — стандартный API для разработки всевозможных инструментов: профайлеров, отладчиков и диагностических утилит. Фактически это единственный легальный способ обратиться к JVM, будь то HotSpot или другая виртуальная машина. Оказывается, JVM TI полезен не только при создании агентов. В докладе мы разберём, в каких случаях JVM TI может пригодиться разработчику, в том числе в обычных Java приложениях. Познакомимся с возможностями интерфейса, включая нововведения из Java 9 и 11, и напишем собственный инструмент. Несмотря на свою мощь, JVM TI не лишён недостатков. Прежде всего, он подразумевает написание кода на C/C++. Но, кроме того, с ним связана масса нетривиальных особенностей и даже JVM-багов. На реальных примерах из практики мы увидим, с какими проблемами JVM TI сталкиваются программисты, и как эти проблемы решить.

16b6c87229eaf58768d25ed7b2bbbf52?s=128

CodeFest

April 08, 2019
Tweet

Transcript

  1. None
  2. 2

  3. 3 • • • • • Java

  4. 4 • • • • • Java

  5. 5

  6. 6 java.lang.NullPointerException at one.app.databus.DatabusHandler.addDetails(DatabusHandler.java:462) at one.app.databus.DatabusHandler.handleActivity(DatabusHandler.java:223) at one.app.activity.ActivityService.reportActivity(ActivityService.java:139) at one.app.activity.ActivityService$RunnableActivity.run(ActivityService.java:176)

    at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.lang.Thread.run(Thread.java:745)
  7. 7 public class DatabusHandler { private void addDetails(Activity activity, EventBuilder

    event) { ... case CHAT_ADD_USERS: for (long userID : activity.getData().getUsers()) { event.addUser(userID); } null?
  8. 8 public class DatabusHandler { private void addDetails(Activity activity, EventBuilder

    event) { ... case CHAT_ADD_USERS: for (long userID : activity.getData().getUsers()) { event.addUser(userID); } null?
  9. 9 public class DatabusHandler { private void addDetails(Activity activity, EventBuilder

    event) { ... case CHAT_ADD_USERS: for (long userID : activity.getData().getUsers()) { event.addUser(userID); } List<Long> null?
  10. 10 java.lang.NullPointerException: called "getUsers()" method on null object at one.app.databus.DatabusHandler.addDetails(DatabusHandler.java:462)

    at one.app.databus.DatabusHandler.handleActivity(DatabusHandler.java:223) at one.app.activity.ActivityService.reportActivity(ActivityService.java:139) at one.app.activity.ActivityService$RunnableActivity.run(ActivityService.java:176) at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.lang.Thread.run(Thread.java:745)
  11. 11

  12. 12

  13. 13

  14. 14 •

  15. 15 •

  16. 16 • •

  17. 17 • •

  18. 18 • • • java –agentpath:path/to/mytool.dll java –javaagent:myagent.jar

  19. None
  20. 20

  21. 21

  22. 22

  23. • • • • 23

  24. 24

  25. 25 #include "jmm.h" src/hotspot/share/include

  26. 26 #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);
  27. 27 #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);
  28. 28

  29. None
  30. 30

  31. 31

  32. 32

  33. 33

  34. 34

  35. 35

  36. 36 • java –agentpath:first.dll –agentpath:second.dll

  37. 37 • • java –agentpath:first.dll –agentpath:second.dll

  38. None
  39. 39 String getLocation() { StackTraceElement caller = Thread.currentThread().getStackTrace()[3]; return caller.getFileName()

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

    + ':' + caller.getLineNumber(); } "AsyncTask.java:14"
  41. 41

  42. 42 String getLocation() { return StackWalker.getInstance().walk(s -> { StackWalker.StackFrame frame

    = s.skip(3).findFirst().get(); return frame.getFileName() + ':' + frame.getLineNumber(); }); } "AsyncTask.java:14"
  43. 43

  44. 44 GetStackTrace(jthread thread, jint start_depth, jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint*

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

    count_ptr) = 3 = 1
  46. 46 public class StackFrame { public static native String getLocation(int

    depth); static { System.loadLibrary("stackFrame"); } }
  47. 47 jint JNI_OnLoad(JavaVM* vm, void* reserved) { vm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_0);

    ... }
  48. 48 jint JNI_OnLoad(JavaVM* vm, void* reserved) { vm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_0);

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

  50. → 50

  51. 51 • • •

  52. 52 • • •

  53. 53 • • • jcmd <pid> JVMTI.agent_load /path/to/agent.dll [arguments]

  54. 54 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved)

    JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved)
  55. None
  56. 56

  57. 57 ByteBuffer buf = ByteBuffer.allocateDirect(1024); ((sun.nio.ch.DirectBuffer) buf).cleaner().clean();

  58. 58 ByteBuffer buf = ByteBuffer.allocateDirect(1024); ((sun.nio.ch.DirectBuffer) buf).cleaner().clean(); Exception in thread

    "main" java.lang.IllegalAccessError: class agent.demo6.PrivateApi (in unnamed module @0x3ac3fd8b) cannot access class jdk.internal.ref.Cleaner (in module java.base)
  59. 59 Field f = String.class.getDeclaredField("value"); f.setAccessible(true); WARNING: An illegal reflective

    access operation has occurred WARNING: Illegal reflective access by agent.demo6.Reflection to field java.lang.String.value WARNING: Please consider reporting this to the maintainers of agent.demo6.Reflection
  60. 60 • • • •

  61. 61 • • • •

  62. None
  63. 63 SampledObjectAlloc(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jobject object, jclass

    object_klass, jlong size) jvmti->SetHeapSamplingInterval(jint sampling_interval)
  64. 64 •

  65. 65 •

  66. 66

  67. 67 • • • •

  68. None