内存优化

内存简介

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系统会自动跟踪所有的

Paste_Image.png

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中的对象要长期保存用强引用,只是临时使用可以用软引用如:下载图片到本地。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容