zoukankan      html  css  js  c++  java
  • 记一次JAVA FULL GC问题处理【3】

    1:  上次的定位原因找到了后,再回过头来看我们的代码问题。实现如下的日志打印格式

      其实通过log4j2 的PatternLayout 配置基本都能实现, 但有一个关键点,我们是WEB应用,希望一次请求通过一个比较唯一的会话号来跟踪。方便作ES根踪。

    2: 代码大约如下,提供了多个静太的info,warn 代码。

     

     3: 以上代码,确认达到了输出日志号(会话号)的目的,但考虑到日志输出的频繁性,觉得这样做不好。

         不好的点主要在于会有多余的内存创建和销毁开销。列举Log4j原始 API 参数,详细问题列举如下: 

        1)接口上问题 info(String msg) 参数为String ,这要求把其它的参数全部转换成String, 如Int ->转String。 特别对于StringBuilder转String 里面的char[] 是会重新生成的。带来极大的内存开销。

          StringBuilder sb=new StringBuilder("").append("123");

          Log.info(sb.toString);

       2)另外就是关于StringBuilder的拼接,这里将 5个String 对象拼接,生成一个StringBuilder。 但这个StringBuilder 的使用,已经很大程度上让Log4j的内存复用优化打大折扣。

    4: 改进策略:

    <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss,SSS}] %-5p %m%n" />  修改前的配置

    <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss,SSS}] %-5p %l[%y]: %m%n" /> 修改后的配置
    @Plugin(name = "LogIdPatternConverter", category = PatternConverter.CATEGORY)
    @ConverterKeys({ "y", "logId" })
    public class LogIdPatternConverter extends LogEventPatternConverter {

    private static final LogIdPatternConverter INSTANCE =
    new LogIdPatternConverter();

    public static LogIdPatternConverter newInstance(
    final String[] options) {
    return INSTANCE;
    }

    private LogIdPatternConverter(){
    super("LogId", "logId");
    }

    @Override
    public void format(LogEvent event, StringBuilder toAppendTo) {
    toAppendTo.append(LogUtil.getThreadLogId());
    // toAppendTo.append(LogUtil.name.get()).append("[").append(LogUtil.getThreadLogId()).append("]:");
    }


    }


    5: 写测试代码
    @Test
    public void testLogBefore() {
    long begin = System.currentTimeMillis();
    for(int i=0; i<5000; i++){
    Log.info("I will print you");

    }
    long over = System.currentTimeMillis();
    System.out.println("操作testLogBefore类型使用的时间为:"+(over-begin)+"毫秒");
    }

    @Test
    public void testLogAfter () {
    LogUtil.initThreadLogId();
    long begin = System.currentTimeMillis();
    for(int i=0; i<5000; i++){
    LogUtil.info("I will print you");
    }
    long over = System.currentTimeMillis();
    System.out.println("操作testLogAfter类型使用的时间为:"+(over-begin)+"毫秒");
    }
    操作testLogBefore类型使用的时间为:615毫秒

    操作testLogAfter类型使用的时间为:339毫秒
    这里有所提升,但对于%l 操作的性能耗损很大。
    5: 原因分析: 可这个代码位置的代码,这个代码为了获取堆栈信息 是通过new Exception 方式获得, 有clone等操作。导致信息量过大。从而性能比较差。

    http://logging.apache.org/log4j/2.x/performance.html#asyncLoggingWithLocation

     

    大概也就是说日志格式使用这些参数的使用会导致性能的急剧下降
    C or $class, %F or %file, %l or %location, %L or %line, %M or %method
    大概下降30-100倍左右,这才找到真正的原因,原来同步和异步的性能影响是一部分,本身日志格式的配置也是存在问题的。

    较为影响性能的日志输出参数
    %C - 调用者的类名(速度慢,不推荐使用);
    %F - 调用者的文件名(速度极慢,不推荐使用)
    %l - 调用者的函数名、文件名、行号(速度极其极其慢,不推荐使用)
    %L - 调用者的行号(速度极慢,不推荐使用)
    %M - 调用者的函数名(速度极慢,不推荐使用)

    6:  进一步修改

    <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss,SSS}] %-5p [%y] %m%n" />
    public class LogUtil {
    private static Logger log = ((LoggerContext) LogManager.getContext(false)).getLogger(com.chinamobile.iot.component.log.Log.class.getName());
    private static Map<String, Level> logLevels = new HashMap(8);
    static ThreadLocal<String> threadLocal = new ThreadLocal();
    static ThreadLocal<String> name = new ThreadLocal();
    private static Random random;

    private LogUtil() {
    }

    public static void debug(String msg) {
    if (log.isDebugEnabled()) {
    log.debug(msg);
    }

    }

    public static void debug(String msg, String logId) {
    if (log.isDebugEnabled()) {
    log.debug(msg);
    }

    }

    public static void info(String msg) {
    if (log.isInfoEnabled()) {
    name.set(getLogTrace());
    log.info(msg);
    }

    }
    。。。。。。。
    }

     

    @Test
    public void testLogBefore() {
    long begin = System.currentTimeMillis();
    for(int i=0; i<5000; i++){
    Log.info("I will print you");

    }
    long over = System.currentTimeMillis();
    System.out.println("操作testLogBefore类型使用的时间为:"+(over-begin)+"毫秒");
    }

    @Test
    public void testLogAfter () {
    LogUtil.initThreadLogId();
    long begin = System.currentTimeMillis();
    for(int i=0; i<5000; i++){
    LogUtil.info("I will print you");
    }
    long over = System.currentTimeMillis();
    System.out.println("操作testLogAfter类型使用的时间为:"+(over-begin)+"毫秒");
    }

    操作testLogBefore类型使用的时间为:627毫秒

    操作testLogAfter类型使用的时间为:156毫秒

    对于logId, 方法名,内存复用方案,在当前情况下测试的比较好的结果。

  • 相关阅读:
    DTN相关研究机构介绍之Comnet@tkk
    Contact
    MyEclips注册码生成
    对list排序,按名字排,如果名字一样就按年龄排
    在JSP页面中获取当前日期时间
    javascript全局变量怎么定义?
    理解了java一次编译多处运行的意思了
    HTML <td> 标签的 nowrap 属性
    <! >
    java.io.File中的绝对路径和相对路径.
  • 原文地址:https://www.cnblogs.com/liujianping/p/14792523.html
Copyright © 2011-2022 走看看