在虚拟机中,我们常说的类的加载过程是指加载(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所在的进程可以根据业务需要选择合适的时机进行销毁。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
答应大伙的备战金三银四,大厂面试真题来啦!
这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《960全网最全Android开发笔记》
《379页Android开发面试宝典》
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数
《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析
资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
[外链图片转存中…(img-Y0pMetPH-1713777378571)]
腾讯、字节跳动、阿里、百度等BAT大厂 2020-2021面试真题解析
[外链图片转存中…(img-TpU3wzxd-1713777378572)]
资料收集不易,如果大家喜欢这篇文章,或者对你有帮助不妨多多点赞转发关注哦。文章会持续更新的。绝对干货!!!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!