内存管理

部分内容引自此篇博文   点击打开链接


1.JVM体系架构

这里并不特指安卓自己的Dalvik虚拟机,Java平台支持安卓的虚拟机有不少,都是按照Java规范来实现的,本质是相同的



运行期数据区即是操作系统分给虚拟机的内存


类文件,在磁盘中通过这个系统将字节码加载到内存,不同的部分放到不同的分区


1.1、程序计数器
      •程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。比如多线程的时候,要去执行之前被挂起的线程,需要这个计数器告诉cpu从哪执行。
1.2、Java 虚拟机栈(Stack)
      •它描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame )用于存储局部变量表、操作栈、动态链接、方法出口等信息。注意,对象的引用也存放在这
      •与程序计数器一样,Java 虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同,即在线程创建时创建,线程结束时栈内存也随之释放。
1.3、本地方法栈
      •本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

以上是线程私有的,每个线程都需要有相应的空间

1.4、Java 堆(Heap)
      •此内存区域的唯一目的就是存放对象实例
      •一个JVM实例只存在一个堆,堆内存的大小是可以调节的。类加载器加载了类文件之后,需要把类放到堆内存中,以便执行器执行。
      •堆内存是线程共享的。
1.5、方法区
      •方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

我们主要关心的就是堆和栈的空间情况,堆里对象多了就会溢出,栈里的调用太多,比如递归调用,如果没有出口,一定溢出



当我们定义好一个类和他的方法,然后去实例化这个类

这个时候,会去先看方法区内是否有加载,如果没有,则通知类装载子系统加载这个类的class文件,然后把不同东西放在不同区

类信息放在方法区,版本号、常量、方法字节码信息等等

加载完,生成实例,放在堆上

引用放在栈


如果主线程调用了,则引用在主线程的栈里,实例在主线程的堆里面,而类里面的代码放在方法区,而不是堆栈,这样可以共用这些代码,例如实例化多个对象,变量什么的不同,在各自的堆栈里,而这些方法字节码就在方法区以共用,通过堆里的数据指针找到方法



2.垃圾回收机制 GC(garbage collection)

他也是一个程序,垃圾回收器,任何一个APP跑起来,至少有两个线程,一个主线程,一个垃圾回收线程


显式内存管理(C/C++)
  内存管理是程序开发者的职责
  显示管理常见问题
  野指针:使用了一个指针,但是该指针指向的内存空间已经被Free
  内存泄露:内存空间已经申请,使用完毕后未主动释放。会一直占用内存。

自动内存管理(Java /C#/ 一些脚本语言)
   内存空间是由一个叫做垃圾回收器的程序自动管理
   优点:
    增加了程序的可靠性,减小了memory leak和野指针的情况、提高了程序猿的效率。
   缺点:
       程序猿无法控制GC的时间
       判断哪些内存需要回收需要耗费系统开销
       逻辑上的内存泄露依然会存在


GC的工作原理
找出不再使用的对象( Garbage ),进行回收( Collection )
如何实现
 所有不再使用的对象都是垃圾
常见判断方法
引用计数法
根搜索算法

    2.1 引用计数法
        给堆中的每一个对象增加一个引用计数器,当每一次创建一个对象并赋值给一个变量值,引用计数器就加1.当对象不再使用时(出了作用域),引用计数机减一。一单引用计数器为0,对象就满足了垃圾回收的条件
         实现起来很简单,但无法解决循环引用的问题
public class CircularRefTest {
    private CircularRefTest instance = null;
    private byte[] buffer = new byte[1024 * 1024];
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		CircularRefTest a = new CircularRefTest();
		CircularRefTest b = new CircularRefTest();
		a.instance = b;
		b.instance = a;
		a = null;
		b = null;
		System.gc();
	}
}

你指向我,我指向你,谁也没办法释放~

    2.2根搜索算法

   •在主流的商用程序处理语言中,都是使用跟搜索算法来判断对象是否存活的。
   •该算法的基本思路就是通过一系列的名为GC Root的对象作为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链(Reference chain)。当一个对象到GC root之间没有任何引用链相连(用图论的话来说就是没GC  roots到这些对象不可达)时,证明该对象是不可用的,GC程序即可回收这些对象。


这种连接有两种可能,一是在A中定义了成员变量B;或者A中定义了方法,调用了B

关于GC roots
不同的虚拟机可能有所区别,大致是以下几种

虚拟机栈中的引用对象,debug中最常见的就在这
方法区中的静态属性引用的对象
方法区中常量引用的对象

Shallow size 就是对象本身占用的内存大小,也就是对象头加上成员变量占用内存大小的总和
Retained size 是该对象自己的shallow size 加上 仅可以从该对象访问(直接或者间接访问)的对象的shallow size之和。
Retained size是该对象被GC之后所能回收的内存的总和。

        针对obj1,他的shallow size 就是他自己的大小,而他的retained size还包括obj2和obj4,;但是obj3就不是了,因为他还可以直接从GC Root访问,并不是单路的
        倘若把GC Root置空了,那么obj3就成了obj1的retained size的一部分,这时,当需要回收obj1时,同时obj2 obj3 obj4就可以一并回收了,因为没有别人要用到他们

      2.3 标记清除算法
      根搜索算法能找到可以引用到哪些,找到路径;标记清除算法可以找到哪些还在被引用并清除不被引用的;这两个合在一起才是一个完整的GC过程
     

左边的root删除后,会再来搜索下,发现下面两个也可以删除

      


这就是GC的一个大概过程

  2.4 GC触发时间

  •1.申请heap space失败后会触发CG回收,空间不足,肯定要先清理一遍
  •2.系统进入idle后一段时间会进行回收,比如当前线程什么都没做,sleep,开始回收
  •3.主动调用GC进行回收,自己调用

3.DEMO

 Heap OOM  & Stack Overflow

布局里加上两个button  属性onclick

public class MainActivity extends Activity {
	
	List<MainActivity> list ;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		list = new ArrayList<MainActivity>();
	}

	/**
	 * 栈溢出
	 * @param v
	 */
	public void click1(View v){
		
		callme();
	}

	public void callme(){
		
		callme();
	};
	/**
	 * 堆溢出
	 * @param v
	 */
	public void click2(View v){
	
 		while(true){		
			MainActivity obj = new MainActivity();
			list.add(obj);
			obj=null;
		}
	}	
}

        callme在不断调自己

        第二个不断new新对象,一般很久才会溢出,可能在new的时候GC就在回收了,所以这里加了一个arraylist,虽然这里对象置为空,但是这里arraylist的引用链还在,不会被GC回收


4.内存泄露

以为被释放的实际上没有被释放,刚才上面的堆溢出就是一个例子,虽然置空了,但是没释放

   

内存泄露可能导致内存溢出,但不是必然导致内存溢出

5.MAT 内存分析工具
这个工具依赖于eclipse,有时候也不会太准,但是可以作为内存分析的参考
比如因为某个程序内存溢出了,我们可以在DDMS里面,点击这个程序的线程,然后再去点击Dump 

这会导出内存里的信息,我是保存到D盘的heap目录下

然后再通过SDK里的工具,hprof-conv把这个原始文件转换一下,最好事先配置过环境变量,省的找这个工具的路径比较麻烦
这个工具名后面要两个参数,第一个为文件原始名称,第二个转换后的命名


最后再用MAT打开它即可,后面就不细说了,结合代码来分析


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值