В моем агенте я перехватываю monitorEnter
события.До сих пор функция-перехватчик ничего не делает, только сразу возвращается.Поскольку я столкнулся с серьезным влиянием на производительность, я пытался понять, что не так.
В настоящее время я понимаю, что модифицированный байт-код работает, но у JIT есть проблемы с его компиляцией.Я не уверен, почему еще и что было бы лучшим способом исправить это.
Наивный подход к перехвату всех monitorEnter
заключался в DUP
мониторинге, выполнении monitorenter
с последующим INVOKESTATIC
для моего перехватчика, проходящего мимо объекта монитора.Это иногда приводило к IllegalMonitorStateException
.(пока не знаю почему).Затем я изменил кодовую последовательность на monitorEnter
, ALOAD
, INVOKESTATIC
для моего перехватчика.Хотя я не испытал исключение снова, результирующий код не может быть скомпилирован JIT (фактически также версия DUP не может).
Вот пример байт-кода метода, вызывающего проблему (класс com.mysql.jdbc.ResultSetImpl
).Единственный код, добавленный мной, это инструкции на 12 и 13:
protected final void checkColumnBounds(int) throws java.sql.SQLException;
descriptor: (I)V
flags: ACC_PROTECTED, ACC_FINAL
Code:
stack=5, locals=4, args_size=2
0: aload_0
1: invokevirtual #319 // Method checkClosed:()Lcom/mysql/jdbc/MySQLConnection;
4: invokeinterface #323, 1 // InterfaceMethod com/mysql/jdbc/MySQLConnection.getConnectionMutex:()Ljava/lang/Object;
9: dup
10: astore_2
11: monitorenter
12: aload_2
13: invokestatic #329 // Method com/test/bootstrap/Interceptor.monitorEntered:(Ljava/lang/Object;)V
16: iload_1
17: iconst_1
18: if_icmpge 60
21: ldc_w #516 // String ResultSet.Column_Index_out_of_range_low
24: iconst_2
25: anewarray #121 // class java/lang/Object
28: dup
29: iconst_0
30: iload_1
31: invokestatic #470 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
34: aastore
35: dup
36: iconst_1
37: aload_0
38: getfield #236 // Field fields:[Lcom/mysql/jdbc/Field;
41: arraylength
42: invokestatic #470 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
45: aastore
46: invokestatic #519 // Method com/mysql/jdbc/Messages.getString:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
49: ldc_w #521 // String S1009
52: aload_0
53: invokevirtual #506 // Method getExceptionInterceptor:()Lcom/mysql/jdbc/ExceptionInterceptor;
56: invokestatic #512 // Method com/mysql/jdbc/SQLError.createSQLException:(Ljava/lang/String;Ljava/lang/String;Lcom/mysql/jdbc/ExceptionInterceptor;)Ljava/sql/SQLException;
59: athrow
60: iload_1
61: aload_0
62: getfield #236 // Field fields:[Lcom/mysql/jdbc/Field;
65: arraylength
66: if_icmple 108
69: ldc_w #523 // String ResultSet.Column_Index_out_of_range_high
72: iconst_2
73: anewarray #121 // class java/lang/Object
76: dup
77: iconst_0
78: iload_1
79: invokestatic #470 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
82: aastore
83: dup
84: iconst_1
85: aload_0
86: getfield #236 // Field fields:[Lcom/mysql/jdbc/Field;
89: arraylength
90: invokestatic #470 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
93: aastore
94: invokestatic #519 // Method com/mysql/jdbc/Messages.getString:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
97: ldc_w #521 // String S1009
100: aload_0
101: invokevirtual #506 // Method getExceptionInterceptor:()Lcom/mysql/jdbc/ExceptionInterceptor;
104: invokestatic #512 // Method com/mysql/jdbc/SQLError.createSQLException:(Ljava/lang/String;Ljava/lang/String;Lcom/mysql/jdbc/ExceptionInterceptor;)Ljava/sql/SQLException;
107: athrow
108: aload_0
109: getfield #196 // Field profileSql:Z
112: ifne 122
115: aload_0
116: getfield #214 // Field useUsageAdvisor:Z
119: ifeq 131
122: aload_0
123: getfield #164 // Field columnUsed:[Z
126: iload_1
127: iconst_1
128: isub
129: iconst_1
130: bastore
131: aload_2
132: monitorexit
133: goto 141
136: astore_3
137: aload_2
138: monitorexit
139: aload_3
140: athrow
141: return
Exception table:
from to target type
16 133 136 any
136 139 136 any
LocalVariableTable:
Start Length Slot Name Signature
0 142 0 this Lcom/mysql/jdbc/ResultSetImpl;
0 142 1 columnIndex I
LineNumberTable:
line 760: 0
line 761: 16
line 762: 21
line 766: 60
line 767: 69
line 773: 108
line 774: 122
line 776: 131
line 777: 141
StackMapTable: number_of_entries = 6
frame_type = 252 /* append */
offset_delta = 60
locals = [ class java/lang/Object ]
frame_type = 47 /* same */
frame_type = 13 /* same */
frame_type = 8 /* same */
frame_type = 68 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 4 /* same */
Exceptions:
throws java.sql.SQLException
Код asm, используемый в посетителе метода, таков:
if (opcode == MONITORENTER)
{
// super.visitInsn(DUP); // in the beginning I used DUP followed, now ALOAD
super.visitInsn(opcode);
super.visitVarInsn(ALOAD, lastAStoreIndex);
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Interceptor.class), "monitorEntered", "(Ljava/lang/Object;)V", false);
}
В результате метод кажется небыть JIT-совместимым.-XX:+PrintCompilation
показывает:
21938 619 ! 3 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes)
21938 619 ! 3 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) COMPILE SKIPPED: invalid parsing (retry at different tier)
!m @ 6 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 13 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
22105 716 ! 4 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes)
22105 716 ! 4 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) COMPILE SKIPPED: cannot parse method (not retryable)
!m @ 6 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 13 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 62 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 13 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 6 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 13 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 13 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 6 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
!m @ 62 com.mysql.jdbc.ResultSetImpl::checkColumnBounds (142 bytes) not compilable (disabled)
Я знаю, что инструкции, которые я добавляю, ничего не дают javac
сгенерирует, но так как это действительный байт-код (по крайней мере, я так думал, и пример работает), я предполагалчто JIT может справиться с этим.Кажется, однако, что JIT ищет некоторые хорошо известные паттерны.Интересно, как другие языки на основе JVM справляются с этим?Всегда ли им нужно создавать один и тот же / подобный байт-код, который генерирует javac
?
Единственное теоретическое решение, которое я сейчас имею в виду, - это попытаться придумать javac
подобный байт-код, который имеетКонечно, сложнее, чем то, что я пытался сделать здесь, так как мне нужно было бы сохранить объект монитора в новой локальной переменной, а затем загрузить его оттуда до monitorEnter
и сделать то же самое еще раз до вызова моего перехватчика.Поэтому мне нужно будет либо перейти на API asm tree (чтобы вернуться снова), либо посмотреть, смогу ли я буферизовать инструкции, чтобы все еще иметь возможность реагировать соответствующим образом в случае, если я достигну monitorEnter
.Любые другие предложения, которые могут быть немного легче реализовать?