> 强引用、软引用、弱引用和虚引用,Java中的引用类型
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中的线程优先级是很低的,执行的次数比较少。
虚引用:垃圾回收时回收,无法通过引用取到对象值
强引用:默认的引用类型,例如StringBuffer buffer = new StringBuffer();就是buffer变量持有的为StringBuilder的强引用类型。
软引用:即SoftReference,其指向的对象只有在内存不足的时候进行回收。
弱引用:即WeakReference,其指向的对象在GC执行时会被回收。
虚引用:即PhantomReference,与ReferenceQueue结合,用作记录该引用指向的对象已被销毁。
-- crash 堆栈信息。从 crash 收集平台上来看,有几个类似的堆栈信息。
Android 生命周期管理不当带来的最大问题就是内存泄露.
- 内存泄漏的根本原因是一个长生命周期对象持有一个短生命周期对象,造成短生命周期对象没有办法被回收所导致的。
内存泄露的危害:只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制。
1、单例造成的内存泄漏。
2、非静态内部类创建静态实例造成的内存泄漏。
3、Handler造成的内存泄漏。
4、线程造成的内存泄漏。
5、资源对象没关闭造成的内存泄漏。
6、Bitmap没有回收导致的内存溢出
-- 即 ML (Memory Leak),内存引用:
指 程序在申请内存后,当该内存不需再使用 但 却无法被释放 & 归还给 程序的现象。
> 野指针与内存泄漏
内存泄漏和野指针- https://blog.csdn.net/Code_ZX/article/details/80554212
野指针:C语言: 当我们声明1个指针变量,没有为这个指针变量赋初始值.这个指针变量的值是1个垃圾指 指向1块随机的内存空间。 OC语言: 指针指向的对象已经被回收掉了.这个指针就叫做野指针.
野指针,也就是指向不可用内存区域的指针。如果对野指针进行操作,将会使程序发生不可预知的错误,甚至可能直接引起崩溃。野指针不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是野指针是很危险的,也具有很强的掩蔽性,if语句对它不起作用。
内存泄漏:①访问已经释放的内存; ②访问没有权限的内存
野指针:指向内存被释放的内存或者没有访问权限的内存的指针。
造成野指针的原因?
1、指针变量没有被初始化(如果值不定,可以初始化为NULL)
2、指针被free/delete后没有置空(free/delete只是把指针所指向的内存释放掉,并没有把指针本身干掉,此时指针指向的是”垃圾“内存,释放后指针应置NULL)
野指针:指向被释放的或者访问受限内存的指针。造成野指针的原因:
1.指针变量没有被初始化(如果值不定,可以初始化为NULL)
2.指针被free或者delete后,没有置为NULL, free和delete只是把指针所指向的内存给释放掉,并没有把指针本身干掉,此时指针指向的是“垃圾”内存。释放后的指针应该被置为NULL.
3.指针操作超越了变量的作用范围,比如返回指向栈内存的指针就是野指针。
内存泄漏危害:
从用户使用程序的角度来看,内存泄漏本身不会产生什么危害。作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积。而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。存在内存泄漏问题的程序除了会占用更多的内存外,还会使程序的性能急剧下降。对于服务器而言,如果出现这种情况,即使系统不崩溃,也会严重影响使用。
不过还有一点,如果你的程序内存泄露正好写到了系统使用的内存或者其他程序使用的内存地址,那么就会导致系统异常或者程序崩溃
内存溢出:
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
-- 什么叫内存泄漏?内存溢出?
内存溢出(out of memory):是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄漏(memory leak):是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
so: memory leak会最终会导致out of memory!
-- 内存泄漏场景:
1.单例造成的内存泄漏(Context持久引用)
2.非静态内部类创建静态实例造成的内存泄漏(Context持久引用)
3.Handler造成的内存泄漏(Context持久引用、Message对象引用)
4.线程、TimeTask造成的内存泄漏(Activity的隐式引用、合适的时候cancel)
5.资源未关闭、监听器未反注册造成的内存泄漏(BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap)
6.使用ListView时造成的内存泄漏(getView未复用convertView)
7.集合造成内存泄漏(static引用造成大量垃圾回收不了)
8.WebView造成的泄露(长期占用的内存不能被回收)
避免内存泄露:
概念:指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光
1.不要为Context长期保存引用(要引用Context就要使得引用对象和它本身的生命周期保持一致)
2.如果要使用到Context,尽量使用ApplicationContext去代替Context,因为ApplicationContext的生命周期较长,引用情况下不会造成内存泄露问题
3.在你不控制对象的生命周期的情况下避免在你的Activity中使用static变量。尽量使用WeakReference去代替一个static
4.垃圾回收器并不保证能准确回收内存,这样在使用自己需要的内容时,主要生命周期和及时释放掉不需要的对象。尽量在Activity的生命周期结束时,在onDestroy中把我们做引用的其他对象做释放,比如:cursor.close()
> 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工具进行转换。
-- 使栈溢出(栈内存溢出,栈上的数据、变量溢出)
public class OutOfStatck{
public static void main(String[] args){
System.out.println("OK");
out(1);
}
private static void out(int i){
System.out.println(i);
out(++i);
}
}
-- 使堆溢出(堆内存溢出,堆是程序可以进行操作的内存区域,进程内存的分配和回收)
public class OutOfHeap{
public static void main(String[] args){
byte[] b = new byte[1024 * 1024 * 1024 * 1];
System.out.println("OK");
}
}
> 堆栈溢出等
Java虚拟机是通过某些数据类型来执行计算的,数据类型可以分为两种:基本类型和引用类型,基本类型的变量持有原始值,而引用类型的变量持有引用值。
Java虚拟机的引用类型被统称为“引用(reference)”,有三种引用类型:类类型、接口类型、以及数组类型,它们的值都是对动态创建对象的引用。类类型的值是对类实例的引用;数组类型的值是对数组对象的引用,在Java虚拟机中,数组是个真正的对象;而接口类型的值,则是对实现了该接口的某个类实例的引用。还有一种特殊的引用值是null,它表示该引用变量没有引用任何对象。
JVM数据访问的方式有两种:一种是句柄形式,引用指向句柄,句柄包含对象地址和对象类型;一种是指针,直接存储对象地址,以句柄少一步,所以访问也会快一些,而HotSpot就是用这种;前者也有一定优化,值发生改变时,引用不用变,后者要改变指针才行。
内存异常有两种表现,一种叫OutOfMemoryError(内存溢出),请求的虚拟机扩展栈已无足够空间,分配给新对象,典型的标记-清理算法容易产品这种情况,另一种叫StackOverflowError(内存泄露),请求的栈深度超过虚拟机所允许 ,例如下标超过数据大小,一般线程不同步会引起这种状况的产生。
Dalvik与JVM的最大差别在于,前者基于寄存器架构(句柄引用),后者基于栈架构(指针引用)
-- 使栈溢出(栈内存溢出,栈上的数据、变量溢出)
public class OutOfStatck{
public static void main(String[] args){
System.out.println("OK");
out(1);
}
private static void out(int i){
System.out.println(i);
out(++i);
}
}
-- 使堆溢出(堆内存溢出,堆是程序可以进行操作的内存区域,进程内存的分配和回收)
public class OutOfHeap{
public static void main(String[] args){
byte[] b = new byte[1024 * 1024 * 1024 * 1];
System.out.println("OK");
}
}
> OOM
> NullPointerException