线上日志反馈内存溢出问题。根据用户反馈,客户操做一段时间以后,APP 内存溢出崩溃。html
(1) 分析线上日志,发现主要分两种:java
第一种以下,多是某个死循环致使内存不停增大致使:web
java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available数据库
第二种以下,压死骆驼的最后一根稻草:性能优化
java.lang.OutOfMemoryError: Failed to allocate a 16 byte allocation with 0 free bytes and 3GB until OOM, max allowed footprint 536872136, growth limit 536870912服务器
这两种状况下都没法定位问题所在。app
经过观察发生的版本信息,是从V9.5.6开始的,因而就开始查那期改动的功能(主要是webview优化,其余的是SDK更新),因为没有线上出问题的机型,就找个相似的华为机器操做webview,发现内存增加并不快,操做很长时间内存变化不大。jvm
(2) 分析用户操做路径ide
让工程导出出现OOM问题用户最近的操做记录,埋点,页面停留,崩溃日志,观察进入了哪些页面,可是照着操做路径操做,也没发现崩溃。工具
(3) 统计出现问题的机型,前五都是华为的,系统版本7.0以上,内存3GB以上。
(4) 使用LeakCanary + Android Profiler排查泄漏的地方,查看线上日志的时候,偶然发现测试有一部手机出现了问题,华为P8,而后使用工具根据用户操做路径查各个模块泄漏问题,发现委托登陆有个持续的10M泄漏,比较大的泄漏还有开屏广告页和行情登陆页面34M。这个手机给APP分配的可用内存最大为256M,操做几回登陆页面就崩溃了。
开屏页:
private ScheduledFuture<?> mTaskFuture = null;
private void gotoMain() {
...
AdCountDownTask adCountDownTask = new AdCountDownTask();
HexinThreadPool.cancelTaskFutre(mTaskFuture, true);
mTaskFuture = HexinThreadPool.getThreadPool().sheduleWithFixedDelay(xxx);
handler.sendEmptyMessageDelayed(xxx, xxx);
}
protected void onDestory() {
...
if (mTaskFuture != null) {
HexinThreadPool.cancelTaskFutre(mTaskFuture, true);
mTaskFuture = null;
}
}
复制代码
委托登陆
public class AdsCT {
private Runnable runable = new Runnable {
// 轮播图片
handler.postDelay(runnable, time);
}
public void onRemove() {
handler.removeCallbacksAndMessages(null);
}
}
public class WeituoLogin {
public void onRemove() {
adsCt.onRemove();
}
}
复制代码
(5) 修改查出来比较大的内存泄漏,券商给以前出问题的两个用户单独安装,发现仍是存在,券商反馈有个用户安装9.4.5的一直都没问题,升级到最新以后就出现问题了,如今怀疑仍是9.4.6哪一个模块致使的。
全称“Out Of Memory”,翻译成中文就是“内存用完了”,来源于java.lang.OutOfMemoryError。
方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。
内存泄露:申请使用完的内存没有释放,致使虚拟机不能再次使用该内存,此时这段内存就泄露了,由于申请者不用了,而又不能被虚拟机分配给别人用。
内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证实此对象是不可用的。不可达对象。
申请一块内存时,若是当前可用内存不足,则会出发一次GC,而后再次申请,此时若是内存还不足,则会抛出OOM。
// 错误示例,内部类持有外部对象致使泄漏
private class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
// 修改后,在activity onDestory()的时候,调用myHandler.removeCallbacksAndMessages(null);
private static class MyHandler extends Handler {
private WeakReference<MainActivity> mainActivityWeakReference;
public MyHandler(MainActivity mainActivity) {
mainActivityWeakReference = new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = mainActivityWeakReference.get();
if (mainActivity == null) {
return;
}
mainActivity.jump(null);
super.handleMessage(msg);
}
}
// context被线程持有
private void registerException(final Context context) {
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
String appName = context.getResources().getString(R.string.app_name);
}
};
}
复制代码
strong soft weak phantom(其实还有一种FinalReference,这个由jvm本身使用,外部没法调用到),主要的区别体如今gc上的处理,以下:
Strong类型,也就是正常使用的类型,不须要显示定义,只要没有任何引用就能够回收
SoftReference类型,若是一个对象只剩下一个soft引用,在jvm内存不足的时候会将这个对象进行回收
WeakReference类型,若是对象只剩下一个weak引用,那gc的时候就会回收。和SoftReference均可以用来实现cache
PhantomReference类型,
private void testStream() {
InputStream in = null;
try {
in = new FileInputStream("xxx.xml");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
}
}
复制代码
单例持有Context,监听器等
Activity的监听,是在Activity onDestory()执行以后再看是否被回收,在Application建立的时候,注册了监听
Application.ActivityLifecycleCallbacks lifecycleCallbacks = new..{
...
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
}
复制代码
工程中查看Page是否被释放,能够在onRemove()的时候使用
RefWatcher watch(Object obj) 复制代码
使用的WeakReference+ReferenceQueue实现
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
复制代码
若是软引用或弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用或弱引用加入到与之关联的引用队列中,由此可判断对象是否被回收。
@Override public void runGc() {
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization(); //强制调用已经失去引用的对象的finalize方法,确保释放实例占用的所有资源。
}
private void enqueueReferences() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new AssertionError();
}
}
};
复制代码
(1) 对内存文件进行分析,因为这个过程比较耗时,所以最终会把这个工做交给运行在另一个进程中的HeapAnalyzerService来执行。
(2) 利用HAHA将以前dump出来的内存文件解析成Snapshot对象,解析获得的Snapshot对象直观上和咱们使用MAT进行内存分析时候罗列出内存中各个对象的结构很类似,它经过对象之间的引用链关系构成了一棵树,咱们能够在这个树种查询到各个对象的信息,包括它的Class对象信息、内存地址、持有的引用及被持有的引用关系等。
(3) 为了可以准确找到被泄漏对象,LeakCanary经过被泄漏对象的弱引用来在Snapshot中定位它。由于,若是一个对象被泄漏,必定也能够在内存中找到这个对象的弱引用,再经过弱引用对象的reference就能够直接定位被泄漏对象。
(4) 在Snapshot中找到一条有效的到被泄漏对象之间的引用路径,从被泄露的对象开始,采用的方法是相似于广度优先的搜索策略,将持有它引用的节点(父节点),加入到一个FIFO队列中,一层一层向上寻找,哪条路径最早到达GCRoot就表示它应该是一条最短路径。
(5) 将以前查找的最短路径转换成最后须要显示的LeakTrace对象,这个对象中包括了一个由路径上各个节点LeakTraceElement组成的链表,表明了检查到的最短泄漏路径。最后一个步骤就是将这些结果封装成AnalysisResult对象而后交给DisplayLeakService进行处理。这个service主要的工做是将检查结果写入文件,以便以后可以直接看到最近几回内存泄露的分析结果,同时以notification的方式通知用户检测到了一次内存泄漏。使用者还能够继承这个service类来并实现afterDefaultHandling来自定义对检查结果的处理,好比将结果上传刚到服务器等。
某些状况下是能够的,好比说局部申请一个很大的内存,若是形成了OOM,在catch的地方释放掉,程序仍是能够继续往下执行的,可是若是捕获以后释放不掉,也仍是会崩溃。
编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?
public com.xxx.Outter$Inner(com.cxh.test2.Outter);
复制代码
咱们在定义的内部类的构造器是无参构造器,编译器仍是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,因此成员内部类中的Outter this&0 指针便指向了外部类对象,所以能够在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,若是没有建立外部类的对象,则没法对Outter this&0引用进行初始化赋值,也就没法建立成员内部类的对象了。
外部类和内部类编译后会生成两个.class文件,若是局部变量的值在编译期间就能够肯定,则直接在匿名内部里面建立一个拷贝。若是局部变量的值没法在编译期间肯定,则经过构造器传参的方式来对拷贝进行初始化赋值。 拷贝的话,若是一个值改变了,就会形成数据不一致性,为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不容许对变量a进行更改(对于引用类型的变量,是不容许指向新的对象),这样数据不一致性的问题就得以解决了。
private static StaticObj staticObj = new StaticObj();
static class StaticObj {
@Override
protected void finalize() throws Throwable {
staticObj = new StaticObj();
super.finalize();
}
}
复制代码
// System.gc()
/** * If we just ran finalization, we might want to do a GC to free the finalized objects. * This lets us do gc/runFinlization/gc sequences but prevents back to back System.gc(). */
private static boolean justRanFinalization;
public static void gc() {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = justRanFinalization;
if (shouldRunGC) {
justRanFinalization = false;
} else {
runGC = true;
}
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
}
public static void runFinalization() {
boolean shouldRunGC;
synchronized (LOCK) {
shouldRunGC = runGC;
runGC = false;
}
if (shouldRunGC) {
Runtime.getRuntime().gc();
}
Runtime.getRuntime().runFinalization();
synchronized (LOCK) {
justRanFinalization = true;
}
}
复制代码
参考: