内存管理机制
- 分配机制:安卓系统为每个进程分配合理的内存,而不会至于内存不够或者某个进程内存占用太多。
- 回收机制:当系统内存不够时,他会有个回收再分配的机制,保证新的进程能够正常运行。
分配和回收都会根据进程的优先级来
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性能更好。