保定专业网站建设建设银行南通通州支行网站

当前位置: 首页 > news >正文

保定专业网站建设,建设银行南通通州支行网站,网站开发排期表模板,企业网站制作公司24小时接单概述最近线上监控发现 OOM 涨幅较大#xff0c;因此去尝试定位和修复这个问题#xff0c;在修复了一些内存泄漏和大对象占用问题后, OOM 依旧未达到正常标准#xff0c;在这些新上报的 hprof 文件中#xff0c;发现几乎所有 case 中都有个叫 FinalizerReference 的对象因此去尝试定位和修复这个问题在修复了一些内存泄漏和大对象占用问题后, OOM 依旧未达到正常标准在这些新上报的 hprof 文件中发现几乎所有 case 中都有个叫 FinalizerReference 的对象数量巨多内存占用高居首位因此判断它就是引起本次 OOM 上涨的罪魁祸首。ReferenceQueue首先前置了解下 ReferenceQueue 引用队列是个啥简言之就是用来存放 Reference 对象的队列当 Reference 对象所引用的对象被 GC 回收时该 Reference 对象就会被加入到引用队列 ReferenceQueue 中。即valqueue ReferenceQueueBean() valbean Bean() valreference SoftReference(bean, queue) 复制代码上述代码中创建了一个 bean 强引用以及一个 reference 软引用当 bean 被回收时该软引用 reference 对象会被放到 queue 队列中后续该 reference 对象需要开发者自行处理(如从队列中 poll 等)。Leakcanary 检测内存泄漏的原理就是应用的 ReferenceQueue 引用队列以 Activity 为例监听 Activity 的生命周期。在 onDestory 的时候通过 ReferenceQueue 来创建对应 Actitity 的 Refrence 引用对象。一段时间后从 ReferenceQueue 中读取如果有这个 Actitity 的 Refrence那么说明这个 Activity 的 Refrence 已经被回收如果 ReferenceQueue 没有这个 Actitity 的 Refrence, 那就说明出现了内存泄漏。关于 Reference 引用网上一搜很多文章大家估计比较清楚了就不再赘述。以前做过一些相关的笔记有兴趣可以看看 JVM垃圾回收-引用。FinalizerReference首先介绍一下 Finalizer 对象它指的是在其 Java 类中复写了 finalize() 方法且该方法非空的对象有的地方称呼这种类叫 f 类我们也这样叫它。在类加载过程中会把加载的 Java 类标记为是否 f 类。FinalizerReference概述现在开始看看 FinalizerReference 是个啥玩意知己知彼才能知道怎么去修它。这个 FinalizerReference 是用来协助 FinalizerDaemon 线程处理对象的 finalize() 工作的一次性出现了三个名词我们先从 FinalizerReference 看起。看看它的部分代码// java/lang/ref/FinalizerReference.javapublicfinalclassFinalizerReferenceT extendsReferenceT {// This queue contains those objects eligible for finalization.publicstaticfinal ReferenceQueueObject queue newReferenceQueueObject();// This list contains a FinalizerReference for every finalizable object in the heap.// Objects in this list may or may not be eligible for finalization yet.privatestatic FinalizerReference? head null;// The links used to construct the list.private FinalizerReference? prev;private FinalizerReference? next;publicstaticvoidadd(Object referent) {FinalizerReference? reference newFinalizerReferenceObject(referent, queue);synchronized (LIST_LOCK) {reference.prev null;reference.next head;if (head ! null) {head.prev reference;}head reference;}}publicstaticvoidremove(FinalizerReference? reference) {synchronized (LIST_LOCK) {FinalizerReference? next reference.next;FinalizerReference? prev reference.prev;reference.next null;reference.prev null;if (prev ! null) {prev.next next;} else {head next;}if (next ! null) {next.prev prev;}}} } 复制代码FinalizerReference 中有两个静态方法: add 和 remove 方法。它创建了一个 FinalizerReference 类型的链表(表头用静态变量 FinalizerReference.head 表示)其中每个 FinalizerReference 对象都使用 ReferenceQueue 类型的静态变量 queue 来创建这样当对应的对象 Object referent 被回收后该 FinalizerReference 会被放入 ReferenceQueue 中。FinalizerReference.add上述 FinalizerReference.add 方法是虚拟机调用的当创建对象的时候如果发现该类是 f 类就会调用 FinalizerReference.add 方法创建 FinalizerRefence 对象并将其加入到 head 链表中。// art/runtime/mirror/class-alloc-inl.htemplatebool kIsInstrumented, Class::AddFinalizer kAddFinalizer, bool kCheckAddFinalizer inline ObjPtrObject Class::Alloc(Thread* self, gc::AllocatorType allocator_type){CheckObjectAlloc();gc::Heap* heap Runtime::Current()-GetHeap();bool add_finalizer;switch (kAddFinalizer) {case Class::AddFinalizer::kUseClassTag:// 这里判断是不是 f 类add_finalizer IsFinalizable();break;case Class::AddFinalizer::kNoAddFinalizer:add_finalizer false;DCHECK_IMPLIES(kCheckAddFinalizer, !IsFinalizable());break;}// Note that the this pointer may be invalidated after the allocation.ObjPtrObject obj heap-AllocObjectWithAllocatorkIsInstrumented, /kCheckLargeObject/false(self, this, this-objectsize, allocator_type, VoidFunctor());if (add_finalizer LIKELY(obj ! nullptr)) {// 如果是 f 类则调用 AddFinalizerReference 方法heap-AddFinalizerReference(self, obj);// …}return obj; }// art/runtime/gc/heap.ccvoidHeap::AddFinalizerReference(Thread* self, ObjPtrmirror::Object* object){ScopedObjectAccess soa(self);ScopedLocalRefjobject arg(self-GetJniEnv(), soa.AddLocalReferencejobject(*object));jvalue args[1];args[0].l arg.get();InvokeWithJValues(soa, nullptr, WellKnownClasses::java_lang_ref_FinalizerReference_add, args);// Restore object in case it gets moved.*object soa.Decodemirror::Object(arg.get()); } 复制代码上面的 AddFinalizerReference 方法会调用到 Java 层的 FinalizerReference.add() 静态方法就完成了 add 的操作。FinalizerReference.remove我们前面提到当 Reference 对象所引用的对象被 GC 回收时该 Reference 对象就会被加入到引用队列 ReferenceQueue 中所以当 f 类的对象发生 gc 时会将其对应的 FinalizerReference 对象加入到 FinalizerReference.queue 队列里。而 remove 的时机与 FinalizerDaemon 守护线程有关我们看看这个线程的源码// libcore/libart/src/main/java/java/lang/Daemons.java// private static abstract class Daemon implements RunnableprivatestaticclassFinalizerDaemonextendsDaemon {privatestaticfinalFinalizerDaemonINSTANCEnewFinalizerDaemon();privatefinal ReferenceQueueObject queue FinalizerReference.queue;OverridepublicvoidrunInternal() {// …while (isRunning()) {try {// Use non-blocking poll to avoid FinalizerWatchdogDaemon communication when busy.FinalizerReference? finalizingReference (FinalizerReference?)queue.poll();// …doFinalize(finalizingReference);} catch (InterruptedException ignored) {} catch (OutOfMemoryError ignored) {}}} } 复制代码在 FinalizerDaemon.runInternal 方法中会通过 FinalizerReference.queue(ReferenceQueue引用队列) 的 poll/remove 方法拿到 queue 中的 Reference 引用接着执行 doFinalize() 方法该方法会调用 Finalizer 对象的 finalize() 方法privatevoiddoFinalize(FinalizerReference? reference) {FinalizerReference.remove(reference);Objectobject reference.get();reference.clear();try {object.finalize();} catch (Throwable ex) {// The RI silently swallows these, but Android has always logged.System.logE(Uncaught exception thrown by finalizer, ex);} finally {// Done finalizing, stop holding the object as live.finalizingObject null;} } 复制代码首先将获取到的 FinalizerReference 对象从 FinalizerReference.head 链表中移除接着通过 reference.get() 方法得到 Java 对象并执行其 finalize() 方法。小结FinalizerReference 的主要作用是协助 FinalizerDaemon 守护线程来执行 Finalizer 对象的 finalize() 方法。在 f 类创建对象时调用 FinalizerReference.add() 方法创建一个 FinalizerReference 对象(使用 ReferenceQueue 队列)并加入到 head 链表里。在 f 类的对象被回收后其对应的 FinalizerReference 对象会被加入到 ReferenceQueue 队列(FinalizerReference.queue)里。FinalizerDaemon 守护线程从 FinalizerReference.queue 队列中取出 FinalizerReference 对象并执行其对应 f 类对象的 finalize() 方法。ReferenceQueueDaemon上面看完了 FinalizerDaemon 守护线程这里再看看 ReferenceQueueDaemon 守护线程上面说到在创建引用对象 Reference 的时候可以关联一个 ReferenceQueue 队列当被引用对象被 gc 回收时该 reference 对象就会被加入到其创建时关联的队列去这个加入队列的操作就是由 ReferenceQueueDaemon 守护线程完成的。privatestaticclassReferenceQueueDaemonextendsDaemon {OverridepublicvoidrunInternal() {while (isRunning()) {Reference? list;try {synchronized (ReferenceQueue.class) {if (ReferenceQueue.unenqueued null) {progressCounter.incrementAndGet();do {ReferenceQueue.class.wait();} while (ReferenceQueue.unenqueued null);progressCounter.incrementAndGet();}list ReferenceQueue.unenqueued;ReferenceQueue.unenqueued null;}} catch (InterruptedException e) {continue;} catch (OutOfMemoryError e) {continue;}ReferenceQueue.enqueuePending(list, progressCounter);}} } 复制代码具体源码感兴趣的话可以自己去看看这里就不贴太多源码了。FinalizerWatchdogDaemon这里再补充一下 FinalizerWatchdogDaemon 守护线程它跟 FinalizerDaemon 以及 ReferenceQueueDaemon 线程是一起 start 的。我们再看一下上面 FinalizerDaemon 和 ReferenceQueueDaemon 线程的 runInternal() 方法// ReferenceQueueDaemonOverridepublicvoidrunInternal() {FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);while (isRunning()) {Reference? list;try {synchronized (ReferenceQueue.class) {if (ReferenceQueue.unenqueued null) {progressCounter.incrementAndGet();FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);do {ReferenceQueue.class.wait();} while (ReferenceQueue.unenqueued null);progressCounter.incrementAndGet();FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.RQ_DAEMON);}list ReferenceQueue.unenqueued;ReferenceQueue.unenqueued null;}} catch (InterruptedException e) {continue;} catch (OutOfMemoryError e) {continue;}ReferenceQueue.enqueuePending(list, progressCounter);FinalizerWatchdogDaemon.INSTANCE.resetTimeouts();} }// FinalizerDaemonOverridepublicvoidrunInternal() {FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);while (isRunning()) {try {FinalizerReference? finalizingReference (FinalizerReference?)queue.poll();if (finalizingReference ! null) {finalizingObject finalizingReference.get();} else {finalizingObject null;FinalizerWatchdogDaemon.INSTANCE.monitoringNotNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);finalizingReference (FinalizerReference?)queue.remove();finalizingObject finalizingReference.get();FinalizerWatchdogDaemon.INSTANCE.monitoringNeeded(FinalizerWatchdogDaemon.FINALIZER_DAEMON);}doFinalize(finalizingReference);} catch (InterruptedException ignored) {} catch (OutOfMemoryError ignored) {}} } 复制代码上面的 monitoringNotNeeded 方法会休眠线程停止 timeout 计时而 monitoringNotNeeded 会唤醒 FinalizerWatchdogDaemon 守护线程。看看 FinalizerWatchdogDaemon 的源码我加了一些注释privatestaticclassFinalizerWatchdogDaemonextendsDaemon {// Single bit values to identify daemon to be watched.// 监控的两种类型staticfinalintFINALIZER_DAEMON1;staticfinalintRQ_DAEMON2;OverridepublicvoidrunInternal() {while (isRunning()) {// sleepUntilNeeded 内部会调用 wait() 方法不贴了if (!sleepUntilNeeded()) {continue;}finalTimeoutExceptionexception waitForProgress();if (exception ! null !VMDebug.isDebuggerConnected()) {// 抛出异常 Thread.currentThread().dispatchUncaughtException(exception)timedOut(exception);break;}}}// 去掉 whichDaemon 的监控进入 waitprivatesynchronizedvoidmonitoringNotNeeded(int whichDaemon) {activeWatchees ~whichDaemon;}// 添加 whichDaemon 的监控, notify 线程privatesynchronizedvoidmonitoringNeeded(int whichDaemon) {intoldWatchees activeWatchees;activeWatchees | whichDaemon;if (oldWatchees 0) {// 调用 notify 唤醒notify();}}// 判断是否添加了指定 whichDaemon 类型的监控privatesynchronizedbooleanisActive(int whichDaemon) {return (activeWatchees whichDaemon) ! 0;}// 调用 isActive 判断开启监控的类型通过 Thread.sleep() 方法休眠指定时间// 休眠结束后返回 TimeoutException 异常若途中监控的逻辑执行完成则表示未超时返回 null// 等待时间: VMRuntime.getFinalizerTimeoutMs// If the FinalizerDaemon took essentially the whole time processing a single reference,// or the ReferenceQueueDaemon failed to make visible progress during that time, return an exception.private TimeoutException waitForProgress() {finalizerTimeoutNs NANOS_PER_MILLI * VMRuntime.getRuntime().getFinalizerTimeoutMs();// …} } 复制代码上面一些细节代码就不贴出了, FinalizerWatchdogDaemon 主要用来监控两种类型的执行时长: FINALIZER_DAEMON 和 RQ_DAEMON。当执行超时时就会抛出 TimeOutException 异常因此不要在 finalize() 方法中做耗时操作。这个超时时间在 AOSP 中定义为 10s, 国内厂商可能会修改这个值, 据说有的改为了 60 s, 有兴趣的同学可以自己解包看看:RUNTIME_OPTIONS_KEY (unsigned int, FinalizerTimeoutMs, 10000u) 复制代码OOM排查通过线上 hprof 文件排除了大对象和内存泄漏的可能接着在 hprof 中发现存在大量的 X(用 X 表示业务上的某个对象) 对象堆积这些对象对应的 Java 类是一个与 Native 层有关的类其重写了 finalize() 方法线下无法复现堆积大量 X 对象的路径。可能有某个业务场景的代码逻辑使用不当造成了 X 对象的疯狂创建导致 FinalizerDaemon 线程来不及回收由于这个 X 对象的创建场景比较多因此无法通过代码 review 来定位到有问题的创建代码采取了一些监控手段后依旧无果。用治标不治本的方式显式调用系统 gc 和 runFinalization 方法在定位不到具体的问题场景后因为猜测是创建了大量的 X 对象导致 FinalizerDaemon 线程回收不及时因此分别在主线程和子线程中显式调用系统 gc 和 runFinalization 方法来手动回收 X 对象。结果子线程调用无效果, OOM 未下降主线程调用发生了 ANR。查看 ANR 堆栈后发现 ANR 的位置都在另外一个 f 对象的 finalize() 方法中调用到的一行 Native 代码因此问题就明了了是因为在某个 finalize() 方法中调用到的这行代码卡死了(逻辑问题导致死锁)因此阻塞了 FinalizerDaemon 线程的执行进而导致对象堆积。另外本应发生的 TimeoutException 异常被我们的异常处理框架捕获了因此未曾暴露出来。总结Java 中并没有析构函数, finalize() 实现了类似析构函数的概念可以在对象被回收前做一些回收性的操作在 JNI 开发中可能被用来进行 Native 内存的释放。关于 f 类使用不当会造成的影响FinalizerDaemon 守护线程的优先级不高在 CPU 紧张的情况下调度可能会被影响所以可能无法及时回收 f 类对象。finalize 对象由于 finalize() 的引用它变成了一个强引用即使没有其他显式的强引用了它也还是无法立即被回收。因此 f 对象至少需要 2 次 gc 才可能被回收第一次 gc 时会将 f 对象加入到 ReferenceQueue 中而等到 finalize() 方法执行完毕后的下一次 gc 里才有可能回收它。由于守护线程的优先级低这期间可能已经发生过多次 gc 了长时间未回收的话又可能会导致 f 类在资源紧张时进入到老年代从而引起老年代的 gc 甚至是 full gc。因此尽量不要重载 finalize() 方法而是通过一些逻辑上的接口去释放内存如果一定要重载它的话也不要让一些需要频繁创建的对象或者大对象去通过 finalize() 释放不然总有一天会出现问题的。与此相关的守护线程有四个感兴趣的同学可以深入看看相关源码// libcore/libart/src/main/java/java/lang/Daemons.javaprivatestaticfinal Daemon[] DAEMONS newDaemon[] {HeapTaskDaemon.INSTANCE,ReferenceQueueDaemon.INSTANCE,FinalizerDaemon.INSTANCE,FinalizerWatchdogDaemon.INSTANCE, };前段时间整理收集的关于Android性能优化的知识脑图总结和学习手册文档既能够夯实底层原理、性能调优等核心技术点又能够掌握普通开发者难以触及的架构设计方法论。那你在工作中、团队里、面试时也就拥有了同行难以复制的核心竞争力。需要完整版的朋友可后台私信【性能优化】Android性能分析与优化实战进阶手册Android性能分析与优化实战进阶手册目录及内容展示第一章:Android性能优化概述第二章:卡顿优化第三章:启动速度优化第四章:内存优化第五章:布局优化第六章:线程优化第七章:电量优化第八章:稳定性优化