内存

内存管理机制

  • 分配机制:安卓系统为每个进程分配合理的内存,而不会至于内存不够或者某个进程内存占用太多。
  • 回收机制:当系统内存不够时,他会有个回收再分配的机制,保证新的进程能够正常运行。

分配和回收都会根据进程的优先级来

dalvik内存(即java内存)

heap(堆内存):线程共享区域

  • 存储数据类型
    • 成员变量
      • 基本数据类型:其变量名及其数据值存放在堆内存中
      • 引用数据类型:对象存放在堆内存中,其变量名和地址值存放在栈中,该地址值指向所引用的对象
    • 局部变量
      • 引用数据类型:对象存放在堆内存中,其变量名和地址值存放在栈中,该地址值指向所引用的对象
  • 内存释放
    GC

stack(栈内存):线程私有区域

  • 存储数据类型
    • 局部变量
      • 基本数据类型:其变量名及其数据值存放在栈内存中
    • 疑问:局部变量中的基本数据类型都是存储在栈内存中吗?
    不是。
    int[] array=new int[]{1,2};
    由于new了一个对象,所以new int[]{1,2}这个对象是存储在堆中的,
    也就是说1,2这两个基本数据类型是存储在堆中。
    
  • 内存释放
    方法结束,内存自动释放,有自己的作用域

方法区:线程共享区域

  • 存储数据类型
    方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量

特殊类型String

String对象创建有两种方式:

  • 字面量形式创建
String str1 = "aaa";
解释:字符串常量池中不存在aaa这个字符串对象的引用,
	所以新创建一个字符串对象,然后将这个引用放入字符串常量池。
	
String str2 = "aaa";
解释:字符串常量池中已经存在aaa这个字符串对象的引用,
	于是将已经存在的字符串对象的引用返回给变量str2,这里不会再重新创建字符串对象。
	
结论:System.out.println(str1 == str2); 结果为true。
  • 使用new创建
不论字符串常量池有没有相同内容对象的引用,都重新创建字符串对象,然后将这个引用存储到栈内存中。

String str3 = new String("aaa");
String str4 = new String("aaa");

结论:System.out.println(str3 == str4); 结果为false。

如果想让new出来的String对象的引用加入到字符串常量池中,可以使用intern方法。
即String str4 = str3.intern();这时候System.out.println(str3 == str4); 结果为true。
  • string,stringbuilder,stringbuffer的区别
    • string是不可变字符序列,stringbuilder和stringbuffer是可变字符序列。
    • stringbuffer是线程安全的,执行速度慢;stringbuilder是线程不安全的,执行速度快。
      在这里插入图片描述
      在这里插入图片描述

问题:内存中的堆栈和数据结构中堆栈的区别?

内存中的堆栈和数据结构中的堆栈没有任何关系,是两个完全不同的概念。它们除了名字一样,没有必然的联系。

内存中的堆栈我们可以理解为是内存卡中的一块空间,由操作系统内核来决定哪一块是堆内存,哪一块是栈内存,大小又是多少。是物理上的概念。

数据结构里的堆栈指的是为了实现某种应用而抽离出来的操作,比如世界富豪排行榜。是组织数据的一种手段或工具。是抽象上的概念。

native内存

  • JNI
    • 如何生成.so
    • 如何使用.so

匿名共享内存Ashmem(冷门知识)(基于Binder+MemoryFile)

共享内存是Linux自带的一种IPC机制

Android直接使用了该模型,不过做出了自己的改进,将不同进程中的同一段物理内存映射到进程各自的虚拟地址空间,从而实现高效的进程间共享,进而形成了Android的匿名共享内存。

在Android中,主要提供了MemoryFile这个类来供应用使用匿名共享内存,通过JNI调用底层C++方法,同时MemoryFile也是进程间大数据传递的一个手段。

它有两个特点:

  • 能够辅助内存管理系统来有效地管理不再使用的内存块
  • 它通过Binder进程间通信机制来实现进程间的内存共享

基本使用:
步骤1:
创建AIDL文件IMemoryAidlInterface.aidl

interface IMemoryAidlInterface {
    ParcelFileDescriptor getParcelFileDescriptor();
}

步骤2:创建远程服务MemoryFetchService

public class MemoryFetchService extends Service {

    private Binder binder = new IMemoryAidlInterface.Stub() {
        @Override
        public void setParcelFileDescriptor(ParcelFileDescriptor p) throws RemoteException {
      	  //读取数据
           FileInputStream fis = new FileInputStream(p.getFileDescriptor());
            try {
                fis.read(new byte[100]);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

步骤3:绑定服务

Intent intent = new Intent(this, MemoryFetchService.class);
bindService(intent, new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        try {
            //第一个是文件名,这个可以为null,第二个参数是文件长度
            byte[] contentBytes = new byte[100];
            MemoryFile memoryFile = new MemoryFile("test_memory", contentBytes.length);
            
            //调用write将数据写入文件
            memoryFile .writeBytes(contentBytes, 0, 0, contentBytes.length);
            
            //由于MemoryFile的getFileDescriptor方法是@UnsupportedAppUsage的,需要用反射得到文件描述符
            Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
            FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);
            
            //调用ParcelFileDescriptor.dup()的目的是将文件描述符序列化,这是因为通过Binder传递的数据必须都是可序列化的。这样文件描述符就通过Binder传到了Service进程中
      		ParcelFileDescriptor parcelFileDescriptor = parcelFileDescriptor.dup(des);

			//将文件描述符传递给远程服务
			IMemoryAidlInterface aidlInterface = IMemoryAidlInterface.Stub.asInterface(service);
			aidlInterface.setParcelFileDescriptor(parcelFileDescriptor);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
}, BIND_AUTO_CREATE);

以上是应用层使用匿名共享内存的方法,关键点就是文件描述符(FileDescriptor)的传递,文件描述符是Linux系统中访问与更新文件的主要方式.

从MemoryFile字面上看出,共享内存被抽象成了文件,不过本质也是如此,就是在tmpfs临时文件系统中创建一个临时文件,(只是创建了节点,而没有看到实际的文件) 该文件与Ashmem驱动程序创建的匿名共享内存对应。

问题:Android的java程序为什么容易出现OOM?

首先要知道一个概念:RAM,即物理内存。

因为Android系统对dalvik的vm heapsize(虚拟内存)做了硬性限制,根据定制系统不一样,有16M、24M等。也就是说,在RAM充足的情况下,也可能发生OOM。

如何解决OOM

  • Android内存优化
  • 创建子进程
    • 创建一个新的进程,就可以把一些对象分配到新进程的heap上。
    • 方法:使用android:process标签
    • 缺点:会增加系统开销
  • 使用JNI在native heap上申请空间
    native heap的增长不受dalvik的vm heapsize的限制。
    只要RAM有足够的剩余空间,都可以在nativr heap上申请空间。
    当然,如果RAM快耗尽了,memory killer会杀进程释放RAM
  • 修改vm heapsize的大小

问题:Bitmap分配在dalvik heap还是native heap?

过多的创建bitmap会导致OOM,且native heap不受dalvik限制,所以bitmap只能是分配在dalvik heap上。因为只有这样才能解释bitmap容易导致OOM。

但是,有人可能会说,Bitmap确实是使用java native方法创建的啊,为什么会分配到dalvik heap中呢?

1,bitmap创建:
Bitmap bm = BitmapFactory.decodeResourceStream(res, value, is, pad, opts);

在这里插入图片描述
在这里插入图片描述
2.进入frameworks源码:
发现会调用到BitmapFactory.cpp中的deDecode方法,最终会调用到Graphics.cpp的createBitmap方法。

在这里插入图片描述
3.结论:
通过NewObject,创建Bitmap又回到了JAVA层。

问题:在Activity之间怎么传递Bitmap?

  • Bitmap实现了Parcelable接口,而Intent对象可以传递任意基本类型,uri,和序列化对象,所以bitmap理论上是可以直接传递的。但不建议这么操作。因为序列化和反序列化的过程中,是先将数据复制一份到内存中,然后再使用这个暂存的数据,创建一个完全相同的对象出来。Bitmap也是一样。复制它的二进制数据到内存,再创建出来一个一模一样的Bitmap对象。这样同一个数据在内存中存了好几份。比较占内存。
  • 一般都是传递uri地址。

问题:Serializable 和 Parcelable的区别?

  • Serializable在序列化时会产生大量的临时变量,容易引起频繁GC。
  • Parcelable不能使用在要将数据存储在磁盘的情况。
  • Parcelable比Serializable性能更好。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值