Android性能优化终结-内存篇

来源:互联网 发布:上市公司毛利率算法 编辑:程序博客网 时间:2024/06/05 18:43

  文笔不行,直接Copy笔记了,花了一周时间研究的


1.java垃圾回收机制gc

   (0)垃圾回收机制原理:
        java里所有的对象实例都是"new 出来的", 所有的对象实例都在堆内存中,变量里只是对象引用,局部变量放在栈里面方法结束后销毁
      局部变量销毁后,其指向的堆中对象实例如果不存在其他变量的引用就会在内存不足时被gc回收  
        全局变量存在堆中,GC在标记阶段会从根节点(GC ROOT)开始标记的可达对象,未被标记的(不可达的)对象就是未被gc root引用的垃圾对象,
      在清理阶段,清理掉所以未被标记的对象,回收掉该对象占用的内存。
        静态变量存在方法区中,该变量不会被回收,把静态变量置为null,该静态变量还是会存在,但是该变量不再指向堆中的对象实例
      堆中的对象实例在引用数为0的情况下会被gc回收  
        
        
   (1)垃圾回收的优缺点:
          优点:GC自动帮我们回收掉不再被引用的对象实例,以便空间被后来的新对象使用  
        
          缺点:它的开销影响程序性能。Java虚拟机必须追踪运行程序中有用的对象,而且最终释放没用的对象。这一个过程需要花费处理器的时间。
          
   (2)java GC机制:
      1)对象将根据存活的时间被分为:年轻代(Young Generation)、年老代(Old Generation)--(两个在堆里面)
                                   永久代(Permanent Generation,也就是方法区)
                                   年轻代又分为Eden区和两个存活区(Survivor0 、Survivor 1)
      
      2)GC机制的基本算法是:分代收集
        ①年轻代-(停止-复制算法):
          在新生代中,使用“停止-复制”算法进行清理,将新生代内存分为2部分,1部分Eden区(new的对象最新存放的区)较大,1部分Survivor比较小,
          并被划分为两个等量的部分。每次进行清理时,将Eden区和一个Survivor0中仍然存活的对象拷贝到 另一个Survivor1中,然后清理掉Eden和刚才
          的Survivor0。 这里也可以发现,停止复制算法中,用来复制的两部分并不总是相等的(传统的停止复制算法两部分内存相等,但新生代中使用
          1个大的Eden区和2个小的Survivor区来避免这个问题)
              由于大部分的对象在创建后很快就不再使用,因此很快变得不可达,于是被年轻代的GC机制清理掉,甚至存活不到Survivor中,所以,

          Eden区与Survivor的比例较大,HotSpot默认是 8:1,即分别占新生代的80%,10%,10%。如果一次回收中,Survivor+Eden中存活下来的内存超过了       10%,则需要将一部分对象分配到老年代。用-XX:SurvivorRatio参数来配置Eden区域Survivor区的容量比值,默认是8,代表Eden:Survivor1:Survivor2=8:1:1.

            
        ②年老代-(标记-清除算法)
            老年代存储的对象比年轻代多得多,而且不乏大对象,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),
          将所有存活的对象向一端移动,以保证内存的连续。大对象直接从新生代进入了老年代存放,大对象一般不使用复制算法,因为一是太大,
          复制效率低,二是过多的大对象,会使得小对象复制的时候无地方存放。还有被长期引用的对象也放在了老年代
             标记-整理算法分为两个阶段:标记阶段和回收阶段,在标记阶段,标记从根节点开始的可达对象,(从GC ROOT开始标记引用链——又叫可达性算法)    
          未被标记的(不可达的)对象就是未被root引用的垃圾对象,在清理阶段,清理掉所以未被标记的对像
          
            PS:GC root(GC的根节点)是什么?
                 每个应用程序都包含一组GC根(root),也叫GC roots,每个根都是一个存储位置,其中包含指向引用类型对象的一个指针。
                    
               Java中可以作为GC ROOT的对象有:
                a.静态变量引用的对象
                b.常量引用的对象(被生命为final的常量池)
                c.本地方法栈(JNI)引用的对象
                d.Java栈中引用的对象
                e.活着的Thread,比如主线程

   (3)System.gc()方法
        此函数建议JVM进行主GC,调用System.gc()也仅仅是一个请求(建议)。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾
     回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。
      
   (4)finalize()方法

         finalize()的主要用途是释放一些其他做法开辟的内存空间,以及做一些清理工作。因为在JAVA中并没有提够像“析构”函数或
     者类似概念的函数,要做一些类似清理工作的时候,必须自己动手创建一个执行清理工作的普通方法,也就是override Object这
     个类中的finalize()方法
         一旦垃圾回收器准备好释放对象占用的存储空间,首先会去调用finalize()方法进行一些必要的清理工作。只有到下一次
     进行垃圾回收动作的时候,才会真正释放这个对象所占用的内存空间。
         
    (5)触发gc的条件:
        JVM进行次GC的频率很高,但因为这种GC占用时间极短,所以对系统产生的影响不大。更值得关注的是主GC的触发条件,因为它对
      系统影响很明显。总的来说,有两个条件会触发主GC:  
        1)当应用程序空闲时,即没有应用线程在运行时,GC会被调用。因为GC在优先级最低的线程中进行,所以当应用忙时,GC线程就
      不会被调用,但以下条件除外。
       
         2)Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用
      GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满
      足要求,则JVM将报“out of memory”的错误,Java应用将停止。
    
          
    (6)减少gc开销的措施:
        ①不要显式调用System.gc():但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。
        ②尽量减少临时对象的使用:少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
        ③对象不用时最好显式置为Null:为Null的对象都会被作为垃圾处理
        ④尽量使用StringBuffer,而不用String来累加字符串
        ⑤能用基本类型如Int,Long,就不用Integer,Long对象
            基本类型变量占用的内存资源比相应对象占用的少得多
        ⑥尽量少用静态对象变量:
            静态变量属于全局变量,不会被GC回收,它们会一直占用内存,从而引发内存泄漏
        

2.java内存分配策略:
    java内存空间:(栈内存中的变量指向堆内存中的变量)
        方法区:主要存放局部static数据、全局static数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在。
        栈区:局部变量的基本数据类型和对象的引用存储于栈中,引用的对象实体存储于堆中。—— 因为它们属于方法中的变量,生命周期随方法而结束。
             
             栈内存中对象的引用被释放掉后,它指向的的数组或者对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,
             在随后的一个不确定的时间被垃圾回收器收走(释放掉)                                  
        堆区:存放由new创建的对象和数组,包括对象的全局变量(包括基本数据类型,引用和引用的对象实体),这部分内存在
             不使用时将会由Java垃圾回收器来负责回收。                        
 
3.引用:
  (0)几点疑问:
      类变量持有成员变量的引用
      成员变量置为空,变量与它引用的对象引用(或者对象)之间的引用关系就没有了,但是与引用它的对象引用之间的引用还存在
      
  (1)为什么内部类会持有外部类的引用?
      编译器编译完成后,在内部类Outer$Inner中,存在一个名字为this$0,类型为Outer的成员变量,并且这个变量是final的。
    另外编译器会为内部类的构造方法添加一个参数, 参数的类型就是外部类的类型.
    其实这个就是所谓的“在内部类对象中存在的指向外部类对象的引用”
   
  (2)强引用,弱引用,软引用,虚引用的回收
       强引用:比如People p=new People(); 变量p与实例对象之间的引用就是强引用(默认的引用方式)
       软引用(SoftReference):则内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存  
       弱引用(WeakReference):弱引用与软引用的区别在于,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程
               扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
       虚引用:拥有虚引用的对象可以在任何时候GC                          
                       
        
4.Activity销毁
   (1)finish():(返回键的作用等于调用了finish()方法)
      当你调用此方法的时候,系统只是将最上面的Activity移出了栈,并没有及时的调用onDestory()方法,其占用的
      资源也没有被及时释放。正常情况下,finish()方法会回调onPause(),onStop()和onDestory()方法
      
   (2)我们在A窗口打开B窗口时在Intent中直接加入标志Intent.FLAG_ACTIVITY_CLEAR_TOP,

      这样开启B时将会清除B上面的所有Activity。在A窗口中使用下面的代码调用B窗口

        

     Intent intent = new Intent();      intent.setClass(Activity1.this, Activity2.class);      intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); //类似于将Activity2的Launch mode设置成singleTask      startActivity(intent);


     接下来在B窗口中需要退出时直接使用finish方法即可全部退出。
        
   (3)onDestory():正常情况下,系统会销毁了这个Activity的实例,当该Actiivty对象引用没有指向原先分配给该Activity对象的内存时,
                  该内存便成为垃圾。JVM的一个系统级线程会自动释放该内存块。
                   
                  执行onDestory(),正常情况下activity这个对象确实是被回收了,但是对象绑定的view却没有被回收
                  我们还需要在onDestory方法中手动去回收他们。将对象置为null,另外写一个空的layout文件,
                  setContentView(R.layout.empty_layout); ------网上也有人说,Activity销毁时view也会被销毁,具体是什么我也没搞清楚
                    
   (4)执行onDestory方法,可能由于发生Activity内存泄漏,导致Actiivty实例不会被销毁回收
   

5.内存溢出(OOM)的特点:
 
   内存溢出(OOM):
      Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用
      GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满
      足要求,则 JVM将报“out of memory”的错误,Java应用将停止
    
   内存溢出有两个原因:
     (1)内存泄漏:本应该被回收的内存没有被回收掉
     (2)内存分配不合理:程序中所拥有的内存都是必须存活的,有效的,有用的;一般由图片引起的,我们应该对图片进行压缩处理
   另外在应用结束后调用bitmap.recycle()再把bitmap==null,另外释放掉ImageView的资源和背景资源   
                              

6.内存泄漏

     (1)内存泄漏检测:
        ①LeakCanary开源库:
            a.build.gradle文件中添加
              
     dependencies {                   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'                   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'                }


            b.在Application类添加:
                LeakCanary.install(this);
            c.Activity内存泄漏检测:
                LeakCanary.install() 返回一个先前配置的RefWatcher,它也安装一个ActivityRefWatcher以便在Activity.onDestroy()
              被调用后自动检测Activity是否出现泄露。
              

            d.Fragment中内存泄漏检测             

           

/**                * 得到RefWatcher对象,方便在Fragment的onDestory中调用对象.watch(this)来                * 检测内存泄漏                * @return                */               public static RefWatcher getRefWatcher(Context context){                  MyApplication application= (MyApplication) context.getApplicationContext();                  return application.watcher;              }


          ②Android Monitor:

             Android Monitor->Dump java Heap->点击Analyzer Tasks右边的绿色运行箭头
              
             
       (2)例如:MainActivity非静态内部类的静态实例造成的内存泄漏,并不是每次打开一个MainActivity都会造成内存泄漏
       某类对象在第一个MainActivity中被声明是static静态的,当第二个被打开的MainActivity并不会再重新初始化某对象了
       因此static 某类对象在内存中只是持有了第一个MainActivity的对象的引用
         但是在匿名内部类或者内部类的方法创建一个Thread造成的内存泄漏,每打开的每一个MainActivity都会造成内存泄漏
                      
                      
       (3)内存泄漏的常见原因与解决办法:
            ①什么是内存泄漏?
                 内存泄漏表示的是不再用到的对象因为被错误引用而无法进行回收
                 
            ①内存泄漏的对象的两个特点:
                       a.这些对象是可达的,即在有向图中存在通路与其相连,即还存在其他存活的类的引用
                       b.这些对象是无用的   
                       
            ②常见的内存泄漏:
            
               a.集合类泄漏:如果这个集合类是全局性的变量,那么没有相应的删除机制,很可能导致集合所占用的内存只增不减
                            解决办法:onDestory方法中清空集合,然后把集合对象置为null
      
               b.单例造成的内存泄漏:
                           如果传入Activity的Context(或者存在Activity引用的对象),且context被该单例类对象或者单例类对象的成员变量引用(因为
                        成员变量会被单例类对象引用而生命周期与单例类对象一致),这个当这个Context对应的Activity退出时,由于GC ROOT可达,导致
                        内存泄漏
                           
                        解决办法:
                               ①不传入Activity的Context(或者被Activity引用的实例对象)而改用Application的Context,
                               ②如果需要传入Activity的Context时改用弱引用的方式传入                                                
                               ③将引用Activity(的Context)的对象置为空
                           
               c.非静态内部类创建静态实例造成的内存泄漏:
                         非静态内部类默认会持有外部类的引用,而非静态内部类内部又创建了一个静态的实例,该实例的生命周期和应用一样长,这就导致了
                       该静态实例会一直持有Activity的引用
                       
        public class MainActivity extends AppCompatActivity {                         private static TestResource mResource = null;                           @Override                           protected void onCreate(Bundle savedInstanceState) {                              //...                              if(mManager == null){                                  mManager = new TestResource();                              }                           }                           class TestResource {}                        }



                       解决办法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要Context,解决办法同上
                      

                d.匿名内部类导致的内存泄漏:
                       ①android开发经常会继承实现Activity/Fragment/View,如果用匿名内部类或者内部类的方法创建一个Thread,
                    因为内部类会持有外部类的引用,另外因为线程是直接被GC Root强引用不会被回收掉,所以会造成Activity内存泄漏
                    
                     解决办法:当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉
                           另外考虑改写成静态内部类或者该内部类抽取出来封装成一个单例,如何需要在内部类
                                                                
                       ②另外使用了匿名内部类,并且把该匿名内部类对象赋值给一个静态对象(或者单例中的成员变量),那么会引起内存泄漏
                    解决办法:不要赋值给一个静态对象,改写成静态内部类或者该内部类抽取出来封装成一个单例                                         

                    

               public class MainActivity extends Activity {                       Runnable ref1 = new MyRunable();//这种方式不会持有MainActivity的引用                       Runnable ref2 = new Runnable() {                       @Override                       public void run() {                                                 }                      };                     }

                 


                e.Handler 造成的内存泄漏
                       假如声明了一个延迟10分钟执行的消息Message,mHandler将其push进了消息队列MessageQueue里。当该
                  Activity被finish()掉时,延迟执行任务的Message还会继续存在于主线程中,它持有该 Activity的Handler引用,
                  而Handler对象为内部类,它持有外部类Activity的引用
                       解决办法:方法一:onDestory方法中调用mHandler.removeCallbacksAndMessages(null);清空消息队列中的Message
                                       然后把handler置为空
                                      方法二:Handler内部类声明为静态的,则其存活期跟Activity的生命周期就无关了;另外如果需要调用外部类成员变量
                                     因为静态内部类中没法调用外部类的非静态成员变量.所以使用弱引用Activity的方式
                            

                   
                g.避免静态内部类中使用非静态外部成员变量(包括context),即使要使用,也要考虑适时把外部成员变量置空;
                  另外我们也可以在内部类中使用弱引用来引用外部类的变量   
                     
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) {                               // ...                               }                           }                        }



                      
                f.尽量避免使用static成员变量
                   static成员变量的生命周期将与整个app进程生命周期一样    
                
                h.避免 override finalize()
                
                i.资源未关闭造成的内存泄漏:
                    对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,
                应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。   
          
                k.属性动画造成内存泄漏
                  另外当我们使用属性动画,我们需要调用一些方法将动画停止,特别是无限循环的动画,否则也会造成内存泄漏,
                
                l.RxJava 使用不当造成内存泄漏
                    如果没有及时取消订阅,就会造成内存泄漏:                
                j.一些不良代码造成的内存压力
                    比如Bitmap对象不使用时我们应该下recycle()再置为空
                    
               
             
                 
6.降低内存消耗和内存泄漏几率的编码习惯总结:
    (1).尽可能少的使用服务:
          ①如果你的应用需要使用服务来进行后台操作,那么尽可能让他在的确需要工作的时候才进行工作。
           另外请确保当你的工作完成之后结束掉你的服务。
          ②管理你的服务最简单的方法是使用一个IntentService,他会在他处理完事件之后自行关闭掉服务,详情请参看使用前台服务。
           
    (2)当用户界面隐藏起来的时候,释放掉内存资源:
           按Home键界面不可见时,会触发onTrimMemory()的回调接口,带着一个TRIM_MEMORY_UI_HIDDEN参数。这个接口同OnStop()
        的回调接口的不同在于 OnStop()接口会在应用跳转的时候被调用,而onTrimMemory接口只有在所有界面都不可见的
        情况下被调用,尽管你需要实现onStop()方法来释放掉Activity资源,例如网络访问或者注销掉广播接收器,但这还不够,你不应该在
        onTrimMemory之前就释放掉你的ui资源。这能够保证用户通过返回键返回到你的应用中,你的ui资源同样存在,而且能够快速的
        显示出来。         
        
    (3)当内存不足时释放掉一些内存:
          在你的应用的生命周期中,onTrimMemory()接口同样会在当整个设备的内存变得很低的时候被调用。
          你应该根据从onTrimMemory()中传来的内存等级,选择性的释放掉你资源  
            
                   
     (4)view的onDetachedFromWindow()方法中做一些内存清理工作
     (5)activity的onDestory方法中内存清理工作,setContentView(R.layout.empty_view);
     (6)减少GC的开销:
        不要调用System.gc()
        不要创造大量的布局变量,因为主GC会在内存不足的时候执行
     (7)上面内存泄漏的解决办法         
     (8)主动释放ImageView的图片资源
           
private static void recycleImageViewBitMap(ImageView imageView) {          if (imageView != null) {                  BitmapDrawable bd = (BitmapDrawable) imageView.getDrawable();                  rceycleBitmapDrawable(bd);                }           }          private static void rceycleBitmapDrawable(BitmapDrawable bitmapDrawable) {               if (bitmapDrawable != null) {                  Bitmap bitmap = bitmapDrawable.getBitmap();                  rceycleBitmap(bitmap);               }               bitmapDrawable = null;          }          private static void rceycleBitmap(Bitmap bitmap) {               if (bitmap != null && !bitmap.isRecycled()) {                   bitmap.recycle();                   bitmap = null;                }           }       



     (9).主动释放ImageView的背景资源
        
public static void recycleBackgroundBitMap(ImageView view) {              if (view != null) {                 BitmapDrawable bd = (BitmapDrawable) view.getBackground();                 rceycleBitmapDrawable(bd);               }         } 



         
     (10)尽量少用Png图,多用NinePatch的图:Android studio右键create 9-Patch file自动转换成.9图
     (11)使用大图之前,尽量先对其进行压缩
     (12)常量声明为static final  

     (13)避免多度绘制

   

 

     开发中遇到的内存泄漏情况差不多就这么多了,如果以后有再补上,转发请注明地址:http://blog.csdn.net/qq_31694651/article/details/52556052




           
                      
2 0
原创粉丝点击