GVKun编程网logo

objective-c – Objective C / iOS:使用ARC释放内存(内存泄漏)(arc下如何释放内存)

3

这篇文章主要围绕objective-c–ObjectiveC/iOS:使用ARC释放内存(内存泄漏)和arc下如何释放内存展开,旨在为您提供一份详细的参考资料。我们将全面介绍objective-c–O

这篇文章主要围绕objective-c – Objective C / iOS:使用ARC释放内存(内存泄漏)arc下如何释放内存展开,旨在为您提供一份详细的参考资料。我们将全面介绍objective-c – Objective C / iOS:使用ARC释放内存(内存泄漏)的优缺点,解答arc下如何释放内存的相关问题,同时也会为您带来Andoid - 内存泄漏、Android 内存泄漏、Android 笔记 - Activity 相关 + 内存泄漏 + Fragment+service、Android 系统开发_内存泄漏篇 -- "内存泄漏"的前世今生的实用方法。

本文目录一览:

objective-c – Objective C / iOS:使用ARC释放内存(内存泄漏)(arc下如何释放内存)

objective-c – Objective C / iOS:使用ARC释放内存(内存泄漏)(arc下如何释放内存)

我是iOS / Objective-C的新手,我不理解正确释放内存.
为了测试它,我创建了一个空的启用ARC的iPhone-Project并创建了一个非常简单的测试类:
#import "MemTest.h"

@implementation MemTest {

}

-(void) start {
    for (int i = 0; i < 1500000; i++) {
        NSMutableString *myString = [NSMutableString string];

        // The appended string is 2000 characters long in the real test class.
        [myString appendString:@"12345678901234567890123456 <very long>"];

        if (i % 1000 == 0) {
            NSLog(@"i = %d",i);
        }

        myString = nil;
    }
}

@end

我只是在AppDelegate中开始测试:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    MemTest *test = [[MemTest alloc] init];
    [test start];

    ....
}

应用程序(正如预期的那样)打印许多不错的数字“i = xy”,但内存使用量随着每次迭代而增加,最后应用程序崩溃:

....
2012-12-06 20:17:40.193 MemTestApp[19250:11303] i = 930000
2012-12-06 20:17:40.208 MemTestApp[19250:11303] i = 931000
MemTestApp(19250,0xac63f2c0) malloc: *** mmap(size=16777216) Failed (error code=12)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug

所以我的问题是:为什么内存使用量会增长?

我认为通过分配nil,应该在使用ARC时释放内存.我在这里想念的是什么?

解决方法

有些事情可能会出错:

>您可能实际上没有启用ARC.你应该仔细检查一下.最简单的方法是在代码中引入-retain并确保抛出编译器错误.
> ARC不一定会阻止对象进入自动释放池.它试图抓住它,如果它可以,但它不能保证.值得注意的是,在-O0(无优化)时,它经常不会阻止对象进入自动释放池.这很可能是你发生的事情.
>即使在更高的优化级别,仍然不能保证启用ARC的代码能够捕获自动释放.

如果你在for循环中粘贴@autoreleasepool {},你会发现内存使用量应该消失.或者,不是使用[NSMutableString string],而是可以尝试[NSMutableString new],它根本不使用自动释放池*,但在ARC代码中应该具有相同的行为.

*嗯,如果需要,NSMutableString可以在内部自由使用自动释放池

Andoid - 内存泄漏

Andoid - 内存泄漏

什么是内存泄漏?

android 内存泄漏是指进程中的某些对象(垃圾对象)已经不再使用,但是他们仍然可以直接或间接的引用到GC roots 导致无法被GC回收。无用的对象占据着内存空间,使得实际可用的内存变小,形象的说法就是内存泄露了。

GC的跟搜索算法

Android 虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots,GC会选择一些它了解还存活的对象作为内存遍历的根节点,比方说thread stack中的变量,JNI中的全局变量,zygote中的对象(class loader加载)等)开始对heap进行遍历,部分没有直接或间接引用到GC Roots 的就是需要回收的垃圾,会被GC回收。

主要发生场景

  • Activity使用静态成员。   静态变量长期维持对大数据对象的引用,阻止垃圾回收。
  • 资源对象未及时关闭。  资源性对象如Cursor、File、Socket,应该在使用后及时关闭。未在finallay中关闭,会导致异常情况下资源对象未被释放的风险。
  • Handler临时性内存泄漏。   Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message 也不是马上就被处理的。在Message中存在一个成员变量target,是对handler的强引用,如果Message在Queue中存在的时间越长,就容易导致handler无法被回收。如果handler是非静态的,则会导致Activity或Service不会被回收。AsyncTask内部也是handler机制,同样存在内存泄漏的风险。这种内存泄漏一般是临时性的。
  • 非静态内部类的静态实例。
    非静态内部类会维持一个到外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持着外部类的引用,阻止被回收掉。
  • 注册对象未反注册。 未反注册会导致观察者列表里维持着对象的引用,阻止垃圾回收。常见的有注册广播接收器,注册观察者等。

预防

  • 不要维持到Activity的长久引用,一个activity的引用的生存周期应该和activity的生命周期相同。
  • 尽量使用context-application代替context-activity
  • Activity中尽量不要使用非静态内部类,可以使用静态内部类和WeakReference代替。

Android 内存泄漏

Android 内存泄漏

前言

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。有些内存泄漏是很难发现的,需要使用恰当的方法或者辅助工具才能检测到,这篇文章记一下 Android 应用程序中如何检测内存泄漏。

一、java 虚拟机运行时数据区域

java 虚拟机在执行 java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域,这些区域有着各自的创建时间和销毁时间,各自用途也不一样。java虚拟机运行时数据区域如下图:

1.1 程序计数器

程序计数器是一块较小的内存空间,是当前线程所执行的字节码的行号指示器。如果线程正在执行一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果是Naive方法,则计数器为空;这个区域不会出现OUtOfMemoryError异常。此区域也是唯一一个在java虚拟机规范中没有规定任何OUtOfMemoryError情况的区域。
java虚拟机多线程是使用线程轮流切换并分配处理执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。为了线程切换后能够恢复到正确的执行位置,每条线程都需要一套独立的线程计数器,这些计数器之间相互独立,独立存储,这个内存区域为“线程私有”。

1.2 java 虚拟机栈

java虚拟机栈也是线程私有,与线程的生命周期一致,在执行每个方法都会创建一个Stack Frame。每一个方法从开始执行到结束,对应一个Stack Frame在虚拟机值栈中从入栈和出栈的过程。如果线程请求的栈深度大于虚拟机所允许的深度,就会出现StackOverFlowException。如果允许动态扩展,在扩展的过程中,如果无法申请到足够的内存,则会抛出OutOfMemoryException异常。

1.3 本地方法栈

和java虚拟机栈的作用类似,不同点在本地方法栈主要是为虚拟机使用到的Native方法提供服务,本地方法栈也会抛出StackOverFlowException和OutOfMemoryException异常。

1.4 java 堆

堆是java虚拟机中内存中最大的一块,被所有线程共享的一块内存区域,在虚拟机创建时创建。作用就是存放对象实例,所有的对象的实例都需要在这里分配内存。几乎所有的对象实例和对象数组都需要在堆上分配。是java虚拟机内存回收的管理的重要区域,因此也被称为“GC”堆,可以被分为:新生代和老年代;Eden空间、From Survivor空间、To Survivor空间。如果堆中没有内存完成实例分配,并且堆也无法扩展时,则抛出OutOfMemoryException异常。

1.5 方法区

方法区和java堆一样,是各个线程共享的内存区域,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译的代码等数据。通常被开发人员成为“永久带”。这个区域的内存回收的目标就是针对常亮池的回收和对类型的卸载,也是较为难处理的部分。如果方法区的内存空间不满足内存分配需求时,Java虚拟机会抛出OutOfMemoryError异常

1.6 运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。它用来存放编译时期生成的字面量和符号引用,这些内容会在类加载后存放在方法区的运行时常量池中。运行时常量池可以理解为是类或接口的常量池的运行时表现形式。当创建类或接口时,如果构造运行时常量池所需的内存超过了方法区所能提供的最大值,Java虚拟机会抛出OutOfMemoryError异常。

二、java 四种引用类型

java 堆中存储的都是引用类型的数据,而 java 对象存在四种引用类型,分别是强引用、软引用、弱引用和虚引用。 Java中提供这四种引用类型主要有两个目的:
第一是可以让程序员通过代码的方式决定某些对象的生命周期;
第二是有利于JVM进行垃圾回收。

下面来阐述一下这四种类型引用的概念:

2.1 强引用

强引用类型是我们平时写代码的时候最常用的引用,指创建一个对象并把这个对象赋给一个引用变量。刚学习java的人都会忽略这个概念,成一种理所当然的事情了,比如:

Class Apple{}

Apple apple =new Apple();

如果某个对象被强引用所引用,这个引用又被其他对象所持有,那么JVM 宁愿抛出 OutOfMemory 错误也不会回收这种对象。

2.2 软引用

如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;

如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。

也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。

另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。

举个例子:

Class Apple{}

Apple apple = new Apple();
SoftReference<Apple> softRef = new SoftReference<Apple>(apple);

此时这个 Apple 对象有两个引用,一个是强引用 apple ,一个是软引用 softRef,若想该对象只被软引用引用,只需将强引用和该对象的连接断开。

apple = null;

在该对象被回收之前,我们也可通过 get 方法获得该对象,再声明一个强引用指向它即可:

Apple a = softRef.get();

此时 a 即是该对象的强引用。如果该对象已经被回收,那么 softRef.get() 将返回 null。

SoftReference 有两个构造方法:

public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
    }


public SoftReference(T referent, ReferenceQueue<? super T> q) {
    
}

作为一个Java对象,SoftReference对象除了具有保存软引用的特殊性之外,也具有Java对象的一般性。所以,当软可及对象被回收之后,虽然这个SoftReference对象的get()方法返回null,但这个SoftReference对象已经不再具有存在的价值,需要一个适当的清除机制,避免大量SoftReference对象带来的内存泄漏。在java.lang.ref包里还提供了ReferenceQueue。如果在创建SoftReference对象的时候,使用了一个ReferenceQueue对象作为参数提供给SoftReference的构造方法,那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的 Reference 对象。另外从 ReferenceQueue 这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。

2.3 弱引用

弱引用,就是引用与对象之间的联系很弱,弱到垃圾回收器会无视这个引用,直接回收对象。 弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。 弱引用也可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

Class Apple{}

Apple apple = new Apple();
WeakReference<Apple> weakRef = new WeakReference<Apple>(apple);
apple = null;

2.4 虚引用

“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

三 举例分析 java 堆中的内存泄漏

一般 java 中所说的内存泄漏指的是 java 堆中所产生的内存泄漏,java 堆中内存是由 java 虚拟机的“GC”所管理的,java 虚拟机实时监控到一些对象不可到达,即在以后的程序中不会被用到,触发 GC 时,这样的对象将会被回收掉,其所占的内存也将释放。从对象的生命周期角度来讲,如果一个长生命周期的对象持有了一个短生命周期对象的引用,将导致这个短生命周期的对象无法回收,其所占的内存释放不了,就会有内存泄漏的风险。

3.1 内存泄漏举例分析 —— Handler 使用不当造成的内存泄漏

Android 中使用 Handler 造成内存泄漏的代码:

public class MainActivity extends AppCompatActivity {

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ...
    }
}

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?) 而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

3.2 内存泄漏的检测

3.2.1 集成 LeakCanary 框架

LeakCanary 开发者应该都会使用,不熟悉的新同学可以参考 LeakCanary 中文使用说明

3.2.2 使用 Android Profiler 检测

使用内存分析器来执行以下操作:

  • 在可能导致性能问题的时间轴中寻找不良的内存分配模式
  • Dump Java堆,以便在任何时间查看哪些对象正在使用内存。长时间的堆转储可以帮助识别内存泄漏。
  • 在正常和极端的用户交互过程中记录内存分配,以精确地确定您的代码在短时间内分配的对象或分配被泄漏的对象。
    内存分析器概述:

如上图所示,内存分析器的默认视图包括以下内容:

① 强制执行垃圾收集事件的按钮。
② 捕获堆转储的按钮。
③ 记录内存分配的按钮。
④ 放大时间线的按钮。
⑤ 跳转到实时内存数据的按钮。
⑥ 事件时间线显示活动状态、用户输入事件和屏幕旋转事件。
⑦ 内存使用时间表,其中包括以下内容:

  • 每个内存类别使用多少内存的堆栈图,如左边的y轴和顶部的颜色键所示。
  • 虚线表示已分配对象的数量,如右侧y轴所示。
  • 每个垃圾收集事件的图标。

查看堆转储时,查看分配了多少内存的快照很有用,它不会显示如何分配内存。为此,您需要记录内存分配。完成记录会话后,您可以看到以下记录的持续时间:

分配了哪些对象以及它们使用了多少空间。 在堆栈跟踪中分配每个对象的位置,其中包括线程。

要查看应用程序的内存分配,请单击内存分析器工具栏中的Record memory allocations。当它记录时,与你的应用程序进行交互,以引起内存溢出或内存泄漏。完成后,单击Stop recording。

要检查分配记录,请按照下列步骤操作:

  • 浏览列表以查找具有非常大的堆计数且可能泄漏的对象,要帮助查找已知类,请单击类名列标题按字母顺序排序。然后单击一个类名,Instance View 窗格就会显示在右侧,显示该类的每个实例,如下图所示。
  • 在Instance View窗格中,单击一个实例。Call Stack选项卡显示在下面,显示了哪个实例被分配在哪个线程中。
  • 在Call Stack选项卡中,单击任意行可以在编辑器中跳转到该代码。

默认情况下,列表是按类名排列的。在列表的顶部,您可以使用右下拉菜单在列表之间切换:

  • Arrange by class: 根据类名分配。
  • Arrange by package:根据包名分配。
  • Arrange by callstack: 根据调用堆栈排序

要捕获堆转储,单击Memory-Profiler工具栏中的dump Java堆,然后分析结合上面选项分析,针对上述示例,结果如下图:

分析结果已经很明确了。 上面示例比较简单,针对复杂的问题,我们还可以使用 eclipse-MAT 工具加以分析。

3.2 利用软引用和弱引用解决OOM问题

根据前面讲了关于软引用和弱引用相关的基础知识,那么到底如何利用它们来优化程序性能,从而避免OOM的问题呢?
下面举个例子,假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题。
设计思路是:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。在Android开发中对于大量图片下载会经常用到。

针对上述例子,首先我们把匿名内部类 Handler 声明为静态内部类,这样Handler类中就不持有 Activity 的引用了。但是,这样在 Handler 中要使用 activity 对象,则通过弱引用来解决这个问题。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // ...
    }

    private static class NoLeakHandler extends Handler {
        
        //持有弱引用MainActivity,GC回收时会被回收掉.
        private WeakReference<MainActivity> mActivity;

        public NoLeakHandler(MainActivity activity) {
            mActivity = new WeakReference<>(activity);
        }
 
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }
}

当然针对 Handler 的内存泄漏,我们也可以通过程序来解决:

  1. 在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
  2. 如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler方法:removeCallbacks(Runnable r)和removeMessages(int what),在页面销毁时把消息对象从消息队列移除就行了。
@Override
public void onDestroy() {
   // 移除所有消息
   handler.removeCallbacksAndMessages(null);
   // 或者移除单条消息
   handler.removeMessages(what);
}

原文出处:https://www.cnblogs.com/joy99/p/10499276.html

Android 笔记 - Activity 相关 + 内存泄漏 + Fragment+service

Android 笔记 - Activity 相关 + 内存泄漏 + Fragment+service

看了下,上次学习 android 还是 17 年的事情,,,,两年过去了我现在终于来搞 android 了。。。

 

 

 

 

 

 

 

官网有一段基础描述:

https://developer.android.google.cn/guide/components/fundamentals

Android 应用采用 Java 编程语言编写。Android SDK 工具将您的代码 — 连同任何数据和资源文件 — 编译到一个 APK:Android 软件包,即带有 .apk 后缀的存档文件中。一个 APK 文件包含 Android 应用的所有内容,它是基于 Android 系统的设备用来安装应用的文件。

安装到设备后,每个 Android 应用都运行在自己的安全沙箱内:

  • Android 操作系统是一种多用户 Linux 系统,其中的每个应用都是一个不同的用户;
  • 默认情况下,系统会为每个应用分配一个唯一的 Linux 用户 ID(该 ID 仅由系统使用,应用并不知晓)。系统为应用中的所有文件设置权限,使得只有分配给该应用的用户 ID 才能访问这些文件;
  • 每个进程都具有自己的虚拟机 (VM),因此应用代码是在与其他应用隔离的环境中运行;
  • 默认情况下,每个应用都在其自己的 Linux 进程内运行。Android 会在需要执行任何应用组件时启动该进程,然后在不再需要该进程或系统必须为其他应用恢复内存时关闭该进程。

Android 系统可以通过这种方式实现最小权限原则。也就是说,默认情况下,每个应用都只能访问执行其工作所需的组件,而不能访问其他组件。 这样便营造出一个非常安全的环境,在这个环境中,应用无法访问系统中其未获得权限的部分。

不过,应用仍然可以通过一些途径与其他应用共享数据以及访问系统服务:

  • 可以安排两个应用共享同一 Linux 用户 ID,在这种情况下,它们能够相互访问彼此的文件。 为了节省系统资源,可以安排具有相同用户 ID 的应用在同一 Linux 进程中运行,并共享同一 VM(应用还必须使用相同的证书签署)。
  • 应用可以请求访问设备数据(如用户的联系人、短信、可装载存储装置 [SD 卡]、相机、蓝牙等)的权限。 用户必须明确授予这些权限。 如需了解详细信息,请参阅 使用系统权限。

https://developer.android.google.cn/guide/topics/resources/runtime-changes

有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 发生这种变化时,Android 会重启正在运行的 Activity(先后调用 onDestroy() 和 onCreate())。重启应用并恢复大量数据不仅成本高昂,而且给用户留下糟糕的使用体验。

如果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,那么因配置变更而引起的完全重启可能会给用户留下应用运行缓慢的体验。 此外,依靠系统通过 onSaveInstanceState() 回调为您保存的 Bundle,可能无法完全恢复 Activity 状态,因为它并非设计用于携带大型对象(例如位图),而且其中的数据必须先序列化,再进行反序列化,这可能会消耗大量内存并使得配置变更速度缓慢。 在这种情况下,如果 Activity 因配置变更而重启,则可通过保留 Fragment 来减轻重新初始化 Activity 的负担。此片段可能包含对您要保留的有状态对象的引用。

 

 

 

当 Android 系统因配置变更而关闭 Activity 时,不会销毁您已标记为要保留的 Activity 的片段。 您可以将此类片段添加到 Activity 以保留有状态的对象。

要在运行时配置变更期间将有状态的对象保留在片段中,请执行以下操作:

  1. 扩展 Fragment 类并声明对有状态对象的引用。
  2. 在创建片段后调用 setRetainInstance(boolean)
  3. 将片段添加到 Activity。
  4. 重启 Activity 后,使用 FragmentManager 检索片段。
注意:尽管您可以存储任何对象,但是切勿传递与 Activity 绑定的对象,
例如,Drawable、Adapter、View 或其他任何与 Context 关联的对象。
否则,它将泄漏原始 Activity 实例的所有视图和资源。
泄漏资源意味着应用将继续持有这些资源,但是无法对其进行垃圾回收,因此可能会丢失大量内存。

  

 

以下内容参考自:《Android 从学习到产品》,《深入理解 java 虚拟机》,《操作系统之哲学原理》

 

先甩一部分定义:

1. 每一个 Activity 都可以启动另一个 Activity 来完成不同的动作,每一次一个 Activity 启动,前一个 Activity 就停止了,但是系统保留一个 Activity 在一个栈上(Back stack)。当一个新的 Activity 启动时,它会被推送到栈顶,取得用户焦点。Back Stack 符合简单的 “后进先出” 原则,所以当用户完成当前 Activity 后单击 Back 按钮,它会被弹出栈(并且被摧毁),然后之前的 Activity 恢复。

 

这里延申一下(猜测,没有看过具体的 Android 底层,不确定是不是准确的),这个 Activity 存储的 back stack 应该是 jvm 中的 VM Stack。

同步一下 VM Stack 是什么,就要讨论 jvm 的运行,java 虚拟机在执行 java 程序的过程中,会把它所管理的内存划分为若干个不同的数据区域,VM Stack 是其中一个区域。

VM Stack (java virtual machine stacks)是线程私有的,他的生命周期与线程相同。(线程概念补充一下:运行中的程序叫进程,一个进程占有一段内存来执行这个程序,一个进程我们可以拆分为多个线程,多个线程使用同一段内存空间来协同工作完成进程。线程是进程里面的一个执行上下文,或者执行序列。对于单核来说,一个时间段只能 handle 一个线程,多核可以有多个线程同时执行,从而提高进程的执行速度)

VM stack 描述的是 java 方法执行的内存模型:每个方法执行的同时都会创建一个栈帧(stack Frame)用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

 

那么对于 Android 来说,内存泄漏的场景有:1.Activity 过多,内存占满,无法创建新的 Activity 对象;2. 多个 Activity 单例化后,一直占据内存资源,无法释放;

 

 

具体怎么做的还没有头绪,等开发完了再研究吧。

Android 系统开发_内存泄漏篇 --

Android 系统开发_内存泄漏篇 -- "内存泄漏"的前世今生

基础了解

什么是内存泄漏?

内存泄漏是当有程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并不是指物理上的内存消失,这里的内存泄漏是指由程序分配的内存,由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费。

Java 内存分配策略

Java 程序运行时的内存分配策略有三种,分别是 静态分配 、 栈式分配 和 堆式分配 ,对应的三种存储策略使用的内存空间主要分别是 静态存储区(也称方法区) 、 栈区 和 堆区 。

- 静态存储区(方法区):主要存放 静态数据 、 全局 static 数据 和 常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。

- 栈区:当方法被执行时,方法体内的 局部变量 (其中包括基础数据类型、对象的引用)都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

-  堆区: 又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存,也就是 对象的实例。这部分内存在不使用时将会由 Java 垃圾回收器(GC)来负责回收。

栈与堆的区别

在方法体内定义的(局部变量)一些基本类型的变量和对象的引用变量都是在方法的栈内存中分配的。当在一段方法块中定义一个变量时,Java 就会在栈中为该变量分配内存空间,当超过该变量的作用域后,该变量也就无效了,分配给它的内存空间也将被释放掉,该内存空间可以被重新使用。

堆内存用来存放所有由 new 创建的对象(包括该对象其中的所有成员变量)和数组。在堆中分配的内存,将由 Java 垃圾回收器来自动管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,这个特殊的变量就是我们上面说的引用变量。我们可以通过这个引用变量来访问堆中的对象或者数组。

举例说明:

public class Sample {
    int s1 = 0;
    Sample mSample1 = new Sample();

    public void method() {
        int s2 = 1;
        // Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,
        // 但 mSample2 指向的对象是存在于堆上
        Sample mSample2 = new Sample();
    }
}
// mSample3 指向的对象实体存放在堆上,包括这个对象的所有成员变量 s1 和 mSample1,
// 而它自己存在于栈中。
Sample mSample3 = new Sample();

Java是如何管理内存

Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为,GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。

监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

Java中的内存泄漏

在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收不回来。在Java中,这些不可达的对象都由GC负责回收,因此程序员不需要考虑这部分的内存泄露。

通过分析,我们得知,对于C++,程序员需要自己管理边和顶点,而对于Java程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式,Java提高了编程的效率。

因此,通过以上分析,我们知道在Java中也有内存泄漏,但范围比C++要小一些。因为Java从语言上保证,任何对象都是可达的,所有的不可达对象都由GC管理。

对于程序员来说,GC基本是透明的,不可见的。虽然,我们只有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义, 该函数不保证JVM的垃圾收集器一定会执行。因为,不同的JVM实现者可能使用不同的算法管理GC。通常,GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。除非在一些特定的场合,GC的执行影响应用程序的性能,例如对于基于Web的实时系统,如网络游戏等,用户不希望GC突然中断应用程序执行而进行垃圾回收,那么我们需要调整GC的参数,让GC能够通过平缓的方式释放内存,例如将垃圾回收分解为一系列的小步骤执行,Sun提供的HotSpot JVM就支持这一特性。

以下给出一个 Java 内存泄漏的典型例子:

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
    Object o = new Object();
    v.add(o);
    o = null;   
}

在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。

常见内存泄漏

永远的单例

单例的使用在我们的程序中随处可见,因为使用它可以完美的解决我们在程序中重复创建对象的问题,不过可别小瞧它。由于**单例的静态特性使得其生命周期跟应用的生命周期一样长 **,所以一旦使用有误,小心无限制的持有Activity的引用而导致内存泄漏。

我们看个例子:

public class AppManager {

    private static AppManager instance;
    private Context context;

    private AppManager(Context context) {
        this.context = context;
    }

    public static AppManager getInstance(Context context) {
        if (instance == null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要!** (实际常见)**

1、如果此时传入的是 Application 的 Context ,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。

2、如果此时传入的是 Activity 的 Context ,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

正确的方式(写法一):

public class AppManager {

    private static AppManager instance;
    private Context context;

    private AppManager(Context context) {
        this.context = context.getApplicationContext(); // 使用 Application 的 context
    }

    public static AppManager getInstance(Context context) {
        if (instance == null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

正确的方式(写法二):

// 在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context

...

context = getApplicationContext();

...
   /**
     * 获取全局的context
     * @return 返回全局context对象
     */
    public static Context getContext(){
        return context;
    }

public class AppManager {

    private static AppManager instance;
    private Context context;

    private AppManager() {
        this.context = MyApplication.getContext(); // 使用Application 的context
    }

    public static AppManager getInstance() {
        if (instance == null) {
            instance = new AppManager();
        }
        return instance;
    }
}

静态Activity

我们看一段代码:

public class MainActivity extends AppCompatActivity {
    private static MainActivity activity;         // 这边设置了静态Activity,发生了内存泄漏
    TextView saButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        saButton = (TextView) findViewById(R.id.text);
        saButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                setStaticActivity();
                nextActivity();
            }
        });
    }
    void setStaticActivity() {
        activity = this;
    }

    void nextActivity(){
        startActivity(new Intent(this,RegisterActivity.class));
        SystemClock.sleep(1000);
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

在上面代码中,我们声明了一个静态的 Activity 变量并且在 TextView 的 OnClick 事件里引用了当前正在运行的 Activity 实例,所以如果在 activity 的生命周期结束之前没有清除这个引用,则会引起内存泄漏。因为声明的 activity 是静态的,会常驻内存,如果该对象不清除,则垃圾回收器无法回收变量。

我们可以这样解决:

    protected void onDestroy() {
        super.onDestroy();
        activity = null;       // 在onDestory方法中将静态变量activity置空,这样垃圾回收器就可以将静态变量回收
    }

静态View

其实和静态Activity颇为相似,我们看下代码:

    ...
    private static View view;               // 定义静态View
    TextView saButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        saButton = (TextView) findViewById(R.id.text);
        saButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                setStaticView();
                nextActivity();
            }
        });
    }
    void setStaticView() {
        view = findViewById(R.id.sv_view);
    }
    ...

View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity,所以当activity生命周期结束了,静态View没有清除掉,还持有activity的引用,因此内存泄漏了。

我们可以这样解决:

protected void onDestroy() {
    super.onDestroy();
    view = null;         // 在onDestroy方法里将静态变量置空
} 

匿名类/AsyncTask

我们看下面的例子:

public class MainActivity extends AppCompatActivity {
    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                while(true);
            }
        }.execute();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        View aicButton = findViewById(R.id.at_button);
        aicButton.setOnClickListener(new View.OnClickListener() {
            @Override 
            public void onClick(View v) {
                startAsyncTask();
            }
        });
    }
}

上面代码在activity中创建了一个匿名类 AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在 Activity 里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。

我们可以这样解决:

自定义静态 AsyncTask 类,并且让 AsyncTask 的周期和 Activity 周期保持一致,也就是在 Activity 生命周期结束时要将 AsyncTask cancel 掉。

非静态内部类

有的时候我们可能会在启动频繁的Activity中,为了避免重复创建相同的数据资源,可能会出现这种写法:

public class MainActivity extends AppCompatActivity {

    private static TestResource mResource = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }

    class TestResource {
        //...
    }
}

上面这段代码在Activity内部创建了一个非静态内部类的单例(mManager),每次启动Activity时都会使用该单例的数据,这样虽然避免了资源的重复创建,不过这种写法却会造成内存泄漏。

因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

正确的做法为:

将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请按照上面推荐的使用Application 的 Context。当然,Application 的 context 不是万能的,所以也不能随便乱用,对于有些地方则必须使用 Activity 的 Context。

Handler

Handler 的使用造成的内存泄漏问题应该说是** 最为常见 **了,很多时候我们为了避免 ANR 而不在主线程进行耗时操作,在处理网络任务或者封装一些请求回调等api都借助Handler来处理,但 Handler 不是万能的,对于 Handler 的使用代码编写不规范即有可能造成内存泄漏。另外,我们知道 Handler、Message 和 MessageQueue 都是相互关联在一起的,万一 Handler 发送的 Message 尚未被处理,则该 Message 及发送它的 Handler 对象将被线程 MessageQueue 一直持有。

由于 Handler 属于 TLS(Thread Local Storage) 变量, 生命周期和 Activity 是不一致的。因此这种实现方式一般很难保证跟 View 或者 Activity 的生命周期保持一致,故很容易导致无法正确释放。

public class SampleActivity extends Activity {

    private final Handler mLeakyHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mLeakyHandler.postDelayed(new Runnable() {
            @Override
            public void run() { /* ... */ }
        }, 1000 * 60 * 10);

        // Go back to the previous Activity.
        finish();
    }
}

在该 SampleActivity 中声明了一个延迟 10分钟 执行的消息 Message,mLeakyHandler 将其 push 进了消息队列 MessageQueue 里。当该 Activity 被 finish() 掉时,延迟执行任务的 Message 还会继续存在于主线程中,它持有该 Activity 的 Handler 引用,所以此时 finish() 掉的 Activity 就不会被回收了从而造成内存泄漏(因 Handler 为非静态内部类,它会持有外部类的引用,在这里就是指 SampleActivity)。

正确的做法为:

在 Activity 中避免使用非静态内部类,比如上面我们将 Handler 声明为静态的,则其存活期跟 Activity 的生命周期就无关了。同时通过弱引用的方式引入 Activity,避免直接将 Activity 作为 context 传进去,见下面代码:

public class SampleActivity extends Activity {

    private static class MyHandler extends Handler {
        private final WeakReference<SampleActivity> mActivity;

        public MyHandler(SampleActivity activity) {
            mActivity = new WeakReference<SampleActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            SampleActivity activity = mActivity.get();
            if (activity != null) {                              // 每次使用前注意判空
                // ...
            }
        }
    }

    private final MyHandler mHandler = new MyHandler(this);

    private static final Runnable sRunnable = new Runnable() {
        @Override
        public void run() { /* ... */ }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Post a message and delay its execution for 10 minutes.
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

        // Go back to the previous Activity.
        finish();
    }
}

从上面的代码中我们可以看出如何避免Handler内存泄漏,推荐使用 "静态内部类 + WeakReference" 这种方式,每次使用前注意判空。

Java对引用的分类有Strong reference、SoftReference、WeakReference、PhatomReference四种。

在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术。

软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。

Thread

看个范例:

public class SampleActivity extends Activity {
    void spawnThread() {
        new Thread() {
            @Override public void run() {
                while(true);
            }
        }.start();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View tButton = findViewById(R.id.t_button);
        tButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                spawnThread();
            }
        });
    }
}

其实这边发生的内存泄漏原因跟AsyncTask是一样的。

正确的做法为:

我们自定义Thread并声明成static这样可以吗?其实这样的做法并不推荐,因为Thread位于GC根部,DVM会和所有的活动线程保持hard references关系,所以运行中的Thread绝不会被GC无端回收了,所以正确的解决办法是在自定义静态内部类的基础上给线程加上取消机制,因此我们可以在Activity的onDestroy方法中将thread关闭掉。

Timer Tasks

看个范例:

public class SampleActivity extends Activity {
    void scheduleTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                while(true);
            }
        },1000);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View ttButton = findViewById(R.id.tt_button);
        ttButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            scheduleTimer();
            }
        });
    }
}

这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而导致Timer和TimerTask一直引用外部类Activity。

正确的做法为:

在适当的时机进行Cancel。

Sensor Manager

看个范例:

public class SampleActivity extends Activity {
    void registerListener() {
           SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
           Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
           sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View smButton = findViewById(R.id.sm_button);
        smButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                registerListener();
            }
        });
    }
}

通过Context调用getSystemService获取系统服务,这些服务运行在他们自己的进程执行一系列后台工作或者提供和硬件交互的接口,如果Context对象需要在一个Service内部事件发生时随时收到通知,则需要把自己作为一个监听器注册进去,这样服务就会持有一个Activity,如果开发者忘记了在Activity被销毁前注销这个监听器,这样就导致内存泄漏。

正确的做法为:

在onDestroy方法里注销监听器。

尽量避免使用 static 成员变量

如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。

这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。

这里修复的方法是:

不要在类初始时初始化静态成员。可以考虑lazy初始化(使用时初始化)。架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。

避免 override finalize()

1、finalize 方法被执行的时间不确定,不能依赖与它来释放紧缺的资源。时间不确定的原因是: 虚拟机调用GC的时间不确定 Finalize daemon线程被调度到的时间不确定

2、finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,原因是:

含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即使在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候由于没有了 finalize reference 与之对应,所以 finalize 方法不会再执行。

3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。

集合对象及时清除

我们通常会把一些对象的引用加入到集合容器(比如ArrayList)中,当我们不再需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

所以在退出程序之前,将集合里面的东西clear,然后置为null,再退出程序,如下:

private List<String> nameList;
private List<Fragment> list;

@Override
public void onDestroy() {
    super.onDestroy();
    if (nameList != null){
        nameList.clear();
        nameList = null;
    }
    if (list != null){
        list.clear();
        list = null;
    }
}

webView

当我们不再需要使用webView的时候,应该调用它的destory()方法来销毁它,并释放其占用的内存,否则其占用的内存长期也不能回收,从而造成内存泄漏。

正确的做法为:

为webView开启另外一个进程,通过AIDL与主线程进行通信,webView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

为webView开启另外一个进程,通过AIDL与主线程进行通信,webView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。

资源未关闭

对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

拓展 -- 相关知识点

static 关键字

使用static声明属性

如果在程序中使用static声明属性,则此属性称为全局属性(也称静态属性),那么声明成全局属性有什么用?我们看下代码:

class Person {
    String name;
    int age;
    static String country = "A城";
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void info() {
        System.out.println("姓名:" + this.name + ",年龄:" + this.age + ",城市:" + country);
    }
};

public class Demo {
    public static void main(String agrs[]) {
        Person p1 = new Person("张三", 30);
        Person p1 = new Person("李四", 31);
        Person p1 = new Person("王五", 32);
        Person.country = "B城";
        p1.info();
        p2.info();
        p3.info();
    }
}

以上程序很清晰的说明了static声明属性的好处,需要注意一点的是,类的公共属性应该由类进行修改是最合适的(当然也可以p1.country = ...),有时也就把使用static声明的属性称为类属性。

使用static声明方法

直接看下代码就清楚了:

class Person {
    private String name;
    private int age;
    private static String country = "A城";
    public static void setCountry(String C) {
        country = c;
    }
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void info() {
        System.out.println("姓名:" + this.name + ",年龄:" + this.age + ",城市:" + country);
    }
    public static String getCountry() {
        return country;
    }
};

public class Demo {
    public static void main(String agrs[]) {
        Person p1 = new Person("张三", 30);
        Person p1 = new Person("李四", 31);
        Person p1 = new Person("王五", 32);
        Person.setCountry("B城");
        p1.info();
        p2.info();
        p3.info();
    }
}

【特殊说明】

       非static声明的方法可以调用static声明的属性或方法        static声明的方法不能调用非static声明的属性或方法

比如以下代码就会出错:

class Person {
    private static String country = "A城";
    private String name = "Hello";
    public static void sFun(String C) {
        System.out.println("name = " + name);       // 错误,不能调用非static属性
        fun();                                      // 错误,不能调用非static方法
    }
    public void fun() {
        System.out.println("World!!!");
    }
};

内部类

基本定义

我们都知道,在类内部可以定义成员变量与方法,同样,在类内部也可以定义另一个类。如果在类Outer的内部定义一个类Inner,此时类Inner就称为内部类,而类Outer则称为外部类。

内部类可声明成 public 或 private。当内部类声明成 public 或 private时,对其访问的限制与成员变量和成员方法完全相同。

内部类的定义格式

标识符 class 外部类的名称 {
    // 外部类的成员
    标识符 class 内部类的名称 {
        // 内部类的成员
    }
}

内部类的好处

可以方便地访问外部类中的私有属性!

静态内部类

使用static可以声明属性或方法,而使用static也可以声明内部类,用static声明的内部类就变成了外部类,但是用static声明的内部类不能访问非static的外部类属性。

比如如下例子:

class Outer {
    private static String info = "Hello World!!!";    // 如果此时info不是static属性,则程序运行报错
    static class Inner {
        public void print() {
            System.out.println(info);
        }
    };
};

public class InnerClassDemo {
    public static void main(String args[]) {
        new Outer.Inner().print();
    }
}

执行结果:

Hello World!!!

在外部访问内部类

一个内部类除了可以通过外部类访问,也可以直接在其他类中进行调用。

【在外部访问内部类的格式】

外部类.内部类 内部类对象 = 外部类实例.new 内部类();
class Outer {
    private String info = "Hello World!!!";  
    class Inner {
        public void print() {
            System.out.println(info);
        }
    };
};

public class InnerClassDemo {
    public static void main(String args[]) {
        Outer out = new Out();              // 实例化外部类对象
        Outer.Inner in = out.new Inner();   // 实例化内部类对象
        in.print();                         // 调用内部类方法
    }
}

在方法中定义内部类

除了在外部类中定义内部类,我们也可以在方法中定义内部类。但是需要注意的是,在方法中定义的内部类不能直接访问方法中的参数,如果方法中的参数想要被内部类访问,则参数前必须加上final关键字。

class Outer {
    private String info = "Hello World!!!";
    public void fun(final int temp) {      // 参数要被访问必须用final声明
        class Inner {
            public void print() {
                System.out.println("类中的属性:" + info);
                System.out.println("方法中的参数:" + temp);
            }
        };
        new Inner().print();
    }
};

public class InnerClassDemo {
    public static void main(String args[]) {
        new Outer().fun(30);               // 调用外部类方法              
    }
}

总结

在开发中,内存泄漏最坏的情况是app耗尽内存导致崩溃,但是往往真实情况不是这样的,相反它只会耗尽大量内存但不至于闪退,可分配的内存少了,GC便会更多的工作释放内存,GC是非常耗时的操作,因此会使得页面卡顿。我们在开发中一定要注意当在Activity里实例化一个对象时看看是否有潜在的内存泄漏,一定要经常对内存泄漏进行检测。

最后给大家分享一份非常系统和全面的Android进阶技术大纲已经进阶资料

想学习更多Android知识,或者获取相关资料请加入Android技术开发交流 878873098  进 群 即可找群管理免费领取

主要是针对做移动开发一到五年,想系统深入提升或者是困于瓶颈的小伙伴。

Android高级技术大纲,以及系统进阶视频;

Android高级技术大纲

Android 进阶视频资料

关于objective-c – Objective C / iOS:使用ARC释放内存(内存泄漏)arc下如何释放内存的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Andoid - 内存泄漏、Android 内存泄漏、Android 笔记 - Activity 相关 + 内存泄漏 + Fragment+service、Android 系统开发_内存泄漏篇 -- "内存泄漏"的前世今生等相关知识的信息别忘了在本站进行查找喔。

本文标签: