Оригинальная ссылка:Блог. Отправить две бутылки. Com / Ah-High-Con Color ...
предисловие
Журналы должны быть основными компонентами приложения, среди которых старый log4j должен быть тем, который мы используем больше всего.Позже, с развитием Интернета, все перешли к высокому параллелизму и обнаружили, что производительность log4j не очень хорошая, потому что конкурирующие блокировки приводят к блокировке, и производительность не может поддерживаться. Вверх. Позже, после того, как его основатель установил отдельный журнал журнала портала, новый главный Apache log4j претерпел серьезное обновление и преобразование, которое является сегодняшним log4j2, и его производительность асинхронного журнала также не имеет себе равных,но ям в его использовании все равно много.Если не обращать внимания,сделаешь личную очередь.Перечислим некоторые ямки log4j,на которые наступили другие:
описание проблемы
основная производительность
Служба push-уведомлений APP, после выпуска, при встрече с пакетными push-задачами, пул потоков заполнен, очередь заполнена, а задачи накапливаются (Примечание: поскольку изменения в этом выпуске очень распространены, я не подозревал этот пункт в первую очередь)
Связанные среды
- spring-boot: 1.5.3
- log4j: 2.7
- jdk1.8
Разместить точки изменения
- Добавлена функция (не относящаяся к данному контенту)
- добавлен в класс
@RefreshScope
аннотация
Технологический поток
- Первоначально оно вызвано сторонним интерфейсом. Мониторинг показывает, что дисплей действительно высок, но это не проблема интерфейса с Ping, Curl и мониторингом сети.
- Прочтите код и обнаружите, что элемент мониторинга включает в себя не только трудоемкий вызов стороннего интерфейса, но и другие операции, поэтому я считаю, что мониторинг не заслуживает доверия, а проверка не является сторонним интерфейсом или проблема с сетью.
- Выяснено, что разница между двумя временными точками в статистическом журнале относительно велика и составляет три секунды.Ключ в том, что между этими двумя временными точками было выбрано только исключение, а затем был записан журнал.
- В коде обнаружено, что конфигурация log4j2 использует синхронный Logger и асинхронный AsyncAppender, есть подозрение, что синхронный лог блокируется во время high concurrency, лог меняется на AsyncLogger с дисраптором, но результат бесполезен.
- Выполните дамп потока, чтобы обнаружить, что многие потоки заблокированы, отображение стека вызовов действительно связано с log4j и явно указывает на печать стека исключений.
- Так как предупреждений в приложении много, и эти логи все выводят стек исключений, так что перестаньте печатать ненужный стек исключений в лог, и тогда проблема решится
анализ проблемы
вопрос о вопросе
- Почему журнал не печатается асинхронно?
- Почему блокируется печать стека исключений?Где блок?
- Почему это вызывает блокировку и как это связано с опубликованной точкой изменения?
Анализ стека потоков
Следующий журнал представляет собой блокирующий стек потока после перехода на асинхронный журнал (AsyncLogger).Можно обнаружить, что журнал не использует безблокировочную библиотеку разрушителя, а преобразуется в синхронный Logger.
"fastExecutor-670" #2178 prio=5 os_prio=0 tid=0x00007f2d483cd800 nid=0x6026 waiting for monitor entry [0x00007f2c158c3000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
- waiting to lock <0x0000000088104b60> (a java.lang.Object)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:94)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.apache.logging.log4j.core.impl.ThrowableProxy.loadClass(ThrowableProxy.java:539)
at org.apache.logging.log4j.core.impl.ThrowableProxy.toExtendedStackTrace(ThrowableProxy.java:660)
at org.apache.logging.log4j.core.impl.ThrowableProxy.<init>(ThrowableProxy.java:137)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:94)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.apache.logging.log4j.core.impl.ThrowableProxy.loadClass(ThrowableProxy.java:539)
at org.apache.logging.log4j.core.impl.ThrowableProxy.toExtendedStackTrace(ThrowableProxy.java:660)
at org.apache.logging.log4j.core.impl.ThrowableProxy.<init>(ThrowableProxy.java:137)
at org.apache.logging.log4j.core.impl.ThrowableProxy.<init>(ThrowableProxy.java:121)
at org.apache.logging.log4j.core.impl.Log4jLogEvent.getThrownProxy(Log4jLogEvent.java:555)
at org.apache.logging.log4j.core.pattern.ExtendedThrowablePatternConverter.format(ExtendedThrowablePatternConverter.java:61)
... 精简部分日志 ...
at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:447)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.callAppendersInCurrentThread(AsyncLoggerConfig.java:105)
at org.apache.logging.log4j.core.async.EventRoute$2.logMessage(EventRoute.java:65)
at org.apache.logging.log4j.core.async.AsyncLoggerConfig.callAppenders(AsyncLoggerConfig.java:95)
at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:432)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:416)
at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:402)
at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:63)
at org.apache.logging.log4j.core.Logger.logMessage(Logger.java:146)
at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2091)
at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:1988)
at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1960)
at org.apache.logging.slf4j.Log4jLogger.warn(Log4jLogger.java:259)
at com.dianwoda.delibird.common.traffic.FastCommander.lambda$runnersEnter$0(FastCommander.java:175)
at com.dianwoda.delibird.common.traffic.FastCommander?Lambda$111/638548222.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Ниже приведено содержимое стека исключений приложения.
com.dianwoda.delibird.common.domain.DeliException:
at com.dianwoda.delibird.push.manager.PushManager.send(PushManager.java:104) ~[classes!/:?]
at sun.reflect.GeneratedMethodAccessor119.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_77]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_77]
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:216) ~[spring-core-4.3.15.RELEASE.jar!/:4.3.15.RELEASE]
at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:472) ~[spring-cloud-context-1.3.3.RELEASE.jar!/:1.3.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.15.RELEASE.jar!/:4.3.15.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673) ~[spring-aop-4.3.15.RELEASE.jar!/:4.3.15.RELEASE]
at com.dianwoda.delibird.push.manager.PushManager?EnhancerBySpringCGLIB?74851038.send(<generated>) ~[classes!/:?]
at com.dianwoda.delibird.push.provider.DeliPushProviderImpl.lambda$send$0(DeliPushProviderImpl.java:84) ~[classes!/:?]
at com.dianwoda.delibird.common.tool.DeliRunnable.run(DeliRunnable.java:26) ~[delibird-common-1.0-SNAPSHOT.jar!/:?]
at com.dianwoda.delibird.common.traffic.FastCommander.lambda$runnersEnter$0(FastCommander.java:173) ~[delibird-common-1.0-SNAPSHOT.jar!/:?]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_77]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_77]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_77]
Вопрос один
Почему журнал не печатается асинхронно?
По умолчанию Log4j2 сначала попытается поместить LogEvent в RingBuffer, если это невозможно (причина, вероятно, в том, что очередь заполнена), решение о следующем действии будет принято через AsyncQueueFullPolicy (настраиваемый) (EventRoute: discard-discard, sync -синхронный, ожидание постановки в очередь — поставить в очередь), в log4j-2.7 по умолчанию запись в лог выполняется синхронно (по умолчанию в 2.9 ожидание постановки в очередь), поэтому, когда объем лога внезапно увеличивается, асинхронный лог становится синхронным журналом!
Вопрос второй
Почему блокируется печать стека исключений?Где блок?
Согласно стеку потоков, проблема в основном потому, чтоorg.apache.logging.log4j.core.impl.ThrowableProxy.toExtendedStackTrace
этот метод, гдеThrowableProxy
Этот класс связан с тем, что LogEvent может передаваться по сети, а стек исключений в LogEvent может быть не распознан другим концом, поэтому стек исключений необходимо переупаковать, а пакет jar исключений и информация о версии в стеке будут удалить. Класс аннотируется следующим образом:
Упаковка метательного объекта и добавление информации о пакете для каждого элемента отслеживания стека.
Используется для указания агента в другом ClassLoader Throwable или JVM при применении десериализации ThrowableProxy, Throwable может не предоставляться, но информация Throwable хранится в других полях агента, таких как сообщение и трассировка стека.
Кроме того, этот метод предназначен для «анализа всех записей стека в этой трассировке стека, которые отличаются от родительского элемента». Короче говоря, он сравнивает стек исключений со стеком текущего потока и анализирует различную информацию о классах в стеке исключений. , включая имя класса, номер строки, имя пакета, версию пакета и другую информацию.В настоящее время вам необходимо получить объект класса в соответствии с именем класса в стеке исключений, и вам необходимо загрузить класс.Проанализируйте загрузку класса процесс согласно стеку потоков, вы можете увидеть заблокированное место ожидания блокировки, именно из-за ожидания блокировки операция печати журнала занимает 3 с.
Ниже приведен стек заблокированного потока:
at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
- waiting to lock <0x0000000088104b60> (a java.lang.Object)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:94)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.apache.logging.log4j.core.impl.ThrowableProxy.loadClass(ThrowableProxy.java:539)
at org.apache.logging.log4j.core.impl.ThrowableProxy.toExtendedStackTrace(ThrowableProxy.java:660)
Ниже приведен фрагмент кода для загрузки класса для получения блокировки:code-1
// java.lang.ClassLoader#loadClass(java.lang.String, boolean)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) { // 源码行号:404
//(1. 注: 先从jvm检查下类加载过没有,已加载就直接返回该对象,防止类重复加载)
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 2. 如果该类加载器有父对象,先用父加载器加载,这就是双亲委派机制
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 3. 如果父加载器加载不到,就用引导加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 4. 如果前面双亲都没有加载到, 采用当前自定义的findClass加载
c = findClass(name); // 源码行号:424
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
Видно, что когда класс загружен, он сначала проверит, был ли он загружен, а затем сразу вернет объект класса в jvm после загрузки.Если он должен получить загруженный класс, он должен быть очень быстрым, потому что это только операция с памятью, полученная блокировка будет снята сразу.В случае тысяч QPS блокировка на 3 секунды невозможна.Так класс в это время был загружен?
Вы можете увидеть, чтобы получить замок0x0000000088104b60
Стек нитки:
"fastExecutor-671" #1739 prio=5 os_prio=0 tid=0x00007f2d00015000 nid=0x5e64 runnable [0x00007f2c237f9000]
java.lang.Thread.State: RUNNABLE
at sun.misc.URLClassPath$Loader.getResource(URLClassPath.java:702)
at sun.misc.URLClassPath.getResource(URLClassPath.java:212)
at java.net.URLClassLoader$1.run(URLClassLoader.java:365)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
- locked <0x0000000088104b60> (a java.lang.Object)
at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:94)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.apache.logging.log4j.core.impl.ThrowableProxy.loadClass(ThrowableProxy.java:539)
... 精简部分内容 ...
В соответствии с приведенным выше стеком потоков, после получения блокировки, перейти к строке 424 исходного кода, что означает, что класс не был загружен в jvm, и родительский загрузчик не может быть загружен, поэтому вызовитеfindClass
Перейти к загрузке.Это очень странно.Если jvm загрузится один раз, то в следующий раз она будет получена напрямую из jvm.В сочетании с блокировкой, упомянутой выше, она будет разблокирована сразу, и она не будет блокироваться вообще.То есть чтобы сказать, что его действительно нужно перезагружать каждый раз, чтобы узнать, какой класс нужно загружать каждый раз, необходимо отладить, чтобы определить.
Ниже log4j2ThrowableProxy
Код процесса загрузки класса:code-2, где вызывается загрузка класса.
// org.apache.logging.log4j.core.impl.ThrowableProxy#loadClass(java.lang.String)
/**
* Loads classes not located via Reflection.getCallerClass.
*
* @param lastLoader The ClassLoader that loaded the Class that called this Class.
* @param className The name of the Class.
* @return The Class object for the Class or null if it could not be located.
*/
private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
// XXX: this is overly complicated (注:确实过于复杂,哈哈哈)
Class<?> clazz;
if (lastLoader != null) {
try {
// 1. 先用ClassLoader加载一下, 加载上就返回
clazz = lastLoader.loadClass(className); // 源码行号: 539
if (clazz != null) {
return clazz;
}
} catch (final Throwable ignore) {
// Ignore exception.
}
}
try {
// 2. 上一步没加载上,或者出现异常,用LoaderUtil再次加载(使用Class.forName以及当前线程的ClassLoader)
clazz = LoaderUtil.loadClass(className);
} catch (final ClassNotFoundException | NoClassDefFoundError e) {
// 3. 加载出现异常,再次尝试一种加载方式
return loadClass(className);
} catch (final SecurityException e) {
return null;
}
return clazz;
}
// 4. 接上面的3, 再次用当前对象类的加载器加载,出现异常返回空
private Class<?> loadClass(final String className) {
try {
return this.getClass().getClassLoader().loadClass(className);
} catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
return null;
}
}
процесс отладки
Пусть код выполняется первым и пройдите проблемный процесс несколько раз, чтобы гарантировать, что загруженный класс загружен, а затемcode-1серединаfindLoadedClass
точку останова в методе и посмотрите, пусто ли возвращаемое значение.code-2серединаlastLoader
ТипLaunchedURLClassLoader
Отношения наследования следующие
loadClass
Такие методы, как:code-3 : org.springframework.boot.loader.LaunchedURLClassLoader#loadClass
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Handler.setUseFastConnectionExceptions(true);
try {
try {
definePackageIfNecessary(name);
}
catch (IllegalArgumentException ex) {
// Tolerate race condition due to being parallel capable
if (getPackage(name) == null) {
// This should never happen as the IllegalArgumentException indicates
// that the package has already been defined and, therefore,
// getPackage(name) should not return null.
throw new AssertionError("Package " + name + " has already been "
+ "defined but it could not be found");
}
}
return super.loadClass(name, resolve);
}
finally {
Handler.setUseFastConnectionExceptions(false);
}
}
воплощать в жизньlastLoader.loadClass(className)
Прыжокcode-1, весь процесс загрузки следует за механизмом родительской делегирования, как показано ниже
LaunchedURLClassLoader — это настраиваемый загрузчик классов, который напрямую вызывает родительский класс.ClassLoader#loadClass
которыйcode-1Как показано в , используйте «загрузчик класса приложения», «загрузчик класса расширения», «загрузчик класса загрузки» для загрузки и, наконец, обнаружил, что при появлении имени классаsun.reflect.GeneratedMethodAccessor204
После родительских загрузчиков загрузчик начальной загрузки, URLClassLoader#findClass не может быть загружен и, наконец, брошенClassNotFoundException
одеялоcode-2Захватите и проигнорируйте шаг 1, затем перейдите к шагу 2, пытаясь загрузить, затем выдается исключение, после попытки захвата снова загрузиться на шаге 3, ненормальный снова возвращается пустым.
такие как журнал исключенияat sun.reflect.GeneratedMethodAccessor119.invoke(Unknown Source) ~[?:?]
, так как класс не может быть загружен, информация об исходном коде и информация о пакете недоступны.
эпизод:В процессе отладки я перезапустил приложение и обнаружил, что прежняя логика отладки больше не может быть достигнута, вроде бы все нормально, и нет классов, которые нельзя было бы загрузить. Затем по ошибке я использовал скрипт пакетного вызова для его вызова, а затем проблема снова появилась
готовится увидетьsun.reflect.GeneratedMethodAccessor204
Почему этот класс не может быть загружен, я обнаружил, что такого класса вообще нет, и я подозреваю, что это класс, сгенерированный отражением, когда я смотрю на имя класса. В дополнение к предыдущему эпизоду, помимо чужих напоминаний и гугления, было установлено, что первопричиной на самом деле была стратегия оптимизации JVM для отражения.
JVM оптимизация отражения
Есть два способа обращения с JVM для отражения:
- Используйте нативный метод для операций отражения, скорость которого каждый раз примерно одинакова.
- Сгенерировать байт-код для операции отражения, то есть сгенерировать класс
sun.reflect.GeneratedMethodAccessor<N>
, который является классом-оболочкой для метода, вызываемого отражением, проксирующим разные методы, и номер суффикса класса будет увеличиваться. Этот метод медленнее при первом вызове, в 3-4 раза медленнее первого метода, но скорость будет увеличена в 20 раз после многократного вызова
существуетReflectionFactory
Есть механизм, то есть, когда количество вызовов метода отражением превышает определенный порог (inflationThreshold), для повышения скорости будет использоваться второй метод. Значение этого порога по умолчанию равно 15. Порог можно передать через параметр jvm.-Dsun.reflect.inflationThreshold
настроить.
Так почему же log4j2 не может быть загружен в сгенерированный класс?sun.reflect.GeneratedMethodAccessor<N>
Шерстяная ткань?
Чтобы ответить на этот вопрос, нам нужно понять второй способ реализации отражения jvm, jvm будет передавать методsun.reflect.ReflectionFactory#newMethodAccessor
Создайте MethodAccessor, и прокси-сервер вызовет реальный метод через метод вызова объекта. На следующем рисунке показан соответствующий класс реализации интерфейса MethodAccessor.
ReflectionFactory#newMethodAccessor
код показывает, как показано ниже:code-4
public MethodAccessor newMethodAccessor(Method method) {
checkInitted();
// noInflation(不膨胀),直接使用字节码增强方式
if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
// 否则使用Inflation膨胀模式, 先创建NativeMethodAccessorImpl,随后将该实现作为DelegatingMethodAccessorImpl的一个delegate,实际上还是委派给NativeMethodAccessorImpl
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
sun.reflect.NativeMethodAccessorImpl#invoke
код показывает, как показано ниже:code-5
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
// We can't inflate methods belonging to vm-anonymous classes because
// that kind of class can't be referred to by name, hence can't be
// found from the generated bytecode.(我们不能膨胀属于vm-anonymous的类,因为这种类不能通过名字引用,因此不能从生成的字节码中被发现)
//
// 这里可以看到,如果调用次数大于inflationThreshold就会膨胀,使用字节码增强的方式
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}
// 没有超过膨胀阈值,就使用JNI方法
return invoke0(method, obj, args);
}
Продолжайте смотреть на код, вы можете увидетьsun.reflect.MethodAccessorGenerator#generate
Реализация заключается в вызове инструмента расширения байт-кода asm для создания класса, что является длительным процессом и здесь не приводится. В конце метода находим, что есть такая операцияsun.reflect.ClassDefiner#defineClass
, просмотреть его исходный код
Код defineClass выглядит следующим образом:code-6
/** <P> We define generated code into a new class loader which
delegates to the defining loader of the target class. It is
necessary for the VM to be able to resolve references to the
target class from the generated bytecodes, which could not occur
if the generated code was loaded into the bootstrap class
loader. </P>
<P> There are two primary reasons for creating a new loader
instead of defining these bytecodes directly into the defining
loader of the target class: first, it avoids any possible
security risk of having these bytecodes in the same loader.
Second, it allows the generated bytecodes to be unloaded earlier
than would otherwise be possible, decreasing run-time
footprint. </P>
*/
static Class<?> defineClass(String name, byte[] bytes, int off, int len,
final ClassLoader parentClassLoader)
{
// 创建一个DelegatingClassLoader用来加载生成的类
ClassLoader newLoader = AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return new DelegatingClassLoader(parentClassLoader);
}
});
return unsafe.defineClass(name, bytes, off, len, newLoader, null);
}
С помощью приведенного выше кода и комментариев обнаружено, что сгенерированный класс связан вDelegatingClassLoader
На этом загрузчике могут быть загружены только классы, сгенерированные этим загрузчиком,Однако в log4jThrowableProxy#loadClass
метод не пытается использовать загрузчик классов, так что это нормально, что он не может быть загружен.
Вопрос третий
Почему это вызывает блокировку и как это связано с опубликованной точкой изменения?
На этом этапе анализа причина блокировки ясна, потому что jvm оптимизирует отражение, так что динамически сгенерированные классы не всегда могут быть загружены через classLoader, поэтому каждый раз, когда анализируется стек исключений, загрузка классов будет выполнено, и из-за родительского делегирования иThrowableProxy#loadClass
Обработка множественных исключений, вызванная временем удержания блокировки, очень длительна, что в конечном итоге приводит к блокировке.
Что касается точки смены релиза, то добавлено@RefreshScope
Аннотация, при сравнении лога стека исключений с добавлением и без, разница тоже найдена.
нет добавления@RefreshScope
Аннотированная степень исключения:
com.dianwoda.delibird.common.domain.DeliException:
at com.dianwoda.delibird.push.manager.PushManager.send(PushManager.java:104) ~[classes/:?]
at com.dianwoda.delibird.push.provider.DeliPushProviderImpl.lambda$send$0(DeliPushProviderImpl.java:84) ~[classes/:?]
at com.dianwoda.delibird.common.tool.DeliRunnable.run(DeliRunnable.java:26) ~[classes/:?]
at com.dianwoda.delibird.common.traffic.FastCommander.lambda$runnersEnter$0(FastCommander.java:174) ~[classes/:?]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_161]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_161]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_161]
добавлять@RefreshScope
Аннотированный стек исключений:
com.dianwoda.delibird.common.domain.DeliException:
at com.dianwoda.delibird.push.manager.PushManager.send(PushManager.java:104) ~[classes/:?]
at sun.reflect.GeneratedMethodAccessor204.invoke(Unknown Source) ~[?:?]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_161]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_161]
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:216) ~[spring-core-4.3.15.RELEASE.jar:4.3.15.RELEASE]
at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:472) ~[spring-cloud-context-1.3.3.RELEASE.jar:1.3.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.15.RELEASE.jar:4.3.15.RELEASE]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673) ~[spring-aop-4.3.15.RELEASE.jar:4.3.15.RELEASE]
at com.dianwoda.delibird.push.manager.PushManager?EnhancerBySpringCGLIB?14df0707.send(<generated>) ~[classes/:?]
at com.dianwoda.delibird.push.provider.DeliPushProviderImpl.lambda$send$0(DeliPushProviderImpl.java:84) ~[classes/:?]
at com.dianwoda.delibird.common.tool.DeliRunnable.run(DeliRunnable.java:26) ~[classes/:?]
at com.dianwoda.delibird.common.traffic.FastCommander.lambda$runnersEnter$0(FastCommander.java:174) ~[classes/:?]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_161]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_161]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_161]
Принцип @RefreshScope
Эта аннотация используется в spring-cloud для обновления bean-компонентов после обновления конфигурации по следующему принципу:
- Аннотация @Scope встроена в @RefreshScope, который используется для механизма Spring Scope, предназначенного для разделения компонентов на разные типы и управления возвратом компонентов через BeanFactory.
- пройти через
RefreshScope
Метод родительского классаGenericScope#postProcessBeanFactory
Зарегистрируйтесь в beanFactory и реализуйте интерфейс Scope.GenericScope#get
метод, который будет помещен в getStandardScopeCache
В кеше он фактически объявляетRefreshScope
Все компоненты лениво загружаются, создаются и кэшируются при первом использовании. - при звонке
RefreshScope#refresh
метод, удалите предыдущий компонент из кеша перед выполнениемGenericScope.BeanLifecycleWrapper#destroy
Bean-метод полного завершения жизненного цикла перед публикациейRefreshScopeRefreshedEvent
событие. - Когда вызывается метод компонента, поскольку компонент является прокси-сервером, он вызывает
org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept
метод, метод вызоветorg.springframework.aop.target.SimpleBeanTargetSource#getTarget
Регенерировать bean-компонент вStandardScopeCache
, тем самым реализуя обновление bean-компонента
Из приведенного принципа видно, что бин с аннотацией @RefreshScope является прокси.Не сложно понять, почему используется рефлексия, а тут проблема возникает из-за оптимизации рефлексии jvm
Суммировать
На самом деле причин этой проблемы много:
- На поверхности кажется, что
@RefreshScope
вызвано аннотацией - С его собственной точки зрения, он столкнулся со слишком большим количеством стеков исключений.
- Из log4j это
log4j 2.7
Стратегия обработки по умолчанию после заполнения очереди - Кроме того, для log4j, похоже, это действительно место, которое необходимо оптимизировать, для динамически генерируемых классов необходимо выполнить специальную обработку и выбрать правильный загрузчик классов, либо не выполнять загрузку классов.
Ссылаться на
- Анализ реализации @RefreshScope и изменений среды в облаке Spring
- Как Spring Cloud реализует горячие обновления
- Повысьте производительность службы с помощью асинхронного ведения журнала
- Глубокое погружение в загрузчики классов Java
- how to suppress the generation of DelegatingClassLoader
- Потенциальные проблемы с использованием памяти с загрузчиками классов прокси-сервера отражения