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

使用Simulink对基于GaN器件的高频电源进行建模与仿真,重点在于优化其效率 本文介绍了基于Simulink的GaN器件高频电源仿真建模方法。通过7个步骤详细讲解系统构建过程:从创建模型、设置电源、配置GaN开关网络,到添加磁性元件、负载和控制器设计,最后进行仿真分析。重点探讨了效率优化策略,包括开关频率选择、磁性元件优化、散热设计改进及控制策略调整。该教程为GaN器件在电力电子系统中的应用提供了完整的仿真流程指导,有助于设计人员验证方案并优化性能。 阅读详情

基础了解

什么是内存泄漏?

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

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四种。

级别回收机制用途生存时间
强从来不会对象的一般状态JVM停止运行时终止
软在内存不足时联合ReferenceQueue构造有效期短/占内存打/生命周期长的对象的二级高速缓冲器(内存不足时才情况)内存不足时终止
弱在垃圾回收时联合ReferenceQueue构造有效期短/占内存打/生命周期长的对象的一级高速缓冲器(系统发生gc时清空)gc运行后终止
虚在垃圾回收时联合ReferenceQueue来跟踪对象被垃圾回收期回收的活动gc运行后终止

在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 进阶资料

【学习系列】SAP RAP 2:创建第一个RAP应用 本文介绍了从零开始创建基于SAP RAP(ABAP RESTful Application Programming Model)的Fiori应用的完整流程。首先说明了学习RAP需要结合官方文档和教程,并提供了相关资源链接。文章详细讲解了开发环境的准备要求,包括系统版本、开发工具和基本知识。重点演示了如何通过8个步骤创建支持CRUD操作和草稿功能的RAP应用:1)创建数据库表;2)定义CDS视图实体;3)创建投影视图;4)定义行为;5)创建行为投影;6)定义服务;7)绑定服务;8)添加UI注解。 阅读详情

相关推荐

H3C VRF实验

POP-vpn-instance-RT1]route-distinguisher 11:11 # VPN实例-RT1的路由区分器值11:11。配置路由泄露,其实就是在一个VPN示例中添加另一个VPN实例的下一跳,比如在VPN实例RT1中添加去往SRV的默认路由。但是也有了一个新的问题,那就是RT1、RT2和RT3也能互访了,这让VPN实例变得没有意义了。[POP]ip vpn-instance RT1 #配置vpn实例-RT1。绑定了VPN实例,所以要把路由添加到VPN实例POP中。

m0_68698993的博客 4023

Android WebView 加载闪退问题处理(Error inflating class android.webkit.WebView)

前言 在布局文件中直接使用 WebView ,Activity 运行时,APP崩溃 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_paren...

一个IT人的技术成长历程。 7027

Python遥感技术实战:NDWI与MNDWI水体提取算法对比与优化

本文详细对比了NDWI与MNDWI两种水体提取算法在Python遥感技术中的应用。通过实战代码演示,分析了二者在自然与城镇场景下的表现差异,并重点介绍了阈值优化与后处理技巧,旨在帮助读者掌握高效、精准的水体提取方法,提升遥感数据分析能力。

f6g7h8i9j的博客 498

android webview关闭后资源不释放,【Android】 WebView内存泄漏优化之路

这几年H5的快速发展,使得Hybrid混合开发越来越流行,而webview也成为了开发中必备的元素。但是我们知道WebView在加载页面时,会占用非常大的内存,无论是iOS还是Android系统上,加载一个Web页面一般需要用到100M左右,而如果我们不及时清理WebView的内存,那最后可能会随着内存消耗的不断增加而发生OOM(Out Of Memory)导致程序崩溃。那今天我就总结一下前一阵子...

weixin_35066076的博客 1631

腾讯云智后台开发实习面试全纪录(已offer)

腾讯云智后台开发实习面经

wenwenaier的博客 5903

sentaurus学习笔记(HFET_pGate_GaN仿真)(五)

`(sdedr:define-constant-profile`和`(sdedr:define-constant-profile-region`命令用于定义并应用一个恒定的活性硼掺杂浓度剖面`"ndop_GaN_gate_const"`到氮化镓栅极层上,其值为`NpGaN_gate`。- `"Oxide"`指定了这一层的材料名称(在这里代表氧化层),而`"nucleation"`是这一层在仿真中的标识名称。- `"GaN"`指定了这一层的材料名称,而`"GaN_gate"`是这一层在仿真中的标识名称。

qq_52640792的博客 940

系统级应用,重签名后WebView闪退问题

因为应用是系统级别的,在AndroidManifest.xml中添加了android:sharedUserId="android.uid.system" 然后在调用WebView时出现闪退: java.lang.UnsupportedOperationException For security reasons, WebView is not allowed in privileged pr...

雪飘碧鸳的博客 1094

《Autosar从入门到精通-实战篇》总目录_培训教程持续更新中...

欢迎大家订阅《Autosar从入门到精通-实战篇》,下方整理了相关博文的目录(单击蓝色字体即可跳转),方便大家获取。 本专栏旨在:扫除Autosar工具链配置过程中的障碍,加深对Autosar理论的理解。 温馨提醒: 如有疑问,可在博文的“评论区”给小编留言,进行技术交流(评论区开启了验证功能,审核通过后,会显示); 如有疑问,也可以通过博文中的第一个图片联系小编,获取专栏/博文的实时状态; 由于工作限制,仅做个人技术交流。添加好友时,请备注您的“姓名-公司-职务...

「汽车电子助手」的博客 12万+

华为p30Pro手机webview使用过程中会出现闪退现象

在 X5 内核的 Webview 中,是找不到 android.webkit.WebSettings.MIXED_CONTENT_ALWAYS_ALLOW 这些参数的,只能手动把值设上去: public static final int MIXED_CONTENT_ALWAYS_ALLOW = 0; public static final int MIXED_CONTENT_COMPATIBILITY_MODE = 2; public static final int MIXED_CONTENT.

fengxu_peng的博客 1474

[QMT量化交易小白入门]-七、可转债双低策略Python代码详解与实战

本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步,自己淋过雨了,希望大家都有一把伞。

木头大左的博客 3840

使用 Simulink 进行电力电子系统的大信号模型仿真

电力电子系统(Power Electronics System)包括各种功率转换器(如 DC-DC 转换器、逆变器等)。这些设备中的关键组件(如 MOSFET、IGBT、电容器、电感器等)不仅影响系统的性能,还直接关系到系统的稳定性、效率和可靠性。因此,在设计阶段进行详细的建模和仿真至关重要。大信号模型仿真的目的是通过模拟系统在非线性条件下的真实行为,评估其瞬态响应、稳态性能以及对不同负载和输入电压变化的响应。通过本教程,我们介绍了如何使用 Simulink 进行电力电子系统的大信号模型仿真。背景介绍。

xiaoheshang_123的博客 303

安卓现代化开发系列——从生命周期到Lifecycle

我们从电脑系统的窗口去理解Activity启动一个程序的时候,程序就会在电脑桌面上创建一个窗口,创建的那一瞬间(通常会很快,可能不需要1秒)就相当于Activity的onCreate()。创建完成后,窗口就可以被用户所看见了,被用户看到的那一瞬间就相当于Activity的onStart()。通常来说,一个新启动的程序会自动获得焦点并可被用户交互,因此onStart()之后,窗口会被置顶到顶层,这一瞬间就相当于Activity的onResume()。

maniuT的博客 846

导航系统 | 全球四大卫星导航系统(2025)核心能力对比分析

北斗系统通过GEO卫星(80°E、110.5°E、140°E)实现区域增强,其IGSO卫星在亚太地区形成"8"字形轨迹,使该区域可见卫星数量比GPS多4-6颗,显著提升复杂环境下的定位可用性[13]。北斗短报文功能在2025年云南地震救援中发挥关键作用,被困人员通过北斗终端发送120字求救信息,实现"位置+状态"一体化上报,救援响应时间缩短40%[15]。北斗通过三频信号(B1C/B2a/B3)消除电离层延迟误差,在城市峡谷环境下定位连续性达99.98%,优于GPS的99.95%[5]。

尘世冰封的专栏 2611

关于Android WebView开发的前世今生,你都知道吗?

安卓原生APP中经常会内嵌个H5页面,这就需要用到WebView。很多人对WebView的使用不以为然, 觉得也就那么回事,但实际上关于WebView我们能聊得有很多… 首先我们聊聊他的出生,众所周知,因为爸爸都是谷歌公司,WebView和Chrome浏览器有着紧密的关系。 他们实际上都是谷歌Chromium Project下的顶级项目,链接:chromium project,他们的身上流着 相似的血液,不同的是: Chrome浏览器是多进程架构(内存换速度),WebView 是单进程架构。 Chr

mydream___anjie的博客 868

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Android 开发中如何避免内存泄漏:让您的应用稳如老狗

在 Android 开发过程中,内存泄漏可是个让人头疼的问题。它就像隐藏在代码中的 “定时炸弹”,随时可能让您的应用崩溃,让用户满意度直线下降。今天,就让我们一起探讨一下内存泄漏的那些事儿,让您从此告别内存泄漏的烦恼。

qixiyang9412的博客 1691

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

android原生开发教程,安卓开发入门到精通

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

m0_61369275的博客 3275

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Android应用篇 - 内存泄漏如何分析

今天来聊聊 Android 中的内存泄漏如何分析。 目录: 什么是内存泄漏 Android 中的内存泄漏场景 分析内存泄漏的几种方法 内存优化的一些点 1. 什么是内存泄漏 内存不在 gc掌控之内了,当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而就导致该对象不能被回收。这种本该被回收的对象不能被回收而停留在内存中的现象...

u014294681的博客 1464

Android性能优化之内存泄漏优化(工具篇)

当应用程序存在这些问题时,不再使用的对象无法被垃圾回收器回收,最终导致内存占用过高。

特立独行的博客 2346

如何快速的开发一个完整的直播app,Android篇

前言 程序员说不焦虑其实是假的,因为无论是现在还是最近几年,很早就有人察觉Android开发的野蛮生长时代已经过去。过去的优势是市场需要,这个技术少有人有,所以在抢占市场的时候,基本上满足需要就已经可以了。但是现在,各式各样的APP层出不穷,APP的质量成为新的竞争标准。这也意味着不管是系统设计还是性能优化等方面,市场都给Android开发者提出了更高的要求。那么,Android是不是真的不吃香了呢?其实不是的,高级及以上还有很大的需求缺口,非常稀缺人才。但是初级开发者真的已经不吃香了。丧气的说一句,初级学

CHAMPION8888的博客 733

安卓应用开发中内存泄漏问题详解及解决方案

本文深入探讨了Android应用开发中的内存泄漏问题及解决方案。分析了内存泄漏的典型现象(内存持续增长、频繁GC、OOM崩溃)和根本原因(长生命周期对象持有短生命周期对象引用)。重点剖析了四大常见泄漏场景:Context引用不当、Handler泄漏、匿名内部类问题和单例模式滥用,并给出了具体解决方案:使用Application Context替代Activity Context、静态内部类+弱引用处理Handler、及时移除消息回调等。文章还提供了最佳实践建议,帮助开发者有效预防和检测内存泄漏问题。

深山技术宅的博客 1195

Android内存泄漏

1 什么是内存泄漏(Memory Leak) leak [liːk] 渗入,漏出;漏,渗漏;泄露,透露 Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentation fault)导致崩溃,同时防止未释放的内存把堆栈(Heap)挤爆的可能,所以写出来的代码更为安全。 segmentation [ˌseɡmenˈteɪʃn] 分割;割断;细胞分裂 但是,在Java中仍存在很多容易导致内存泄漏的逻辑可能(logical leak)。如果不小心,Android应

xingyu19911016的博客 4525

避免内存泄漏,做一个有“洁癖”的Android开发者(基础篇)

对于众多 Android 程序员而言,在需求与应用性能之间,主要精力都会聚焦新需求的开发。随着项目复杂度的增加,应用性能越来越低,各类故障频发。程序员们奔波于各种“救火现场”,疲于奔命。本文将根据笔者在 Android 应用程序开发过程中所遇以及思考,针对内存泄漏提炼出一套可以应用于开发中的基础篇方法论,也许会让你的开发效率事半功倍。 先从管理好内存应用开始 提及Random-access memory(随机存取存储器RAM)大家并不陌生,在任何软件开发环境中它都是宝贵的资源,而对于物理内存经常受到..

Mobholly的博客 218

安卓Handler内存泄漏

链接: link Android常见内存泄漏汇总mumuxi_关注0.2642020.08.12 23:16:22字数 3,275阅读 240目录: 一、内存泄漏介绍 二、常见内存泄漏场景       1.单例导致内存泄露       2.静态变量导致内存泄漏       3.非静态内部类导致内存泄露   &n

ruiruiddd的博客 1345

android studio 调试闪退_避免内存泄漏,做一个有“洁癖”的Android开发者(基础篇)...

对于众多 Android 程序员而言,在需求与应用性能之间,主要精力都会聚焦新需求的开发。随着项目复杂度的增加,应用性能越来越低,各类故障频发。程序员们奔波于各种“救火现场”,疲于奔命。本文将根据笔者在 Android 应用程序开发过程中所遇以及思考,针对内存泄漏提炼出一套可以应用于开发中的基础篇方法论,也许会让你的开发效率事半功倍。先从管理好内存应用开始提及Random-access memor...

weixin_42251888的博客 514
上一篇: Android 密钥保护和 C/S 网络传输安全理论指南
下一篇: 都2019年了,还问GET和POST的区别
北辰丶
博客等级 码龄8年 67粉丝 197原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值