成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

接口從4秒到200毫秒-小小的日志竟能引發(fā)如此問(wèn)題

開發(fā) 前端
我們的業(yè)務(wù)需對(duì)接不同渠道以完成線上回收業(yè)務(wù),然而各渠道皆有其獨(dú)特的質(zhì)檢標(biāo)準(zhǔn)。為此,我們需對(duì)質(zhì)檢標(biāo)準(zhǔn)予以統(tǒng)一化處理,將外部標(biāo)準(zhǔn)轉(zhuǎn)化為內(nèi)部可識(shí)別的質(zhì)檢標(biāo)準(zhǔn)。

1 問(wèn)題背景

我們的業(yè)務(wù)需對(duì)接不同渠道以完成線上回收業(yè)務(wù),然而各渠道皆有其獨(dú)特的質(zhì)檢標(biāo)準(zhǔn)。為此,我們需對(duì)質(zhì)檢標(biāo)準(zhǔn)予以統(tǒng)一化處理,將外部標(biāo)準(zhǔn)轉(zhuǎn)化為內(nèi)部可識(shí)別的質(zhì)檢標(biāo)準(zhǔn)。在此場(chǎng)景下,我們提供了標(biāo)準(zhǔn)映射功能,將外部質(zhì)檢項(xiàng)與內(nèi)部質(zhì)檢項(xiàng)進(jìn)行關(guān)聯(lián)。此次問(wèn)題源于映射關(guān)系極為復(fù)雜,在導(dǎo)入映射關(guān)系后,發(fā)現(xiàn)映射邏輯耗時(shí)嚴(yán)重,最終定位至日志打印問(wèn)題。接下來(lái),讓我們一同探究如何定位到日志問(wèn)題以及為何輸出大量日志會(huì)致使接口變慢。

2 問(wèn)題驗(yàn)證

在測(cè)試環(huán)境中維護(hù)某渠道質(zhì)檢項(xiàng)與內(nèi)部質(zhì)檢項(xiàng)映射關(guān)系15000+;

發(fā)現(xiàn)模版質(zhì)檢轉(zhuǎn)換接口耗時(shí)4s左右;

圖片圖片

圖片

將質(zhì)檢項(xiàng)的映射關(guān)系減少到100+時(shí);

模版質(zhì)檢轉(zhuǎn)換接口耗時(shí)到了100毫秒左右。

圖片圖片

圖片圖片

發(fā)現(xiàn)模版映射接口時(shí)長(zhǎng)確實(shí)與映射項(xiàng)多少有很強(qiáng)的關(guān)聯(lián)性。

3 問(wèn)題初步定位

通過(guò)阿里的Arthas工具的trace命令可以分析方法的耗時(shí)。

圖片圖片

經(jīng)觀察,該方法的耗時(shí)主要集中于打印日志。查看代碼得知,在將外部質(zhì)檢項(xiàng)映射為內(nèi)部質(zhì)檢項(xiàng)的過(guò)程中會(huì)產(chǎn)生大量日志,且映射關(guān)系越多,日志數(shù)量越多。因項(xiàng)目急于上線,故先對(duì)打印日志的代碼進(jìn)行注釋,隨后發(fā)現(xiàn)接口的響應(yīng)速度明顯變快。

4 初步思考

4.1 有沒(méi)有可能日志打印配置的是同步打印?

首先考慮是否為同步打印日志,畢竟同步讀寫文件會(huì)涉及磁盤隨機(jī)讀寫,進(jìn)而影響效率。經(jīng)查看 log4j 的配置,得知打印日志的配置為異步打印,而非同步打印。

4.2 既然是異步打印,有沒(méi)有可能是多線程爭(zhēng)搶資源導(dǎo)致打印日志過(guò)慢呢?

排除同步打印日志的原因后,由于起初并不了解異步日志打印的底層實(shí)現(xiàn)。但依據(jù)經(jīng)驗(yàn),因需異步打印日志,需有地方存儲(chǔ)需要打印的日志,故而可能涉及資源爭(zhēng)搶。基于此想法,創(chuàng)建一個(gè)測(cè)試方法,進(jìn)行單線程循環(huán)打印日志2萬(wàn)次操作。

@Test
private void test4(){
    long l = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
      log.info(i+"");
    }
    System.out.println("打印日志耗時(shí):" + (System.currentTimeMillis() - l));
}

發(fā)現(xiàn)打印日志耗時(shí)為 600 多毫秒。這就感覺(jué)很奇怪,如果僅僅存儲(chǔ) 20000 個(gè)需要打印的日志任務(wù)需要這么長(zhǎng)時(shí)間嗎?

于是,搞了一個(gè)線程池,提交20000個(gè)任務(wù)也查看一下耗時(shí);

public static void main(String[] args) {
        long l = System.currentTimeMillis();
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(20000));
        for (int i = 0; i < 20000; i++) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    
                }
            });
        }
        System.out.println("打印耗時(shí):" + (System.currentTimeMillis() - l));
    }

發(fā)現(xiàn)只需要2,30毫秒。

在單線程的環(huán)境中,若打印大量日志,速度也極為緩慢,由此可見,并非是多線程的問(wèn)題所致。

我們都清楚,線程池在提交任務(wù)時(shí),若隊(duì)列已滿且達(dá)到最大線程數(shù),便會(huì)執(zhí)行拋棄策略,其中有一種是將任務(wù)交回給提交任務(wù)的線程執(zhí)行。那么在異步日志打印過(guò)程中,倘若需打印的日志過(guò)多,使得存儲(chǔ)需要異步打印日志的地方滿了,是否也存在相應(yīng)的拋棄策略呢?比如將異步打印轉(zhuǎn)為同步打印。所以,我決定先去探究異步打印日志的原理。

5 異步日志打印原理

5.1 Disruptor原理

經(jīng)過(guò)查詢資料發(fā)現(xiàn),異步日志打印的底層采用的是Disruptor框架。

圖片圖片

其基本原理為生產(chǎn)者向環(huán)形數(shù)組中存入數(shù)據(jù),消費(fèi)者則消費(fèi)環(huán)形數(shù)組中的數(shù)據(jù)。sequence 用于限制生產(chǎn)者的生產(chǎn)以及消費(fèi)者的消費(fèi)。通過(guò)閱讀代碼得知,本質(zhì)上生產(chǎn)者會(huì)保存所有消費(fèi)者以及自身的 sequence 以限制生產(chǎn),消費(fèi)者保存生產(chǎn)者來(lái)限制消費(fèi)。

//org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor#start
public synchronized void start() {
  //....
  //
  disruptor.handleEventsWith(handlers);
  //....
}

//com.lmax.disruptor.dsl.Disruptor#handleEventsWith(com.lmax.disruptor.EventHandler<? super T>...)
//添加事件處理(消費(fèi)者)
public final EventHandlerGroup<T> handleEventsWith(final EventHandler<? super T>... handlers) {
  return createEventProcessors(new Sequence[0], handlers);
}

//com.lmax.disruptor.dsl.Disruptor#createEventProcessors(com.lmax.disruptor.Sequence[], com.lmax.disruptor.EventHandler<? super T>[])
//創(chuàng)建事件處理(消費(fèi)對(duì)象)
EventHandlerGroup<T> createEventProcessors(
  final Sequence[] barrierSequences,
  final EventHandler<? super T>[] eventHandlers) {
  //....
  //所有消費(fèi)者的Sequence
  final Sequence[] processorSequences = new Sequence[eventHandlers.length];
  //將生產(chǎn)者包裝成為SequenceBarrier
  final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);
  //遍歷事件處理
  for (int i = 0, eventHandlersLength = eventHandlers.length; i < eventHandlersLength; i++) {
    final EventHandler<? super T> eventHandler = eventHandlers[i];
    //創(chuàng)建事件處理(消費(fèi)者)
    final BatchEventProcessor<T> batchEventProcessor =
      new BatchEventProcessor<>(ringBuffer, barrier, eventHandler);
    //....
    //將消費(fèi)者的Sequence放入gatingSequences中
    processorSequences[i] = batchEventProcessor.getSequence();
  }
  //更新生產(chǎn)者的processorSequences
  updateGatingSequencesForNextInChain(barrierSequences, processorSequences);
  //....
}

5.2 異步日志放入Disruptor環(huán)形數(shù)組的過(guò)程

圖片圖片

代碼

  • 將數(shù)據(jù)放入Disruptor環(huán)形數(shù)組中
//org.apache.logging.log4j.core.async.AsyncLoggerConfig#logToAsyncDelegate
private void logToAsyncDelegate(final LogEvent event) {
  if (!isFiltered(event)) {
    // Passes on the event to a separate thread that will call
    // asyncCallAppenders(LogEvent).
    populateLazilyInitializedFields(event);
  //嘗試將logevnet對(duì)象發(fā)布到環(huán)形數(shù)組中
    if (!delegate.tryEnqueue(event, this)) {
      //如果沒(méi)有放進(jìn)去,說(shuō)明隊(duì)列滿了,執(zhí)行隊(duì)列滿的處理方法,本質(zhì)上類似于線程池隊(duì)列滿丟棄策略
      //ENQUEUE:將日志放入環(huán)形數(shù)組(放入不進(jìn)去就循環(huán)等待,一直到放進(jìn)去)
      //SYNCHRONOUS:當(dāng)前線程打印日志
      //DISCARD:直接丟棄,忽略日志
      handleQueueFull(event);
    }
  }
}
  • 放入環(huán)形數(shù)組的過(guò)程
//org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor#tryEnqueue
public boolean tryEnqueue(final LogEvent event, final AsyncLoggerConfig asyncLoggerConfig) {
    final LogEvent logEvent = prepareEvent(event);
    //獲取環(huán)形數(shù)組
    return disruptor.getRingBuffer().
      //放入環(huán)形數(shù)組中
      tryPublishEvent(translator, logEvent, asyncLoggerConfig);
}
//com.lmax.disruptor.RingBuffer#tryPublishEvent(com.lmax.disruptor.EventTranslatorTwoArg<E,A,B>, A, B)
//嘗試放入環(huán)形數(shù)組中
public <A, B> boolean tryPublishEvent(EventTranslatorTwoArg<E, A, B> translator, A arg0, B arg1){
    try
    {
      //嘗試獲取生產(chǎn)者下一個(gè)要存放到環(huán)形數(shù)組中的位置
      final long sequence = sequencer.tryNext();
      //將其放入指定的位置中
      translateAndPublish(translator, sequence, arg0, arg1);
      return true;
    }
    catch (InsufficientCapacityException e)
    {
      //容量不足,就返回fals,執(zhí)行環(huán)形數(shù)組滿的處理方法
      return false;
    }
}
//com.lmax.disruptor.MultiProducerSequencer#tryNext(int)
//獲取生產(chǎn)者下一個(gè)位置,n表示當(dāng)前位置+1,tryNext()就是tryNext(1)
public long tryNext(int n) throws InsufficientCapacityException {
    if (n < 1) {
      throw new IllegalArgumentException("n must be > 0");
    }

    long current;
    long next;

    do {
      //獲取當(dāng)前位置
      current = cursor.get();
      //想要獲取的位置
      next = current + n;
      //判斷是否有可用容量
      //實(shí)現(xiàn)原理大概就是比較Sequence(消費(fèi)者消費(fèi)到哪里了)和current中的較小值,與next差值是否大于環(huán)形數(shù)組的大小
      if (!hasAvailableCapacity(gatingSequences, n, current)) {
        throw InsufficientCapacityException.INSTANCE;
      }
    }
    while (!cursor.compareAndSet(current, next));
    return next;
}

發(fā)現(xiàn)確實(shí)有類似于線程池的拋棄策略。

5.3 復(fù)用4.2中打印日志的測(cè)試代碼進(jìn)行打斷點(diǎn)

經(jīng)過(guò)調(diào)試發(fā)現(xiàn),在日志打印的過(guò)程中消費(fèi)者消費(fèi)快,沒(méi)有出現(xiàn)handleQueueFull的調(diào)用。

所以并不會(huì)經(jīng)過(guò)ENQUEUE、SYNCHRONOUS、DISCARD的三種策略的處理。

那么現(xiàn)在問(wèn)題就變得十分離譜,并非上述原因所致。因此,決定采用暴力破解之法,即逐行注釋代碼的方式,以查看究竟是哪一行代碼引發(fā)了日志打印緩慢的問(wèn)題。

6 將日志打印過(guò)程中的代碼進(jìn)行逐行注釋,找到那一行導(dǎo)致日志輸出慢

6.1 定位原因發(fā)現(xiàn)是getLocation方法

經(jīng)過(guò)逐行注釋后,確定是由于getLocation方法所致。那么這行代碼究竟有何作用呢?

查詢資料得知getLocation 是用來(lái)獲取內(nèi)容匹配日志輸出格式中的%C、%F、%l、%L、%M 。

以下是該方法的調(diào)用之處以及其內(nèi)部代碼。

//org.apache.logging.log4j.spi.AbstractLogger#logMessageTrackRecursion
private void logMessageTrackRecursion(final String fqcn,
                                      final Level level,
                                      final Marker marker,
                                      final Message message,
                                      final Throwable throwable) {
  try {
    incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031
    tryLogMessage(fqcn, getLocation(fqcn), level, marker, message, throwable);
  } finally {
    decrementRecursionDepth();
  }
}
//org.apache.logging.log4j.spi.AbstractLogger#getLocation
//獲取日志是哪個(gè)類,哪個(gè)方法,哪一行打印的
//舉例[11-05 00:03:39 596 INFO ] [] [] [] [main] [] c.z.h.p.r.r.c.ChannelCommonFacadeTest.main(383) - 111
//中的c.z.h.p.r.r.c.ChannelCommonFacadeTest.main(383)字符串
private StackTraceElement getLocation(String fqcn) {
  //requiresLocation 用于判斷是否需要Location,不需要直接返回null
  return requiresLocation() ? StackLocatorUtil.calcLocation(fqcn) : null;
}

繼續(xù)向下追蹤會(huì)到下方代碼:

//org.apache.logging.log4j.util.StackLocator#calcLocation
public StackTraceElement calcLocation(final String fqcnOfLogger) {
    //....
   //fqcnOfLogger=org.apache.logging.slf4j.Log4jLogger
    //獲取當(dāng)前方法調(diào)用鏈路上所有的調(diào)用鏈路
    final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
    boolean found = false;
    for (int i = 0; i < stackTrace.length; i++) {
      final String className = stackTrace[i].getClassName();
      //判斷類名稱是否相同
      if (fqcnOfLogger.equals(className)) {

        found = true;
        continue;
      }
      //這樣就得到了誰(shuí)調(diào)用的日志輸出了
      if (found && !fqcnOfLogger.equals(className)) {
        return stackTrace[i];
      }
    }
    return null;
}
//java.lang.Throwable#getStackTrace
public StackTraceElement[] getStackTrace() {
 return getOurStackTrace().clone();
}
//java.lang.Throwable#getOurStackTrace
private synchronized StackTraceElement[] getOurStackTrace() {
  // Initialize stack trace field with information from
  // backtrace if this is the first call to this method
  if (stackTrace == UNASSIGNED_STACK ||
      (stackTrace == null && backtrace != null) /* Out of protocol state */) {
    //獲取調(diào)用鏈路的長(zhǎng)度
    int depth = getStackTraceDepth();
    stackTrace = new StackTraceElement[depth];
    for (int i=0; i < depth; i++)
      //獲取每一級(jí)調(diào)用鏈路
      stackTrace[i] = getStackTraceElement(i);
  } else if (stackTrace == null) {
    return UNASSIGNED_STACK;
  }
  return stackTrace;
}
//方法都是本地方法
native int getStackTraceDepth();
native StackTraceElement getStackTraceElement(int index);

通過(guò)上述跟蹤源碼,懷疑是Java調(diào)用C++代碼或者需要遍歷StackTraceElement數(shù)組導(dǎo)致異步日志打印過(guò)慢。

所以先將getOurStackTrace方法進(jìn)行修改,不調(diào)用C++方法,直接創(chuàng)建對(duì)象,來(lái)排除不是因?yàn)楸闅v數(shù)組導(dǎo)致的原因。

//org.apache.logging.log4j.util.StackLocator#calcLocation
public StackTraceElement calcLocation(final String fqcnOfLogger) {
    //....
   //fqcnOfLogger=org.apache.logging.slf4j.Log4jLogger
    //將這里改為自己的寫的方法調(diào)用,構(gòu)建方法調(diào)用鏈路
    final StackTraceElement[] stackTrace = stackTraceElements(fqcnOfLogger);
    boolean found = false;
    for (int i = 0; i < stackTrace.length; i++) {
      final String className = stackTrace[i].getClassName();
      //判斷類名稱是否相同
      if (fqcnOfLogger.equals(className)) {

        found = true;
        continue;
      }
      //這樣就得到了誰(shuí)調(diào)用的日志輸出了
      if (found && !fqcnOfLogger.equals(className)) {
        return stackTrace[i];
      }
    }
    return null;
}
/**
* 構(gòu)建stackTraceElement數(shù)組
* @param fqcnOfLogger
* @return
*/
private StackTraceElement[] stackTraceElements(String fqcnOfLogger) {
    //構(gòu)建一個(gè)42長(zhǎng)度的,保持測(cè)試代碼獲取相同長(zhǎng)度的方法調(diào)用鏈路
    int size = 42;
    StackTraceElement[] stackTraceElement = new StackTraceElement[size];
    for (int i = 0; i < size - 2; i++) {
      stackTraceElement[i] = new StackTraceElement("1", "1", "1", 1);
    }
    stackTraceElement[size - 2] = new StackTraceElement(fqcnOfLogger, "1", "1", 1);
    stackTraceElement[size - 1] = new StackTraceElement("1", "1", "1", 1);
    return stackTraceElement;
}

//然后再執(zhí)行代碼測(cè)試代碼
@Test
private void test4(){
    long l = System.currentTimeMillis();
    for (int i = 0; i < 20000; i++) {
      log.info(i+"");
    }
    System.out.println("打印日志耗時(shí):" + (System.currentTimeMillis() - l));
}

發(fā)現(xiàn)耗時(shí)只需要50毫秒,所以由此懷疑是Java調(diào)用C++代碼導(dǎo)致異步日志打印過(guò)慢。Java調(diào)用C++代碼用的框架叫JNI。

6.2 官方資料

圖片圖片

這段文字說(shuō)明使用堆棧跟蹤比不實(shí)用堆棧跟蹤慢30-100倍。

圖片圖片

這張圖用來(lái)說(shuō)明單線程異步日志記錄時(shí)需要記錄調(diào)用位置信息與不需要記錄調(diào)用位置信息的對(duì)比。

6.3 JNI原理

JNI調(diào)用Java代碼是一種類似反射的原理,先找到j(luò)class、再找到j(luò)methodId,然后調(diào)用,這樣一步步地來(lái);

Java調(diào)用C/C++代碼創(chuàng)建對(duì)象是需要保存對(duì)象指針,然后各種操作是要將指針傳入到j(luò)ni層,然后強(qiáng)轉(zhuǎn)到具體對(duì)象再進(jìn)行操作的。

6.3.1 JNI 架構(gòu)

JNI 的基本架構(gòu)可以分為以下幾個(gè)部分:

  • Java 代碼:調(diào)用 native 方法的 Java 類。
  • JNI 頭文件:由 Java 編譯器生成的 C/C++ 頭文件,定義了 Java 類中的 native 方法。
  • 本地實(shí)現(xiàn):C/C++ 代碼實(shí)現(xiàn) native 方法的具體邏輯。
  • Java 虛擬機(jī):提供 JNI 支持的運(yùn)行環(huán)境,負(fù)責(zé)加載和執(zhí)行 Java 字節(jié)碼。

6.3.2 數(shù)據(jù)類型轉(zhuǎn)換

JNI 負(fù)責(zé)在 Java 數(shù)據(jù)類型和 C/C++ 數(shù)據(jù)類型之間進(jìn)行轉(zhuǎn)換。常見的數(shù)據(jù)轉(zhuǎn)換方法包括:

  • Java 字符串:使用 GetStringUTFChars 和 NewStringUTF 方法進(jìn)行轉(zhuǎn)換。
  • Java 數(shù)組:使用 GetIntArrayElements、SetIntArrayRegion 等方法處理數(shù)組。
  • Java 對(duì)象:使用 GetObjectField、CallObjectMethod 等方法訪問(wèn)對(duì)象的字段和方法。

6.3.3 異常處理

JNI 提供異常處理機(jī)制,允許本地代碼檢測(cè)和拋出 Java 異常。通過(guò) env->ExceptionCheck() 和 env->Throw 方法,可以在本地代碼中檢查異常狀態(tài)并拋出異常。

6.3.4 線程管理

JNI 允許多線程訪問(wèn) JVM,提供了與線程相關(guān)的 API,如 AttachCurrentThread 和 DetachCurrentThread,用于在本地代碼中管理 Java 線程。

6.3.5 性能考慮

  • 開銷:JNI 方法調(diào)用相對(duì) Java 方法調(diào)用來(lái)說(shuō)有更高的開銷,因?yàn)樯婕暗缴舷挛那袚Q和數(shù)據(jù)轉(zhuǎn)換。
  • 效率:盡管 JNI 可以提供更高的性能,特別是在需要大量計(jì)算或系統(tǒng)級(jí)操作時(shí),過(guò)多的 JNI 調(diào)用可能導(dǎo)致性能下降。

6.4 編寫本地方法測(cè)試

public class Hello {
    public native int helloFromCpp(int i);

    static {
       //加載動(dòng)態(tài)鏈接
        System.load(System.getProperty("user.dir") +"/libnative.dylib");
    }
}

下面是如何能進(jìn)行本地方法調(diào)用的過(guò)程:

  1. javac Hello.java -h . 將Java文件編譯為.class并產(chǎn)生頭文件。
  2. 產(chǎn)生的頭文件的展示
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class test_Hello */

#ifndef _Included_test_Hello
#define _Included_test_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     test_Hello
 * Method:    helloFromCpp
 * Signature: (I)I
 */
//jni產(chǎn)生的方法名稱是有規(guī)則的 Java開頭 test表示包 Hello表示類 helloFromCpp表示方法
JNIEXPORT jint JNICALL Java_test_Hello_helloFromCpp
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif
  • 編寫頭文件對(duì)應(yīng)的cpp文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include "test_Hello.h"
/* Header for class test_Hello */


/*
 * Class:     test_Hello
 * Method:    helloFromCpp
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_test_Hello_helloFromCpp
  (JNIEnv * env, jobject o , jint j1){
    return j1;
  }
  • 產(chǎn)生動(dòng)態(tài)鏈接g++ -dynamiclib  -o libnative.dylib -I "/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/include/darwin" -I "/Library/Java/JavaVirtualMachines/jdk-1.8.jdk/Contents/Home/include" test_Hello.cpp
  • 執(zhí)行代碼測(cè)試
public static void test() {
  long l = System.currentTimeMillis();
  for (int i = 0; i < 20000*32; i++) {
  Hello h = new Hello();
  h.helloFromCpp(i);
  }
  System.out.println("耗時(shí)時(shí)間" + (System.currentTimeMillis() - l));
}

注意:蘋果電腦要是arm架構(gòu)的,一定要注意執(zhí)行Java程序的JDK版本要是arm架構(gòu)的。

發(fā)現(xiàn)耗時(shí)很少,所以并不是因?yàn)镴ava通過(guò)JNI執(zhí)行C語(yǔ)言代碼的問(wèn)題。

所以最終結(jié)論應(yīng)該是底層執(zhí)行C++代碼慢的緣故。

7 結(jié)論

  • 避免打印過(guò)多無(wú)用日志,將測(cè)試過(guò)程中需要觀察的日志輸出為為 DEBUG 級(jí)別。
  • 生產(chǎn)環(huán)境打印日志的級(jí)別設(shè)置為INFO。
  • 如果不需要關(guān)注日志打印的位置信息,可以將日志輸出格式中的%C、%F、%l、%L、%M去掉。

8 擴(kuò)展(想了解C底層實(shí)現(xiàn))

可以查看一下下面三個(gè)類:

  • jdk/src/share/native/java/lang/Throwable.c
  • hotspot/src/share/vm/prims/jvm.cpp
  • hotspot/src/share/vm/classfile/javaClasses.cpp
責(zé)任編輯:武曉燕 來(lái)源: 轉(zhuǎn)轉(zhuǎn)技術(shù)
相關(guān)推薦

2013-11-11 11:17:45

AngularJS性能優(yōu)化

2025-06-27 09:05:47

2021-04-27 06:20:25

MySQL集群優(yōu)化

2021-04-22 07:29:46

數(shù)據(jù)展現(xiàn)方式

2012-03-11 15:27:57

微軟

2023-09-27 07:35:27

2017-05-31 13:58:05

戴爾宕機(jī)服務(wù)器

2025-06-25 09:30:14

2019-01-30 09:34:56

ElasticSearLogstashKibana

2014-08-04 15:13:27

光纖

2024-04-12 09:02:15

JavaCPU執(zhí)行時(shí)間線程

2019-08-21 14:35:18

壓縮文件優(yōu)化過(guò)程Java

2022-09-26 09:41:25

MySQL數(shù)據(jù)庫(kù)

2025-04-03 09:45:51

2025-03-24 08:51:16

2020-11-12 18:51:43

Java編程語(yǔ)言

2017-12-25 11:15:06

JavaArray數(shù)組

2017-10-31 15:28:27

RUDP傳輸優(yōu)化實(shí)踐

2024-04-10 08:00:00

PostgresNoSQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久r精品 | 国产一区影院 | 亚洲欧美综合网 | 99福利网| 久久综合av | 亚洲欧美视频 | 久久久久久国产精品免费免费狐狸 | 日韩一二三区视频 | 欧美久久一区二区三区 | 中文字幕动漫成人 | 午夜免费视频观看 | 日韩一区二区免费视频 | 欧美精品在线一区 | 午夜看片 | 亚洲精品电影 | 日韩欧美一区在线 | 免费国产网站 | 色婷婷久久久久swag精品 | 91久久精品视频 | 亚洲乱码一区二区三区在线观看 | 久久看看 | 亚洲国产精品日韩av不卡在线 | 成人在线影视 | 女生羞羞网站 | 欧美综合久久久 | 国产精品一区二区电影 | 亚洲在线 | 日韩一区二区三区av | 中文字幕一区二区三区日韩精品 | 亚洲精品欧洲 | 欧美日韩久久久 | 国产伦一区二区三区久久 | 午夜丰满寂寞少妇精品 | 视频一区二区在线观看 | 午夜理伦三级理论三级在线观看 | 久久高清| 国产一区二区三区高清 | 亚洲在线一区 | 国产精品99久久久久久久久久久久 | 国产成人99久久亚洲综合精品 | 久久亚洲精品久久国产一区二区 |