Neil's blog

Let's start from here


  • 首页

  • 归档

  • 标签

  • 关于

LeakCanary源码浅析

发表于 2019-01-02 |

一、原理分析

首先在Application中进行初始化操作,进行监听。监听的原理在于 Application 的 registerActivityLifecycleCallbacks 方法,该方法可以对应用内所有 Activity 的生命周期做监听。

1
2
3
4
5
6
7
8
9
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}

在最后一步buildAndInstall()中实现监听操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}

在ActivityRefWatcher实现监听,只对onDestory进行了监听处理。

1
2
3
4
5
6
7
8
9
10
11
12
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};

当一个Activity销毁时,那么就会执行

refWatcher.watch(activity);

接着看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* Identical to {@link #watch(Object, String)} with an empty string reference name.
*
* @see #watch(Object, String)
*/
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}

这里会把待检测的对象构造成KeyedWeakReference,其实就是WeakReference,只是添加了key和name.构造时传入ReferenceQueue
,WeakReference构造函数会传入ReferenceQueue,当WeakReference指向的对象被垃圾回收时,会把WeakReference放入ReferenceQueue。

在构造时我们需要传入一个ReferenceQueue,这个ReferenceQueue是直接传入了WeakReference中的.

WeakReference源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}

这里需要明白一件事件,就是为什么每次WeakReference所指向的对象被GC后,这个弱引用都会被放入这个与之相关联的ReferenceQueue队列中。看下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* Try handle pending {@link Reference} if there is one.<p>
* Return {@code true} as a hint that there might be another
* {@link Reference} pending or {@code false} when there are no more pending
* {@link Reference}s at the moment and the program can do some other
* useful work instead of looping.
*
* @param waitForNotify if {@code true} and there was no pending
* {@link Reference}, wait until notified from VM
* or interrupted; if {@code false}, return immediately
* when there is no pending {@link Reference}.
* @return {@code true} if there was a {@link Reference} pending and it
* was processed, or we waited for notification and either got it
* or thread was interrupted before being notified;
* {@code false} otherwise.
*/
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
/* If there were a special system-only priority greater than
* MAX_PRIORITY, it would be used here
*/
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}

然后进入 ensureGoneAsync(watchStartNanoTime, reference);进行检测。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {//如果确认移除,则判定为未泄漏
return DONE;
}
gcTrigger.runGc();//再次执行GC,避免误判
removeWeaklyReachableReferences();//再次移除,二次确认
if (!gone(reference)) {//仍没有移除,判断为泄露了,执行分析泄漏操作
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
// 如果retainedKeys中key仍存在,说明对象未被垃圾回收。反之则已经垃圾回收。
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}

通过Runtime.gc()和System.runFinalization()进行GC操作。
System.gc()只在System.runFinalization()后才会调用Runtime().getRuntime().gc()操作。否则在System.runFinalization()才会调用Runtime().getRuntime().gc()操作。

System.runFinalization()调用Runtime().getRuntime().runFinalization()。

由于System.gc()无法保证一定会调用gc,所以LeakCanary直接调用Runtime().getRuntime().gc()。

System类的gc()和runFinalization()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface GcTrigger {
GcTrigger DEFAULT = new GcTrigger() {
@Override public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
private void enqueueReferences() {
// Hack. We don't have a programmatic way to wait for the reference queue daemon to move
// references to the appropriate queues.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
void runGc();
}

首先通过removeWeaklyReachableReferences()手动移除KeyedWeakReference,具体操作是通过queue.poll() 取出KeyedWeakReference然后在retainedKeys中移除该KeyedWeakReference,然后再进行一次gcTrigger.runGc();操作二次确认retainedKeys中是否已经移除了key.如果仍然没有移除,那么确定是内存泄漏了,然后进行内存泄漏分析。

二、小结

判断泄漏实现基本步骤

  1. LeakCanary.install()返回了一个RefWatcher。会注册对应用内所有 Activity 生命周期的监听。RefWatcher.queue是一个ReferenceQueue实例。

  2. 在Activity销毁时,进行泄漏判断void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }

  3. 这里会把检测到的 activity 实例关联包装为一个自定义的弱引用(KeyedWeakReference),但是这里在指定弱引用时,LeakCanary 同时还为这个弱引用指定了一个 ReferenceQueue 队列。

  4. 通过继承自WeakReference的KeyedWeakReference,增加了key+name字段从而支持对象的唯一起名(通过UUID)和名字获取。

  5. 在RefWatcher.watch()时把UUID放入RefWatcher.retainedKeys,并把对象跟RefWatcher.queue关联。

  6. 通过Runtime.gc()和System.runFinalization()进行GC操作。

  7. 在RefWatcher.removeWeaklyReachableReferences()调用queue.poll()取出KeyedWeakReference,并从retainedKeys中删除。所以,如果retainedKeys中key仍存在,说明对象未被垃圾回收。反之则已经垃圾回收。

  8. 如果对象未被垃圾回收,则执行分析

BlockCanry源码浅析

发表于 2018-12-24 |

BlockCanary

#Android/BlockCanary

一、基本使用

1
implementation ‘com.github.markzhai:blockcanary-android:1.5.0’
1
BlockCanary.install(this, new BlockCanaryContext()).start();

二、BlockCanary原理

计算主线程中消息分发耗时,超出阈值则提醒卡顿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg=" + msg.what);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}

检测UI卡顿,Android更新UI都是在主线程操作,那么所有的更新都会到loop()方法中来。那么计算dispatchMessage耗时就可以了。

1
2
3
4
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
1
2
3
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

通过两个时间差来判断,简单粗暴。通过Looper中public void setMessageLogging(Printer printer)来设置自己自定义的Printer,重写void println(String x);方法即可实现方法耗时统计了。

BlockCanary 中的具体实现是LooperMonitor,具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}

计算时间差来判断是否卡顿了,默认卡顿阈值3S.

明白了原理,再来看看实现。
通过start()开启。那么start()中做了什么?

1
2
3
4
5
6
7
8
9
/**
* Start monitoring.
*/
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}

很简单,打开监控。这个mBlockCanaryCore.monitor就是自己自定义实现的Printer.

三、整体流程

BlockCanary: 传送门

github原图如下:

BlockCanary

Java线程池浅析

发表于 2018-12-04 |

Java线程池

合理利用线程池能够带来三个好处。第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

一、简单介绍

最简单的新建一个线程的方式就是new Thread

1
2
3
4
5
6
7
new Thread(new Runnable() {
@Overrider
public void run(){
//do something
}
}
).start();

虽然简单,但是有很多缺点:

  1. 频繁的使用new Thread 来新建线程会造成新建对象性能差
  2. 新建的线程缺乏统一的管理,并且新建线程没有限制,互相竞争,可能占用系统资源导致oom
  3. 功能单一

既然不能new Thread不能满足多样的需求变化,那么Java提供的线程池就很好的解决了这些问题。Java提供四种线程池,下面一一介绍。

二、Java线程池

优点:

  1. 重用线程池中的线程,避免因频繁创建和销毁线程带来的性能的开销
  2. 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致阻塞的现象
  3. 能够对线程进行简单的管理,并提供定时执行及指定间隔循环执行的功能

Java中的线程池的具体实现是在ThreadPoolExecutor,通过配置不同的参数来实现不同功能的线程池。

ThreadPoolExecutor构造函数有四个,源码实现最终都是调用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

构造函数的各个参数将会影响到线程池的功能特性,

  • corePoolSize: 线程池中的核心线程池数量,除非设置了allowCoreThreadTimeOut为true,否则即使处于空闲状态也一直保留在线程池中。
  • maximumPoolSize:最大线程数,当活动的线程数达到最大,后续新的任务将会被阻塞
  • keepAliveTime:非核心线程空闲时的超时时间,空闲超过该时间将会被回收,当allowCoreThreadTimeOut为true也会作用于核心线程
  • unit:超时时间的单位
  • workQueue:线程池中的任务队列,通过线程池的execute方法提交的Runnable会存储在这个参数中在JDK中提供了如下阻塞队列:
    1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
    2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
    3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
    4、priorityBlockingQuene:具有优先级的无界阻塞队列;

  • threadFactory:线程工厂,为线程池提供创建新线程的功能

  • handler:RejectedExecutionHandler(饱和策略),当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。
    1、AbortPolicy:直接抛出异常。
    2、CallerRunsPolicy:只用调用者所在线程来运行任务。
    3、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
    4、DiscardPolicy:不处理,丢弃掉。
    当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

三、线程池的分类

常见的线程池有四种:

  1. FixedThreadPool
  2. CachedThreadPool
  3. ScheduledThreadPool
  4. SingleThreadExecutor

1.FixedThreadPool

FixedThreadPool是通过Executors的newCachedThreadPool()方法来创建的。它是一种线程数量固定的线程池,并且全都是核心线程。当该线程池中所有的线程都被占用了,新任务都会处于等待状态,直到有空闲线程。FixedThreadPool只有核心线程,并且没有超时机制,任务队列无限大。

1
2
3
4
5
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

2.CacheThreadPool

CacheThreadPool是通过Executors的newCacheThreadPool()来创建的。它是线程数量不定的线程池,最大数量可达到Integer.MAX_VALUE,当该线程池中的线程都被占用了,有新的任务将会新建线程来处理,该线程池中的线程有超时机制,超时时长为60s,任务队列与FixedThreadPool不同的是该任务队列相当于一个空集合,那么任务将会马上执行,在这种情况下,SynchronousQueue可以理解为无法插入任务。从这些特性看来,CacheThreadPool是比较适合执行大量的耗时较少的任务的。

1
2
3
4
5
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}

3.ScheduledThreadPool

ScheduledThreadPool是通过Executors的newScheduledThreadPool(int corePoolSize)来创建,核心线程是固定的,非核心线程是无限制的,超时时间默认10s,这类线程池主要用于执行定时任务和具有固定周期的重复任务。

1
2
3
4
5
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}

4.SingleThreadExecutor

SingleThreadExecutor是通过newSingleThreadExecutor()来创建的,该线程池中只有一个核心线程,没有超时时间,它确保所有的任务都在同一线程中按顺序执行。会统一所有外界的任务到同一个线程按序执行,这些任务不需考虑同步的问题。

1
2
3
4
5
6
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

四、任务队列

1
BlockingQueue<Runnable> workQueue

即用于保存等待执行的任务的阻塞队列。

BlockingQueue是个接口,有如下实现类:

  1. ArrayBlockQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象必须明确大小,像数组一样。最大的特点是可以防止资源被耗尽。

  2. LinkedBlockQueue:一个可改变大小的阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。创建其对象如果没有明确大小,默认值是Integer.MAX_VALUE。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。

  3. PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。

  4. SynchronousQueue:同步队列。同步队列没有任何容量,每个插入必须等待另一个线程移除,反之亦然。一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue

五、饱和策略

RejectedExecutionHandler(饱和策略),当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

RejectedExecutionHandler提供了四种方式来处理任务拒绝策略:

  1. DiscardPolicy(直接丢弃)
  2. DiscardOldestPolicy(丢弃队列中最老的任务)
  3. ABortPolicy(抛异常)
  4. CallerRunsPolicy(将任务分给调用线程来执行)

六、基本使用

向线程池提交任务

1
2
3
4
5
6
7
8
Runnable cacheThread = new Runnable() {
@Override
public void run() {
System.out.println("Test CacheThreadPool");
}
};
ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
cacheThreadPool.execute(cacheThread);

七、整体分析

整体流程大致如图:

threadpoolpic

了解了整体流程,那么再去看源码就会很清楚明了了。

Android Studio3.0+ Trace分析

发表于 2018-11-07 |

一、简要说明

在进行性能优化的时候,很多情况下需要去看方法耗时。这个时候我们就需要通过Trace来查看方法具体耗时情况。

二、具体使用方法

1.使用DDMS工具来查看trace进行分析

在Android Studio3.0 之前,在AS IDE面板中->Tools->Android Device Monitor 打开即可使用,通过点击Start Method Profiling(为红色)按钮开始进行方法调用跟踪(点击后会变为黑色),停止时再点击停止,即可生成trace文件进行分析。

需要注意的是,在AS3.0+ DDMS从面板上移除了,官方给的说法是:

However, most components of the Android Device Monitor are deprecated in favor of updated tools available in Android Studio 3.0 and higher.

那就手动的去启动就OK了。(在Terminal中输入命令 monitor 启动DDMS)

2.通过代码进行追踪

在需要分析的代码中添加

开始

Debug.startMethodTracing()

结束

Debug.stopMethodTracing()

这样就会生成.trace文件了。

三、分析trace文件

在面板中会有各种数据,这个时候就需要搞清楚各个参数所代表的具体含义了。

最关心的数据有:
很重要的指标:Calls + Recur Calls / Total , 最重要的指标: Cpu Time / Call
因为我们最关心的有两点,一是调用次数不多,但每次调用却需要花费很长时间的函数。这个可以从Cpu Time / Call反映出来。另外一个是那些自身占用时间不长,但调用却非常频繁的函数。这个可以从Calls + Recur Calls / Total 反映出来。

当体验卡顿的时候,我们可以借助TraceView来定位问题

Java垃圾回收机制浅析

发表于 2018-10-21 |

JVM垃圾回收机制

背景

一、为什么需要进行垃圾回收?

在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象;而在Java中,当没有对象引用指向原先分配给某个对象 的内存时,该内存便成为垃圾。 垃圾回收能自动释放内存空间,减轻编程的负担,JVM的一个系统级线程会自动释放该内存块。垃圾回收意味着程序不再需要的对象是”无用信息”,这些信息将被丢弃。当一个对 象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片。由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,内存会出现碎片。碎片是分配给对象的内存块之间的空闲内存洞。碎片整理将所占用的堆内存移到堆的一端,JVM将整理出的内存分配给新的对象。

垃圾回收算法

一、标记清除算法

标记-清除算法分为标记和清除两个阶段。该算法首先从根集合进行扫描,对存活的对象对象标记,标记完毕后,再扫描整个空间中未被标记的对象并进行回收。

标记清除

弊端:

1.效率:标记和清除的效率都不高

2.空间:不需要对对象进行移动,仅对不存活的对象进行移除操作,对存活的对象不做处理,标记清除后会产生大量的不连续的内存碎片,空间碎片太多的话,当需要分配较大的对象的时候,就无法找到足够的连续的连续的内存而不得不进行另一次的垃圾回收动作。

内存碎片弊端

二、复制算法

复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。

当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这种算法适用于对象存活率低的场景,比如新生代。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

现在商用的虚拟机都采用这种算法来回收新生代。

复制算法

因为研究发现,新生代中的对象每次回收都基本上只有10%左右的对象存活,所以需要复制的对象很少,效率还不错。

实践中会将新生代内存分为一块较大的Eden空间和两块较小的Survivor空间。

当回收时,将Eden和Survivor中还存活着的对象一次地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间

新生代回收

弊端:在对象存活率比较高的情况下,那么进行垃圾回收的时候就会进行大量的复制操作,效率比较低。

三、标记整理算法

复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况

所以在老年代一般不能直接选用这种算法。标记整理算法的标记过程类似标记清除算法,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,类似于磁盘整理的过程,该垃圾回收算法适用于对象存活率高的场景(老年代)

标记整理算法

标记整理算法与标记清除算法最显著的区别是:标记清除算法不进行对象的移动,并且仅对不存活的对象进行处理;而标记整理算法会将所有的存活对象移动到一端,并对不存活对象进行处理,因此其不会产生内存碎片,可看做标记-清除算法的进阶版。

对存活对象进行整理

四、分代收集算法

对于比较大型的系统来说,当创建的对象和方法变量比较多时,堆内存中的对象也会比较多,如果逐一分析对象是否该回收,那么势必造成效率低下。分代收集算法是基于这样一个事实:不同的对象的生命周期(存活情况)是不一样的,而不同生命周期的对象位于堆中不同的区域,因此对堆内存不同区域采用不同的策略进行回收可以提高 JVM 的执行效率。当代商用虚拟机使用的都是分代收集算法:新生代对象存活率低,就采用复制算法;老年代存活率高,就用标记清除算法或者标记整理算法。Java堆内存一般可以分为新生代、老年代和永久代三个模块。

堆内存分代

1.新生代(Young Generation)

新生代的目标就是尽可能快速的收集掉那些生命周期短的对象,一般情况下,所有新生成的对象首先都是放在新生代的。

新生代内存按照 8:1:1 的比例分为一个eden区和两个survivor(survivor0,survivor1)区,大部分对象在Eden区中生成。
在进行垃圾回收时,先将eden区存活对象复制到survivor0区,然后清空eden区,当这个survivor0区也满了时,则将eden区和survivor0区存活对象复制到survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后交换survivor0区和survivor1区的角色(即下次垃圾回收时会扫描Eden区和survivor1区),即保持survivor0区为空,如此往复。

特别地,当survivor1区也不足以存放eden区和survivor0区的存活对象时,就将存活对象直接存放到老年代。如果老年代也满了,就会触发一次FullGC,也就是新生代、老年代都进行回收。

注意,新生代发生的GC也叫做MinorGC,MinorGC发生频率比较高,不一定等 Eden区满了才触发。

2.老年代(Old Generation)

老年代存放的都是一些生命周期较长的对象,就像上面所叙述的那样,在新生代中经历了N次垃圾回收后仍然存活的对象就会被放到老年代中。此外,老年代的内存也比新生代大很多(大概比例是1:2),当老年代满时会触发Major GC(Full GC),老年代对象存活时间比较长,因此FullGC发生的频率比较低。

3.永久代(Permanent Generation)

永久代主要用于存放静态文件,如Java类、方法等。永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如使用反射、动态代理、CGLib等bytecode框架时,在这种时候需要设置一个比较大的永久代空间来存放这些运行过程中新增的类。

1234…14
Neil Liu

Neil Liu

优秀不够,你是否无可替代

68 日志
25 标签
GitHub
© 2019 Neil Liu
由 Hexo 强力驱动
主题 - NexT.Muse