内存简介
RAM(random access memory)随机存取存储器。说白了就是内存。
一般Java在内存分配时会涉及到以下区域:
- 寄存器(Registers):速度最快的存储场所,因为寄存器位于处理器内部,我们在程序中无法控制
- 栈(Stack):存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中
- 堆(Heap):堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器(GC)来管理。
- 静态域(static field): 静态存储区域就是指在固定的位置存放应用程序运行时一直存在的数据,Java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量
- 常量池(constant pool):虚拟机必须为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(string,integer和floating point常量)和对其他类型,字段和方法的符号引用。
- 非RAM存储:硬盘等永久存储空间
堆栈特点对比:
由于篇幅原因,下面只简单的介绍一下堆栈的一些特性。
栈:当定义一个变量时,Java就在栈中为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
堆:当堆中的new产生数组和对象超出其作用域后,它们不会被释放,只有在没有引用变量指向它们的时候才变成垃圾,不能再被使用。即使这样,所占内存也不会立即释放,而是等待被垃圾回收器收走。这也是Java比较占内存的原因。栈:存取速度比堆要快,仅次于寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆:堆是一个运行时数据区,可以动态地分配内存大小,因此存取速度较慢。也正因为这个特点,堆的生存期不必事先告诉编译器,而且Java的垃圾收集器会自动收走这些不再使用的数据。栈:栈中的数据可以共享, 它是由编译器完成的,有利于节省空间。
例如:需要定义两个变量int a = 3;int b = 3;
编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再让a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并让a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
堆:例如上面栈中a的修改并不会影响到b, 而在堆中一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
Android中的垃圾回收机制
Android平台最吸引开发者的一个特性:有垃圾回收机制,无需手动管理内存,Android系统会自动跟踪所有的
Young Generation
- 大多数新建的对象都位于Eden区
- 当Eden区被对象填满时,就会执行Minor GC.并把所有存活下来的对象转移到其中一个survivor区
- Survivor Space: S0、S1有两个,存放每次垃圾回收所存活的对象
- Minor GC同样会检查survivor区中存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。
Old Generation
- 存放长期存活的对象和经过多次Minor GC后依然存活下来的对象
- 满了进行Major GC
Parmanent Generation:
- 存放方法区,方法区中有要加载的类信息、静态变量、final类型的常量、属性和方法信息
垃圾回收机制&FPS
- Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,那么整个过程如果保证在16ms以内就能达到一个流畅的画面即为60FPS。
- 如果某一帧的操作超过了16ms就会让用户感觉到卡顿。
- UI渲染过程发生GC,导致某一帧绘制时间超过16ms.
内存泄漏
内存泄漏是指某一段内存在程序里功能上已经不需要了,但是垃圾回收机制回收内存时检测那段内存还是被需要的,不能被回收,这种在程序中在没有使用的但是又不能被回收的内存就是被泄漏的内存,那为什么会这样呢?
正常的话应该是程序里不需要的内存就可以被回收,这是垃圾回收机制做的事呀,如果垃圾回收机制正常运行的情况下,不应该这样啊,但是实际就是垃圾回收机制正常的情况下发生的内存泄漏。其实到这里java程序员就得知道垃圾回收机制中,判断一段内存是否是垃圾,是否可回收的条件,这个条件是通过检查这段内存是否存在引用和被引用关系,不存在这关系时,就认为可回收,若还存在引用或被引用关系,就认为不可回收,现在就可以知道导致内存泄漏的原因是程序员没有将不用的内存去掉引用关系(因为程序中大多内存石油对象指向的,所以去掉引用关系就是置空)。内存泄漏会导致一些内存没法被正常利用,话句话就是可以使用内存变少了,这样轻则增加垃圾回收机制运行频率,重则内存溢出(当系统需要分配一段内存,但是现有内存在垃圾回收运行后任然不足时,就会内存溢出);为避免内存泄漏,在写程序时已经确定不需要的引用型变量,就置空;虽然即使内存没泄露,也有可能出现内存溢出,这时的内存溢出就是有别的问题导致的。
- 应用程序分配了大量不能被回收的对象
- 系统可分配内存越来越少
- 新对象的创建需要的内存不够
- GC之后再分配
- 60fps
举例
- 非静态内部类的静态实例造成的泄露
public class MainActivity extends Acitivity{
private static TestResource sresource=null;
@Override
protected void onCreate(Bundle saveInstanceState){
super.onCreate(saveInstanceState);
setContentView(R.layout.activity_main);
if (sresource==null){
sresource=new TestResource();
}
}
class TestResource{
//...
}
上面的代码中的sresource实例类型为静态实例,在第一个MainActivity act1实例创建时,sresource会获得并一直持有act1的引用。当MainAcitivity销毁后重建,因为sresource持有act1的引用,所以act1是无法被GC回收的,进程中会存在2个MainActivity实例(act1和重建后的MainActivity实例),这个act1对象就是一个无用的但一直占用内存的对象,即无法回收的垃圾对象。所以,对于启动模式不是单一模式的Activity, 应该避免在activity里面实例化其非静态内部类的静态实例。
调用Context
-
Handler的引用(非静态内部类是持有外部类类引用)
public class SampleActivity extends Activity {private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 发送一个10分钟后执行的一个消息 mHandler.postDelayed(new Runnable() { @Override public void run() { } }, 600000); // 结束当前的Activity finish(); } }
解决方法
public class SampleActivity extends Activity {
private final WeakReference<SampleActivity> mActivity;
/**
* 使用静态的内部类,不会持有当前对象的引用
*/
private static class MyHandler extends Handler {
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);
// 发送一个10分钟后执行的一个消息
mHandler.postDelayed(sRunnable, 600000);
// 结束
finish();
}
}
线程引发的内存泄漏---变成静态
集合对象没有清理
将集合设为null
资源对象没有关闭(在onDestory中关闭)
避免内存泄露的方法:
- 尽量不要让静态变量引用Activity
- 使用WeakReference
- 使用静态内部类代替内部类
- 静态内部类使用弱引用来引用外部类
- 在声名周期结束的时候释放资源
内存抖动
指在短时间内有大量的对象被创建或者被回收的现象,内存抖动出现原因主要是频繁(很重要)在循环里创建对象(导致大量对象在短时间内被创建,由于新对象是要占用内存空间的而且是频繁,如果一次或者两次在循环里创建对象对内存影响不大,不会造成严重内存抖动这样可以接受也不可避免,频繁的话就很内存抖动很严重),内存抖动的影响是如果抖动很频繁,会导致垃圾回收机制频繁运行(短时间内产生大量对象,需要大量内存,而且还是频繁抖动,就可能会需要回收内存以用于产生对象,垃圾回收机制就自然会频繁运行了)。综上就是频繁内存抖动会导致垃圾回收频繁运行。
内存检测工具
Memory Monitor
方便显示内存使用和GC情况
快速定位卡顿是否和GC有关
快速定位Crash是否和内存占用过高有关
快速定位潜在的内存泄漏问题
简单易用
不能准确定位问题Allocation Tracker
定位代码中分配的对象的类型,大小、时间、线程、堆栈等信息
定位内存抖动问题
配合Heap Viewer一起定位内存泄漏问题Heap Viewer
内存快照信息
每次GC之后收集
减少内存使用
- 使用更轻量的数据结构(比如SpareArray代替HashMap)
- 避免在onDraw方法中创建对象
- 对象池(Message.obtin())
- LRUCache
- Bitmap内存复用,压缩(inSampleSize,inBitmap)
- StringBuilder(字符串的拼接)
视图优化
1.降低View层级
- LinearLayout VS RelativeLayout
- merge
- 不必要的背景
2.去掉window默认的背景(getWindow().setBackgroundDrawable(null))
去掉不必要的背景(每次添加背景都会再绘制一次)
ClipRect&QuickReject(尤其在自定义控件时使用)
ViewStub(控件某些条件才展示)
.9图用作背景(例如ImageView)
电量消耗
25~30%消耗用在核心功能上
- 画图
- 布局
- 动画
剩下的75%左右
- 上传统计数据
- 检查位置信息
- 轮训服务器,拉取广告信息
网络
- Android网络模块一段时间一直在运行
WakeLock
- 阻止系统进入睡眠状态(谨慎使用、释放很难)
AlarmManager里AlarmManager.setInexact()不会严格按照时间会把相邻的时间放到一起进行
JobScheduler
制定几乎任意一个场景去唤醒
非即时的任务
Battery Stats
Battery Historian
更详细的数据
网络优化
- 何时请求(是否是即时请求)
- 如何请求(一次发送所有相关的请求)
解决办法 利用WIFI
预取数据
避免轮询服务器(使用googole的GSM或国内第三方的推送服务)
数据压缩(从减少网络请求消耗的时间,但会增加一些解析数据的时间,不过是可以接受的)
一些补充
WeakReference与SoftReference
通常用于Cache.如果Cache中的对象要长期保存用强引用,只是临时使用可以用软引用如:下载图片到本地。