在虚拟机中,我们常说的类的加载过程是指加载(Loading)
、验证(Verification)
、准备(Preparation)
、解析(Resolution)
、初始化(Initialization)
这五个阶段,它们的具体作用为:
- 加载
加载过程是将二进制字节流(Class字节码文件)通过类加载器
加载到内存并实例化Class对象的过程(加载到方法区内
)。这个过程独立于虚拟机之外,并且二进制流可以从不同的环境内获取或者由其他文件生成。
- 验证
验证Class文件的字节流是否符合虚拟机的要求,以免造成虚拟机出现异常。包括:文件格式验证
、元数据验证
、字节码验证
、符号引用验证
。
- 准备
为静态变量(被final关键字修饰
)分配内存空间、赋值和设置类变量初始化(自动初始化)。
- 解析
将常量池内的符号引用
替换为直接引用
的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄以及调用点限定符7类符号引用进行。
- 初始化
执行类构造器<clinit>
方法的过程,变量的声明初始化就在这个阶段进行。
虚拟机类加载的时机?1)遇到new、getstatic、putstatic或者invokestatic 这四条字节码指令的时候,且该类没有进行初始化则进行该类的初始化;
2)使用反射机制的时候;
3)初始化类的父类;
4)初始化虚拟机要执行主类;
5)使用动态语言特性的时候;总之,当对一个类进行主动引用的时候就会进行初始化操作,而进行被动引用的时候便不会触发类初始化操作,比如通过子类引用父类静态字段时子类不会被初始化。
常见内存泄漏与优化
2.1 内存泄漏
当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被垃圾收集器回收,结果它们就一直存在于内存中(通常指Java堆内存),占用有效空间,永远无法被删除。随着内存不断泄漏,堆中的可用空间就不断变小,这意味着为了执行常用的程序,垃圾清理需要启动的次数越来越多,非常严重的话会直接造成应用程序报OOM异常。优化/避免内存泄漏原则:
涉及到使用Context时,尽量使用Application的Context;
对于非静态内部类、匿名内部类,需将其独立出来或者改为静态类;
在静态内部类中持有外部类(非静态)的对象引用,使用弱引用来处理;
不再使用的资源(对象),需显示释放资源(对象置为null),如果是集合需要清空;
保持对对象生命周期的敏感,尤其注意单例、静态对象、全局性集合等的生命周期;
2.2 常见内存泄漏与优化
(1) 单例造成的内存泄漏
- 案例
/** 工具类,单例模式
- @Auther: Jiangdg
- @Date: 2019/10/8 17:23
- @Description:
*/
public class CommonUtils {
private static CommonUtils instance;
private Context mCtx;
private CommonUtils(Context context){
this.mCtx = context;
}
public static CommonUtils getInstance(Context context) {
if(instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}
/**使用单例模式时造成内存泄漏
*
- @Auther: Jiangdg
- @Date: 2019/10/8 17:24
- @Description:
*/
public class SingleActivity extends AppCompatActivity {
private CommonUtils mUtils;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUtils = CommonUtils.getInstance(this);
}
}
- 分析与优化
在上述示例中,当SingleActivity实例化Commontils对象完毕后,Commontils将持有SingleActivity对象的引用,而由于单例模式的静态特性,Commontils对象的生命周期将于应用进程的一致,这就会导致在应用未退出的情况下,如果SingleActivity对象已经不再需要了,而Commontils对象该持有该对象的引用就会使得GC无法对其进行正常回收,从而导致了内存泄漏。优化:对于需要传入Context参数的情况,尽量使用Application的Context,因为它会伴随着应用进程的存在而存在。
public class SingleActivity extends AppCompatActivity {
private CommonUtils mUtils;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 造成内存泄漏
//mUtils = CommonUtils.getInstance(this);
mUtils = CommonUtils.getInstance(this.getApplicationContext());
}
}
(2) Handler造成的内存泄漏
- 案例
/** 使用Handler造成内存泄漏
- @Auther: Jiangdg
- @Date: 2019/10/8 17:55
- @Description:
*/
public class HandlerActivity extends AppCompatActivity {
// 匿名内部类
private Handler mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Thread(new Runnable() {
@Override
public void run() {
// 处理耗时任务
// …
mUIHandler.sendEmptyMessage(0x00);
}
});
}
}
- 分析与优化
在Android应用启动时,应用程序的主线程会为其自动创建一个Looper
对象和与之关联的MessageQueue
,当主线程实例化一个Handler
对象后,它就自动与主线程的MessageQueue
关联起来,所有发送到MessageQueue
的Message
(消息)都会持有Handler的引用。由于主线程的Looper对象会随着应用进程一直存在的且Java类中的非静态内部类和匿名内部类默认持有外部类的引用
,假如HandlerActivity
提前出栈不使用了,但MessageQueue
中仍然还有未处理的Message
,Looper
就会不断地从MessageQueue
取出消息交给Handler
来处理,就会导致Handler
对象一直持有HandlerActivity
对象的引用,从而出现HandlerActivity
对象无法被GC正常回收,进而造成内存泄漏。优化:将Handler类独立出来,或者使用静态内部类,因为静态内部类不持有外部类的引用。
public class HandlerActivity extends AppCompatActivity {
// 匿名内部类默认持有HandlerActivity的引用
// 造成内存泄漏
// private Handler mUIHandler = new Handler() {
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
// }
// };
// 优化,使用静态内部类
// 假如要持有HandlerActivity,以便在UIHandler中访问其成员变量或成员方法
// 需要使用弱引用处理
private UIHandler mUIHandler;
static class UIHandler extends Handler {
private WeakReference mWfActivity;
public UIHandler(HandlerActivity activity) {
mWfActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUIHandler = new UIHandler(this);
}
}
Java中四种引用关系:
- 强引用用来描述永远不会被垃圾收集器回收掉的对象,类似"Object obj = new Object"
- 软引用用来描述一些还有用但并非必须的对象,由
SoftReference
类实现。被软引用关联着的对象会在系统将要发生OOM之前,垃圾收集器才会回收掉这些对象。- 弱引用用来描述非必须的对象,比软引用更弱一些,由
WeakReference
类实现。被弱引用的对象只能生产到下一次垃圾收集发生之前,无论当前内存是否足够。- 虚引用最弱的引种引用关系,由
PhantomReference
类实现。一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用来获取该对象实例。为一个对象设置虚引用关联的唯一目的是能在这个对象被垃圾收集器回收时收到一个系统通知
。
(3) 线程(非静态内部类或匿名内部类)造成的内存泄漏
- 案例
/** 使用线程造成的内存泄漏
- @Auther: Jiangdg
- @Date: 2019/10/9 10:04
- @Description:
*/
public class ThreadActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 开启一个子线程
new Thread(new MyRunnable()).start();
// 开启一个异步任务
new MyAsyncTask(this).execute();
}
class MyRunnable implements Runnable {
@Override
public void run() {
}
}
class MyAsyncTask extends AsyncTask {
private Context mCtx;
public MyAsyncTask(Context context) {
this.mCtx = context;
}
@Override
protected Object doInBackground(Object[] objects) {
return null;
}
}
}
- 分析与优化
在之前的分析中可知,Java类中的非静态内部类和匿名内部类默认持有外部类的引用。对于上述示例中的MyRunnable
和MyAsyncTask
来说,它们是一个非静态内部类
,将默认持有ThreadActivity
对象的引用。假如子线程的任务在ThreadActivity
销毁之前还未完成,就会导致ThreadActivity
无法被GC正常回收,造成内存泄漏。优化:将MyRunnable和MyAsyncTask独立出来,或使用静态内部类,因为静态内部类不持有外部类的引用。
public class ThreadActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 开启一个子线程
new Thread(new MyRunnable()).start();
// 开启一个异步任务
// 优化:使用Application的Context
new MyAsyncTask(this.getApplicationContext()).execute();
}
// 优化:使用静态内部类
static class MyRunnable implements Runnable {
@Override
public void run() {
}
}
// 优化:使用静态内部类
// 如果需要传入Context,使用Application的Context
static class MyAsyncTask extends AsyncTask {
private Context mCtx;
public MyAsyncTask(Context context) {
this.mCtx = context;
}
@Override
protected Object doInBackground(Object[] objects) {
return null;
}
}
}
(4) 静态实例造成的内存泄漏
- 案例
/**非静态内部类创建静态实例造成的内存泄漏
- @Auther: Jiangdg
- @Date: 2019/10/9 10:43
- @Description:
*/
public class StaticInstanceActivity extends AppCompatActivity {
private static SomeResources mSomeResources;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(mSomeResources == null) {
mSomeResources = new SomeResources(this);
}
}
class SomeResources {
private Context mCtx;
public SomeResources(Context context) {
this.mCtx = context;
}
}
}
- 分析与优化
在上述案例中,演示了防止StaticInstanceActivity
重建,比如横竖屏切换,导致反复创建SomeResources
实例的问题,这里使用了static
修饰关键字将SomeResources
实例声明了静态实例,以确保该实例始终存在的是同一个,且它的生命周期与应用相同。然而,由于SomeResources
是一个非静态内部类
,其对象默认持有外部类StaticInstanceActivity
的引用,就会导SomeResources
的对象一直持有该引用,造成内存泄漏。优化:使用单例模式实现SomeResources,或者将其改成静态内部类。如果需要传入Context参数,必须使用Application的Context。
public class StaticInstanceActivity extends AppCompatActivity {
private static SomeResources mSomeResources;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(mSomeResources == null) {
// 优化,使用Application的Context
mSomeResources = new SomeResources(this.getApplicationContext());
}
}
// 优化:使用静态内部类
static class SomeResources {
private Context mCtx;
public SomeResources(Context context) {
this.mCtx = context;
}
}
}
(5) 资源未关闭或监听器未移除(注销)引起的内存泄露情况在开发中,如果使用了BraodcastReceiver
,ContentObserver
,File
,Cursor
,Stream
,Bitmap
、自定义属性attributeattr
、传感器等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。比如:
// 使用传感器等资源,需要注销
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
sensorManager.unregisterListener(listener);
// 使用BraodcastReceiver,需要注销
Myreceiver recevier = new Myreceiver();
intentFilter = new IntentFilter();
intentFilter.addAction(“android.net.conn.CONNECTIVITY_CHANGE”);
registerReceiver(recevier,intentFilter);
unRegisterReceiver(recevier);
// 自定义属性,需要recycle
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AttrDeclareView);
int color = a.getColor(R.styleable.AttrDeclareView_background_color, 0);
a.recycle();
除了上述常见的5种内存泄漏外,还有包括无限循环动画
、使用ListView
、使用集合容器
以及使用WebView
也会造成内存泄漏,其中,无限循环动画造成泄漏的原因是没有再Activity的onDestory中停止动画;使用ListView造成泄漏的原因是构造Adapter时没有使用缓存的convertView;使用集合容器造成泄漏的原因是在不使用相关对象时,没有清理掉集合中存储的对象引用。在优化时,在退出程序之前将集合中的元素(引用)全部清理掉,再置为null;使用WebView造成泄漏的原因是在不使用WebView时没有调用其destory方法来销毁它,导致其长期占用内存且不能被回收。在优化时,可以为WebView开启另外一个进程,通过AIDL与主线程进行通信,便于WebVIew所在的进程可以根据业务需要选择合适的时机进行销毁。
总结
写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!
要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。**
[外链图片转存中…(img-50PIiG5p-1714700473360)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!