查漏补缺(面试),对象引用与OOM等问题

> 强引用、软引用、弱引用和虚引用
Java 对象引用方式 —— 强引用、软引用、弱引用和虚引用- https://www.cnblogs.com/renhui/p/6069437.html
Java四种引用包括强引用,软引用,弱引用,虚引用- https://www.cnblogs.com/yw-ah/p/5830458.html
java强引用,软引用,弱引用,虚引用- https://blog.csdn.net/liaodehong/article/details/52223354
  强引用StrongReference:最常见的引用类型,jvm即使是oom也不会回收拥有强引用的对象,只要引用存在,垃圾回收器永远不会回收
  软引用SoftReference:在jvm内存不够的时候就会回收拥有软引用的对象,在jvm内存充足的时候不会回收
  弱引用WeakReference:跟软引用对象不一样的是,弱引用对象会在每一次的gc中被回收,不管jvm的内存怎么样,但是gc在jvm中的线程优先级是很低的,执行的次数比较少。
  虚引用:垃圾回收时回收,无法通过引用取到对象值

> mHandler.post(new Runnable()
将要执行的内容重写到run()方法中,然后将Runnable()接口封装成Message加入到handler的消息队列中(由post()方法完成);
Runnable接口实现线程,Handler消息队列更新UI- https://blog.csdn.net/u010698072/article/details/51675638
mHandler.post(new Runnable(){  好像是new 了一个 interface, 其实是new的一个实现Runnable的匿名内部类(Inner Anonymous Class)
Runnable是一个接口,不是一个线程,一般线程会实现Runnable。 所以如果我们使用匿名内部类是运行在UI主线程的,如果我们使用实现这个Runnable接口的线程类,则是运行在对应线程的。

  多线程,多线程和多进程不同;多进程的难点在于如何互相交互数据,由于操作系统对进程的保护措施,因此进程之间无法直接访问对方的内存区域;也就无法直接进行数据的交互;而同一进程内多线程是共享一个进程的资源的;因此多线程需要解决的问题是如何保证对资源的同步;
  每一个线程都可以有一个存储别的任务的队列(MessageQueue),其他的线程可以给这个队列里提交任务(Runnable);而除了队列之外,我们线程内必须有一个对象专门负责处理队列中的请求(Looper);

 Android将任务封装成Message对象,Message里面有Runnable对象,有一些标识id,参数等等;因此在Android中,Message就被作为一个需求来提交到队列中。

  Looper的模型是单例,然后进入到循环中,在循环总执行以下操作:
从MessageQueue中获取Message;执行Message;回收Message;
  而Handler是什么呢,Handler的作用就是提供压入MessageQueue的接口方法,提供执行Message的接口方法;Handler的构建离不开Looper,因为Handler必须是隶属于某一个线程的,他要提供压入MessageQueue的方法,因此必须需要Looper,因为Handler所压入的MessageQueue对象就是Looper所拥有的;Looper在每一个线程中都是单例的,这就避免了多个线程并行的情况。Looper自带有MessageQueue队列对象,因此不管是入队还是出队都是在自带的这个MessageQueue队列里。

Runnable 并不一定是新开一个线程,比如下面的调用方法就是运行在UI主线程中的:
Handler mHandler=new Handler();
mHandler.post(new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
}
});

//这里post()方法内部依然是将updatethread封装成Message加入到Handler的消息队列中,在handler所相连的线程(这里是在主线程)中去执行,因此和使用handleMessage()没有本质的区别。
        handle.post(updatethread);
}

> handle.post(updatethread)引起的匿名内部类
特点的话,除了只能使用一次;
匿名内部类是局部内部类的更深入一步;
假如只创建某类的一个对象时,就不必将该类进行命名;
匿名内部类的前提是存在一个类或者接口,且匿名内部类是写在方法中的;
只针对重写一个方法时使用,需要重写多个方法时不建议使用。

  1.什么是内部类呢?内部类就是在类的内部创建一个类,为什么我们要在类的内部创建一个类呢?不直接在类的外面直接创建另一个类呢?何必这么麻烦(因为我定义的这个内部类仅仅在本类中是有用的,其他的类使用完全没有意义,所以我就定义在一个类的内部仅仅供给这个类来使用。)
  2.什么是匿名内部类呢?就更有意思了,就是所我定义的这个类在本类里面我就都认为他是没有意义的,因为我只需要提供给本类中的一个方法来使用,其他方法不需要使用嘛。(所以我们就不在类的内部定义了,直接在一个方法中的返回符(;)之前我们就给他new ()并写出来,这样这个类就仅仅提供给这个方法使用)
  3.什么时候使用匿名内部类,什么时候使用匿名内部类呢?

就是定义的这个类如果提供给两个或者两个以上的方法使用时就是用内部类、如果仅仅提供给一个方法使用时可以使用匿名内部类。

Java内部类和匿名内部类的用法- https://blog.csdn.net/guyuealian/article/details/51981163
 内部类(nested classes),面向对象程序设计中,可以在一个类的内部定义另一个类。嵌套类分为两种,即静态嵌套类和非静态嵌套类。静态嵌套类使用很少,最重要的是非静态嵌套类,也即是被称作为内部类(inner)。内部类是JAVA语言的主要附加部分。内部类几乎可以处于一个类内部任何位置,可以与实例变量处于同一级,或处于方法之内,甚至是一个表达式的一部分。

内部类是JAVA语言的主要附加部分。嵌套类从JDK1.1开始引入。其中inner类又可分为三种:
 其一、在一个类(外部类)中直接定义的内部类;
 其二、在一个方法(外部类的方法)中定义的内部类;
 其三、匿名内部类。

为什么需要内部类?
⒈ 内部类对象可以访问创建它的对象的实现,包括私有数据;
⒉ 内部类不为同一包的其他类所见,具有很好的封装性;
⒊ 使用内部类可以很方便的编写事件驱动程序;
⒋ 匿名内部类可以方便的定义运行时回调;
5.内部类可以方便的定义

在使用匿名内部类时,要记住以下几个原则:
 a·匿名内部类不能有构造方法。
 b·匿名内部类不能定义任何静态成员、方法和类。
 c·匿名内部类不能是public,protected,private,static。
 d·只能创建匿名内部类的一个实例。
 e·一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
 f·因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。

> OOM的发生、原因和解决

OOM定位

图解Android 内存分析工具之Mat使用教程- https://blog.csdn.net/vfush/article/details/49511463

代码走查的方式来优化解决的,OOM发生在运行时.

AndroidRuntime: FATAL EXCEPTION: main Process: com.example.desaco.testandroid, PID: 8746
 java.lang.OutOfMemoryError: Failed to allocate a 207667212 byte allocation with 16777120 free bytes and 41MB until OOM

dalvik.vm.heapgrowthlimit=64m // 单个应用可用最大内存
主要对应的是这个值,它表示单个进程内存被限定在64m,即程序运行过程中实际只能使用64m内存,超出就会报OOM。(仅仅针对dalvik堆,不包括native堆)

 强引用: 通常我们编写的代码都是Strong Ref,eg :Person person = new Person("sunny");不管系统资源有多紧张,强引用的对象都绝对不会被回收,即使他以后不再用到。
 软引用:只要有足够的内存,就一直保持对象。一般可用来实现缓存,通过java.lang.r.efSoftReference类实现。内存非常紧张的时候会被回收,其他时候不会被回收,所以在使用之前需要判空,从而判断当前时候已经被回收了。
 弱引用:通过WeakReference类实现,eg : WeakReference p = new WeakReference(new Person("Rain"));不管内存是否足够,系统垃圾回收时必定会回收。
 虚引用:不能单独使用,主要是用于追踪对象被垃圾回收的状态。通过PhantomReference类和引用队列ReferenceQueue类联合使用实现。虚引用并不会决定对象的生命周期,也无法通过虚引用获得对象实例。虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用,来了解这个对象是否将要被回收。

出现OOM有几种情况:
1.加载对象过大
2.相应资源过多,来不及加载。
解决这些问题,有:
1.内存引用上做一些处理,常用的有软引用。
2.内存中加载图片直接在内存中做处理(如边界压缩),这个Glide\Fresco 图片框架可能封装好了
3.动态回收内存
4.优化Delivk虚拟机的堆内存分配
5.自定义堆内存大小 。

APP内存由 dalvik内存 和 native内存 2部分组成,dalvik也就是java堆,创建的对象就是就是在这里分配的,而native是通过c/c++方式申请的内存,Bitmap就是以这种方式分配的。(android3.0以后,系统都默认通过dalvik分配的,native作为堆来管理)。

查看APP内存分配情况?
 1.通过DDMS中的heap选项卡监视内存情况:
 Heap视图中部有一个叫做data object, 即数据对象,也就是我们的程序中大量存在的类类型的对象。
 在data object一行中有一列是“Total Size”, 其值就是当前进程中所有Java数据对象的内存总量。如果代码中存在没有释放对象引用的情况,则data object的“Total Size”值在每次gc后不会有美线的回落。随着操作次数的增加“Total Size”的值会越来越大。直到到达一个上限 后导致进程被kill掉。
 2.在App里面我们可以通过totalMemory与freeMemory:
Runtime.getRuntime().freeMemory()
RUntime.getRuntime().totalMemory()
 3.adb shell dumpsys meminfo com.android.demo
LRU: 在一级缓存中一致保存最近被访问到的bitmap对象,而已经被访问过的图片在LinkedHashMap的容量超过我们预设值时将会把容器中存在的时间最长的对象移除,这个时候我么可以将被移除的LinkedHashMap中的放到二级缓存容器,而二级缓存中的对象管理就交给系统来做了,当系统需要gc时就会首先回收二级缓存容器的Bitmap对象了。

 内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

android中常见的原因主要有以下几个:
1.数据库的cursor没有关闭。
2.构造adapter没有使用缓存contentview。
3.调用registerReceiver()后未调用unregisterReceiver().
4.未关闭InputStream/OutputStream。
5.Bitmap使用后未调用recycle()。
6.Context泄漏。
7.static关键字等。

> java.lang.NullPointerException
>静态方法可以重载但是不可以重写
Java 中的内存分配,主要是分三块:
静态储存区:编译时就分配好,在程序整个运行期间都存在。它主要存放静态数据和常量。
栈区:当方法执行时,会在栈区内存中创建方法体内部的局部变量,方法结束后自动释放内存。

堆区:通常存放 new 出来的对象。由 Java 垃圾回收器回收。


> OOM的发生原理与定位,解决 Android???
导致这样的异常主要有以下原因:①、加载大图片或数量过多的图片,因为图片是超级耗内存的,②、操作数据库的时候Cursor忘记关闭,③、资源未释放,比如io流,file等,④、内存泄露。我们用用的OOM主要是加载图片导致的。

友盟上频繁报OOM,使用过eclipse或者Android Studio dump过hprof文件并会使用MAT简单分析内存使用情况的Android开发人员.根据内存泄露的特点,重复打开关掉猜测的内存泄露的Activity,这个时候可以在Android Studio的 Monitors中看到内存会在一次打开关掉过程中积累增加,并没有随着关掉Activity而回收所有的内存,这说明应用是发生了内存泄露。

避免Context相关的内存泄露,记住以下事情:
1、 不要保留对Context-Activity长时间的引用(对Activity的引用的时候,必须确保拥有和Activity一样的生命周期)
2、尝试使用Context-Application来替代Context-Activity 3、如果你不想控制内部类的生命周期,应避免在Activity中使用非静态的内部类,而应该使用静态的内部类,并在其中创建一个对Activity的弱引用。
      这种情况的解决办法是使用一个静态的内部类,其中拥有对外部类的WeakReference。
2-3,Thread 引用其他对象也容易出现对象泄露。
2-4,onReceive方法里执行了太多的操作

内存监测工具 DDMS --> Heap
Android tools 中的 DDMS 就带有一个很不错的内存监测工具 Heap
内存分析工具 -- Memory Analyzer Tool(MAT)。

  MAT通过解析Hprof文件来分析内存使用情况。HPROF其实是在J2SE5.0中包含的用来分析CPU使用和堆内存占用的日志文件,实质上是虚拟机在某一时刻的内存快照,dalvik中也包含了这样的工具,但是其文件格式和JVM的格式不完全相同,可以用SDK中自带的hprof-conv工具进行转换。

> OOM


> NullPointerException