JVM反調調用優化,致使發生大量異常時log4j2線程阻塞

背景

在使用log4j2打日誌時,當發生大量異常時,形成大量線程block問題的問題。java

一個關於log4j2的高併發問題:https://blog.fliaping.com/a-high-concurrency-problem-of-log4j2/apache

 

大量線程block緣由

發生異常,打印異常棧時,會調用org.apache.logging.log4j.core.impl.ThrowableProxy.toExtendedStackTrace方法。api

ThrowableProxy.toExtendedStackTrace內部會進行loadClass操做。併發

而且能夠看到ClassLoader的loadClass在加載類時oracle

1)首先會持有鎖。jvm

2)調用findLoadedClass看下是否類已經被加載過了高併發

3)若是類沒被加載過,根據雙親委派模型去加載類。優化

能夠看到當某個類被加載過了,調用findLoadedClass會直接返回,鎖也會被很快釋放掉,無需通過雙親委派等後面的一系列步驟。spa

 

可是,在進行反射調用時,JVM會進行優化,會動態生成名爲sun.reflect.GeneratedMethodAccessor<N>的類,這個類沒法經過ClassLoader.loadClass方法加載(爲何沒法經過ClassLoader.loadClass加載?由於JVM內部自定義一個加載器DelegatingClassLoader來加載這個類,這致使應用類加載器 Launcher$AppClassLoader找不到它)。線程

致使每次解析異常棧進行類加載時,鎖佔有的時間很長,最終致使阻塞。

 

關於JVM對反射調用的優化

Java中對反射的優化

使用反射調用某個類的方法,jvm內部有兩種方式

  1. JNI:使用native方法進行反射操做。

  2. pure-Java:生成bytecode進行反射操做,即生成類sun.reflect.GeneratedMethodAccessor<N>,它是一個被反射調用方法的包裝類,代理不一樣的方法,類後綴序號會遞增。這種方式第一次調用速度較慢,較之第一種會慢3-4倍,可是屢次調用後速度會提高20倍

對於使用JNI的方式,由於每次都要調用native方法再返回,速度會比較慢。因此,當一個方法被反射調用的次數超過必定次數(默認15次)時,JVM內部會進行優化,使用第2種方法,來加快運行速度。

JVM有兩個參數來控制這種優化

-Dsun.reflect.inflationThreshold=<value>
value默認爲15,即反射調用某個方法15次後,會由JNI的方式變爲pure-java的方式

-Dsun.reflect.noInflation=true

默認爲false。當設置爲true時,表示在第一次反射調用時,就轉爲pure-java的方式

關於如何驗證上面所說的反射優化以及兩個參數的具體做用,能夠參考R大的這篇博客https://rednaxelafx.iteye.com/blog/548536

 

下面是一個驗證反射優化的樣例:

public class TestMethodInvoke { public static void main(String[] args) throws Exception { Class<?> clz = Class.forName("A"); Object o = clz.newInstance(); Method m = clz.getMethod("foo", String.class); for (int i = 0; i < 100; i++) { m.invoke(o, Integer.toString(i)); } } }
public class A { public void foo(String name) { System.out.println("Hello, " + name); } }

配置以下JVM參數,使得在第一次反射調用時,就轉爲pure-java的方式

打斷點跟蹤:

能夠看到GeneratedMethodAccessor1的classLoader爲DelegatingClassLoader,其parent爲AppClassLoader。

 

 

如何關閉JVM對反射調用的優化?

想關閉JVM對反射優化怎麼辦?

JVM中只提供了兩個參數,所以,沒有辦法徹底關閉反射優化。

一種能想到的接近於關閉反射優化的方法就是將inflationThreshold設爲的一個特別大的數。

inflationThreshold是java中的int型值,能夠考慮把其設置爲Integer.MAX_VALUE ((2^31)-1)。

$ java -Dsun.reflect.inflationThreshold=2147483647 MyApp
 

 

參考資料

https://rednaxelafx.iteye.com/blog/548536 R大的博客

https://blogs.oracle.com/buck/inflation-system-properties

相關文章
相關標籤/搜索