美文网首页
9.2019java内存模型相关

9.2019java内存模型相关

作者: 任振铭 | 来源:发表于2019-10-24 16:47 被阅读0次
    java内存模型

    堆内存是被所有线程共享的运行时内存区域,存在可见性的问题。线程之间共享变量存储在主存中,每个线程都有一个私有的本地内存,本地内存存储了该线程共享变量的副本(本地内存是一个抽象概念,并不真实存在),两个线程要通信的话,首先A线程把本地内存更新过的共享变量更新到主存中,然后B线程去主存中读取A线程更新过的共享变量,也就是说假设线程A执行了i = 1这行代码更新主线程变量i的值,会首先在自己的工作线程中堆变量i进行赋值,然后再写入主存当中,而不是直接写入主存

    new出来的子线程和new 出来的对象有何不同

    线程运行在虚拟机栈中;
    对象实例运行在堆中,是线程共享的

    创建了一个子线程,然后在子线程中创建一个对象实例,这个对象是否属于这个子线程

    不是,因为线程运行在虚拟机栈中,而对象实例是在堆中创建,是线程共享的,所以不是这个线程私有的

    在activity中new了大量的Thread,因为频繁的new对象会导致频繁的gc,那么此时这些Thread会不会被垃圾回收器回收?

    不会,首先对象实例和Thread的概念已经混淆了,对象存储在堆中,而线程运行在虚拟机栈中,虽然都是new出来的,但是不是一回事;另外栈中的内存不是垃圾回收器管理的,线程创建之后,执行完毕自会自动出栈。

    安卓中四大引用

    四种引用什么时候会被回收,需要知道Java垃圾回收机制的回收原理,根据可达性分析法判断引用到GC Roots之间是否存在引用,如果没有,那么就会在gc的时候被回收,可作为GC Roots的对象包括方法区中静态属性引用的对象,方法区中常量引用的对象,虚拟机栈(本地变量表)中引用的对象,本地方法栈JNI(native)方法中引用的对象

    强引用:

    强引用不能简单的理解为new出来的对象,new出来的额对象必须有GC Roots引用到它才能称之为强引用,对于一个强引用对象,如果没有其他的引用关系,只要超过了引用的作用域(如超出局部变量作用范围)或者显示将相应(强)引用赋值为null(当GC ROOT不再引用它),就可以根据具体的垃圾回收机制被回收。

    软引用(SoftReference):

    软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

    弱引用(WeakReference):

    只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
    问题,我在当前activity创建的handler中使用WeakReference包裹了当前activity引用,那么此时发生了gc(当前页面没有退出),这个activity引用是否还能从WeakReference中获取到呢?答案当然是可以,但是这似乎与定义矛盾了,定义说的是只要发现了弱引用,就会去回收,其实原因很简单,即便是使用了弱引用,在GC roots仍然可达的情况下,也是不能被回收的,这种情况就是activity虽然被弱引用处理,但是由于activity的强引用仍然存在(页面存在,强引用自然存在),那么垃圾回收器就不会去回收它,只有当页面退出,activity被置为null才会进行回收。

    虚引用(PhantomReference):

    虚引用必须和引用队列一起使用。深入理解JAVA虚拟机一书中有这样一句描述:“为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知”。那么如何监听这个通知,其实就是通过这个传入的引用队列实现的,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,将这个虚引用加入引用队列,在其关联的虚引用出队前,不会彻底销毁该对象。 所以可以通过检查引用队列中是否有相应的虚引用来判断对象是否已经被回收了(我想LeakCanray检测activity内存泄漏的时候也是仿照这个原理写的,如果activity被回收,就把它加入引用队列,通过判断引用队列中是否包含这个activity来判断activity是否被回收,包含则被回收,不包含则泄漏)。所以虚引用更多的是用于对象回收的监听,能做的功能如下:
    重要对象回收监听 进行日志统计
    系统gc监听 因为虚引用每次GC都会被回收,那么我们就可以通过虚引用来判断gc的频率,如果频率过大,内存使用可能存在问题,才导致了系统gc频繁调用

    Java的内存分配

    Java程序运行时的内存结构分成:方法区、栈内存、堆内存、本地方法栈几种。
    方法区存放装载的类数据信息,包括:
    ·基本信息:每个类的全限定名、每个类的直接超类的全限定名、该类是类还是接口、该类型的访问修饰符、直接超接口的全限定名的有序列表。
    ·每个已装载类的详细信息:运行时常量池、字段信息、方法信息、静态变量、到类classloader的引用、到类class的引用。
    栈内存
    Java栈内存由局部变量区、操作数栈、帧数据区组成,以帧的形式存放本地方法的调用状态(包括方法调用的参数、局部变量、中间结果……)。
    堆内存
    堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
    本地方法栈内存
    Java通过Java本地接口JNI(Java Native Interface)来调用其它语言编写的程序,在Java里面用native修饰符来描述一个方法是本地方法。
    String的内存分配
    String是一个特殊的包装类数据,由于String类的值不可变性,当String变量需要经常变换其值时,应该考虑使用StringBuffer或StringBuilder类,以提高程序效率。

    内存泄漏的场景和解决办法

    1.非静态内部类的静态实例
    非静态内部类会持有外部类的引用,如果非静态内部类的实例是静态的,就会长期的维持着外部类的引用,组织被系统回收,解决办法是使用静态内部类
    2.多线程相关的匿名内部类和非静态内部类
    匿名内部类同样会持有外部类的引用,如果在线程中执行耗时操作就有可能发生内存泄漏,导致外部类无法被回收,直到耗时任务结束,解决办法是在页面退出时结束线程中的任务
    3.Handler内存泄漏
    Handler导致的内存泄漏也可以被归纳为非静态内部类导致的,Handler内部message是被存储在MessageQueue中的,有些message不能马上被处理,存在的时间会很长,导致handler无法被回收,如果handler是非静态的,就会导致它的外部类无法被回收,解决办法是1.使用静态handler,外部类引用使用弱引用处理2.在退出页面时移除消息队列中的消息
    4.Context导致内存泄漏
    根据场景确定使用Activity的Context还是Application的Context,因为二者生命周期不同,对于不必须使用Activity的Context的场景(Dialog),一律采用Application的Context,单例模式是最常见的发生此泄漏的场景,比如传入一个Activity的Context被静态类引用,导致无法回收
    5.静态View导致泄漏
    使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致无法回收,解决办法是在Activity销毁的时候将静态View设置为null(View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这个context对象是我们的Activity,声明一个静态变量引用这个View,也就引用了activity)
    6.WebView导致的内存泄漏
    WebView只要使用一次,内存就不会被释放,所以WebView都存在内存泄漏的问题,通常的解决办法是为WebView单开一个进程,使用AIDL进行通信,根据业务需求在合适的时机释放掉
    7.资源对象未关闭导致
    如Cursor,File等,内部往往都使用了缓冲,会造成内存泄漏,一定要确保关闭它并将引用置为null
    8.集合中的对象未清理
    集合用于保存对象,如果集合越来越大,不进行合理的清理,尤其是入股集合是静态的
    9.Bitmap导致内存泄漏
    bitmap是比较占内存的,所以一定要在不使用的时候及时进行清理,避免静态变量持有大的bitmap对象
    10.监听器未关闭
    很多需要register和unregister的系统服务要在合适的时候进行unregister,手动添加的listener也需要及时移除

    如何避免OOM?

    1.使用更加轻量的数据结构:如使用ArrayMap/SparseArray替代HashMap,HashMap更耗内存,因为它需要额外的实例对象来记录Mapping操作,SparseArray更加高效,因为它避免了Key Value的自动装箱,和装箱后的解箱操作
    2.便面枚举的使用,可以用静态常量或者注解@IntDef替代
    3.Bitmap优化:
    a.尺寸压缩:通过InSampleSize设置合适的缩放
    b.颜色质量:设置合适的format,ARGB_6666/RBG_545/ARGB_4444/ALPHA_6,存在很大差异
    c.inBitmap:使用inBitmap属性可以告知Bitmap解码器去尝试使用已经存在的内存区域,新解码的Bitmap会尝试去使用之前那张Bitmap在Heap中所占据的pixel data内存区域,而不是去问内存重新申请一块区域来存放Bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小,但复用存在一些限制,具体体现在:在Android 4.4之前只能重用相同大小的Bitmap的内存,而Android 4.4及以后版本则只要后来的Bitmap比之前的小即可。使用inBitmap参数前,每创建一个Bitmap对象都会分配一块内存供其使用,而使用了inBitmap参数后,多个Bitmap可以复用一块内存,这样可以提高性能
    4.StringBuilder替代String: 在有些时候,代码中会需要使用到大量的字符串拼接的操作,这种时候有必要考虑使用StringBuilder来替代频繁的“+”
    5.避免在类似onDraw这样的方法中创建对象,因为它会迅速占用大量内存,引起频繁的GC甚至内存抖动
    6.减少内存泄漏也是一种避免OOM的方法

    LruCache原理

    底层通过LinkedHashMap实现,LinkedHashmap内部有双向链表,并且它支持两种排序方式,一种是插入的顺序,一种是读取的顺序,在LruCache中它使用的是读取的顺序,当我在里边插入了很多元素后,假设此时我读取了其中的一个元素,那么这个元素会被移动到链表的尾部,一直度就一直移动,这样一来链表就会形成这样的结构,尾部是最近使用最多的,头部是最近使用最少的。那么当我再次往里边插入元素的时候,当容量达到我们设置的阈值,就会不断的从链表头部移除缓存,达到将缓存容量控制在一定范围内的效果。

    相关文章

      网友评论

          本文标题:9.2019java内存模型相关

          本文链接:https://www.haomeiwen.com/subject/ugezaqtx.html