Android 优化二 Java内存分配机制及内存泄漏

Java内存分配机制及内存泄漏
目录介绍

  • 1.JVM内存管理
  • 1.1 JVM内存管理图
  • 1.2 Java采用GC进行内存管理。
  • 2.JVM内存分配的几种策略
  • 2.1 静态的
  • 2.2 栈式的
  • 2.3 堆式的
  • 2.4 堆和栈的区别
  • 2.5 得出结论
  • 2.6 举个例子
  • 2.7 调用 System.gc();进行内存回收
  • 3.GC简单介绍
  • 3.1 内存垃圾回收机制
  • 3.2 关于GC介绍
  • 3.3 如何监听GC过程
  • 3.4 GC过程与对象的引用类型关系
  • 4.内存泄漏简单介绍
  • 4.1 内存泄漏的定义
  • 4.2 内存泄漏与内存溢出的区别
  • 4.3 内存泄漏带来的影响
  • 4.4 典型内存泄漏案例

关于内存泄漏笔记

0.本人写的综合案例

  • #### 案例
  • #### 说明及截图
  • 模块:新闻,音乐,视频,图片,唐诗宋词,快递,天气,记事本,阅读器等等
  • 接口:七牛,阿里云,天行,干货集中营,极速数据,追书神器等等

思维导图

1.JVM内存管理

1.1 JVM内存管理


1.2 Java采用GC进行内存管理。

  • Android虚拟机的垃圾回收采用的是根搜索算法。GC会从根节点(GC Roots)开始对heap进行遍历。到最后,部分没有直接或者间接引用到GC Roots的就是需要回收的垃圾,会被GC回收掉。而内存泄漏出现的原因就是存在了无效的引用,导致本来需要被GC的对象没有被回收掉。
    深入的JVM内存管理知识,推荐《深入理解Java虚拟机》。

2.JVM内存分配的几种策略。

2.1 静态的

  • 静态的存储区,内存在程序编译的时候就已经分配好了,这块内存在程序整个运行期间都一直存在
    它主要存放静态数据、全局的static数据和一些常量。

2.2 栈式的

  • 在执行方法时,方法一些内部变量的存储都可以放在栈上面创建,方法执行结束的时候这些存储单元就会自动被注释掉。栈 内存包括分配的运算速度很快,因为内在在处理器里面。当然容量有限,并且栈式一块连续的内存区域,大小是由操作系统决定的,他先进后出,进出完成不会产生碎片,运行效率高且稳定

2.3 堆式的

  • 也叫动态内存 。我们通常使用new 来申请分配一个内存。这里也是我们讨论内存泄漏优化的关键存储区。GC会根据内存的使用情况,对堆内存里的垃圾内存进行回收。堆内存是一块不连续的内存区域,如果频繁地new/remove会造成大量的内存碎片,GC频繁的回收,导致内存抖动,这也会消耗我们应用的性能

2.4 堆和栈的区别

  • 在函数中(说明是局部变量)定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。
  • 当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。
  • 堆内存用于存放所有由new创建的对象(内容包括该对象其中的所有成员变量)和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。
  • 在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

2.5 得出结论

  • 1.局部变量的基本数据类型和引用,存储于栈中,引用的对象实体存储于堆中。因为它们属于方法中的变量,生命周期随方法而结束。
    2.成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体),因为它们属于类,类对象终究是要被new出来使用的。
    3.我们所说的内存泄露,只针对堆内存,他们存放的就是引用指向的对象实体。

2.6 举个例子

public class Sample() {
    int s1 = 0;
    Sample mSample1 = new Sample();
    public void method() {
        int s2 = 1;
        Sample mSample2 = new Sample();
    }
}
Sample mSample3 = new Sample();
Sample 类的局部变量 s2 和引用变量 mSample2 都是存在于栈中,但 mSample2 指向的对象是存在于堆上的。
mSample3 指向的对象实体存放在堆上,包括这个对象的所有成员变量 s1 和 mSample1,而它自己存在于栈中。

2.7 调用 System.gc();进行内存回收

  • 我们知道可以调用 System.gc();进行内存回收,但是GC不一定会执行。面对GC的机制,我们是否无能为力?其实我们可以通过声明一些引用标记来让GC更好对内存进行回收。


  • 小技巧
    成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体),因为他们属于类,类对象最终还是要被new出来的
    局部变量的基本数据类型和引用存在栈中,应用的对象实体存储在堆中。因为它们属于方法当中的变量,生命周期会随着方法一起结束

3.GC工作原理

3.1 内存垃圾回收机制

  • 是从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链,当遍历一遍后得到上述这些无法回收的对象和他们所引用的对象链,组成无法回收的对象集合,而其他孤立对象(集)就作为垃圾回收
  • GC为了能够正确释放对象,必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC都需要进行监控。监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。

3.2 关于GC介绍

  • 有几个函数可以访问GC,例如运行GC的函数System.gc(),但是根据Java语言规范定义,该函数不保证JVM的垃圾收集器一定会执行。因为不同的JVM实现者可能使用不同的算法管理GC
  • 通常GC的线程的优先级别较低。JVM调用GC的策略也有很多种,有的是内存使用到达一定程度时,GC才开始工作,也有定时执行的,有的是平缓执行GC,有的是中断式执行GC。但通常来说,我们不需要关心这些。
  • 通过关键字 new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间

3.3 如何监听GC过程

  • 系统每进行一次GC操作时,都会在LogCat中打印一条日志,我们只要去分析这条日志就可以了,日志的基本格式如下
    D/dalvikvm: , ,
    第一部分GC_Reason,这个是触发这次GC操作的

    原因,一般情况下一共有以下几种触发GC操作的原因:
    - GC_CONCURRENT: 当我们应用程序的堆内存快要满的时候,系统会自动触发GC操作来释放内存。
    - GC_FOR_MALLOC: 当我们的应用程序需要分配更多内存,可是现有内存已经不足的时候,系统会进行GC操作来释放内存。
    - GC_HPROF_DUMP_HEAP: 当生成HPROF文件的时候,系统会进行GC操作,关于HPROF文件我们下面会讲到。
    - GC_EXPLICIT: 这种情况就是我们刚才提到过的,主动通知系统去进行GC操作,比如调用System.gc()方法来通知系统。或者在DDMS中,通过工具按钮也是可以显式地告诉系统进行GC操作的。

    第二部分Amount_freed,表示系统通过这次GC操作释放了多少内存
    第三部分Heap_stats中会显示当前内存的空闲比例以及使用情况(活动对象所占内存 / 当前程序总内存)
    第四部分Pause_time表示这次GC操作导致应用程序暂停的时间。关于这个暂停的时间,Android在2.3的版本当中进行过一次优化,在2.3之前GC操作是不能并发进行的,也就是系统正在进行GC,那么应用程序就只能阻塞住等待GC结束。虽说这个阻塞的过程并不会很长,也就是几百毫秒,但是用户在使用我们的程序时还是有可能会感觉到略微的卡顿。而自2.3之后,GC操作改成了并发的方式进行,就是说GC的过程中不会影响到应用程序的正常运行,但是在GC操作的开始和结束的时候会短暂阻塞一段时间,不过优化到这种程度,用户已经是完全无法察觉到了

3.4 GC过程与对象的引用类型关系

  • Java对引用的分类Strong reference, SoftReference, WeakReference, PhatomReference


  • 软引用和弱引用
    在Android应用的开发中,为了防止内存溢出,在处理一些占用内存大而且声明周期较长的对象时候,可以尽量应用软引用和弱引用技术
    软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
    利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。
  • 内存泄漏的原因:
    堆内存中的长生命周期的对象持有短生命周期对象的强/软引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的根本原因

4.内存泄漏简单介绍

4.1 内存泄漏的定义

  • 当一个对象已经不需要使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用,从而导致了对象不能被GC回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏

4.2 内存泄漏与内存溢出的区别

  • 内存泄漏(Memory Leak)
    进程中某些对象已经没有使用的价值了,但是他们却还可以直接或间接地被引用到GC Root导致无法回收。当内存泄漏过多的时候,再加上应用本身占用的内存,日积月累最终就会导致内存溢出OOM
  • 内存溢出(OOM)
    当应用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出

4.3 内存泄漏带来的影响

  • 应用卡顿
    泄漏的内存影响了GC的内存分配,过多的内存泄漏会影响应用的执行效率
  • 应用异常(OOM)
    过多的内存泄漏,最终会导致 Dalvik分配的内存,出现OOM

4.4 典型内存泄漏案例

  • 案例代码

    Vector v = new Vector(10);
    for (int i = 1; i < 100; i++) {
    Object o = new Object();
    v.add(o);
    o = null;
    }
  • 分析
    在这个例子中,我们循环申请Object对象,并将所申请的对象放入一个 Vector 中,如果我们仅仅释放引用本身,那么 Vector 仍然引用该对象,所以这个对象对 GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从 Vector 中删除,最简单的方法就是将 Vector 对象设置为 null。

其他说明

时间: 2024-12-03 05:44:56

Android 优化二 Java内存分配机制及内存泄漏的相关文章

RAMCloud:内存云存储的内存分配机制

现在全闪存阵列已经见怪不怪了,EMC的XtremIO,还有VNX- F(Rockies),IBM FlashSystem.全闪存真正为效率而生,重新定义存储速度.凭借极致性能,高可用性,为您极大提高企业级应用效率.提到闪存的优势,那么毋庸置疑的就是速度!而在速度优势背后,SSD则面临着价格.容量以及寿命等方面的限制. 当然随着技术的发展,成本的下降,SSD有可能会取代机械硬盘,成为下一代企业存储的主要介质.机械硬盘可能转变为磁带的角色. 但是,闪存速度的确就是现在存储系统的极限吗?现在有需要基于

MySQL · 源码分析 · 内存分配机制

前言 内存资源由操作系统管理,分配与回收操作可能会执行系统调用(以 malloc 算法为例,较大的内存空间分配接口是 mmap, 而较小的空间 free 之后并不归还给操作系统 ),频繁的系统调用必然会降低系统性能,但是可以最大限度的把使用完毕的内存让给其它进程使用,相反长时间占有内存资源可以减少系统调用次数,但是内存资源不足会导致操作系统频繁换页,降低服务器的整体性能. 数据库是使用内存的"大户",合理的内存分配机制就尤为重要,上一期月报介绍了 PostgreSQL 的内存上下文,本

SQL SERVER 内存分配及常见内存问题(1)——简介

原文:SQL SERVER 内存分配及常见内存问题(1)--简介 一.问题: 1.SQL Server 所占用内存数量从启动以后就不断地增加:       首先,作为成熟的产品,内存溢出的机会微乎其微.对此要了解SQL SERVER与windows是如何协调.共享内存.并且SQL SERVER的内部对内存的管理机制. 2.在Windows 2003以上版本运行的SQL Server,内存使用量突然急剧下降:      内存是Sqlserver的生命线.在errorlog中,出现一下情况:   

SQL SERVER 内存分配及常见内存问题(2)——DMV查询

原文:SQL SERVER 内存分配及常见内存问题(2)--DMV查询 内存动态管理视图(DMV): 从sys.dm_os_memory_clerks开始. SELECT [type] , SUM(virtual_memory_reserved_kb) AS [VM Reserved] , SUM(virtual_memory_committed_kb) AS [VM Committed] , SUM(awe_allocated_kb) AS [AWE Allocated] , SUM(shar

GLIBC内存分配机制引发的“内存泄露”

我们正在开发的类数据库系统有一个内存模块,出现了一个疑似"内存泄露"问题,现象如下:内存模块的内存释放以后没有归还操作系统,比如内存模块占用的内存为10GB,释放内存以后,通过TOP命令或者/proc/pid/status查看占用的内存有时仍然为10G,有时为5G,有时为3G, etc,内存释放的行为不确定. 首先说一下内存模块的内存管理机制.我们的内存管理很简单,使用全局的定长内存池,每一个内存块为64KB,如果申请的内存小于等于64KB时,直接从内存池的空闲链表中获取一个内存块,内

SQL SERVER 内存分配及常见内存问题 简介

一.问题: 1.SQL Server 所占用内存数量从启动以后就不断地增加:       首先,作为成熟的产品,内存溢出的机会微乎其微.对此要了解SQL SERVER与windows是如何协调.共享内存.并且SQL SERVER的内部对内存的管理机制. 2.在Windows 2003以上版本运行的SQL Server,内存使用量突然急剧下降:      内存是Sqlserver的生命线.在errorlog中,出现一下情况:       这类问题往往不是sql server导致的,而是Window

java 字符串内存分配的分析与总结(推荐)_java

经常在网上各大版块都能看到对于java字符串运行时内存分配的探讨,形如:String a = "123",String b = new String("123"),这两种形式的字符串是存放在什么地方的呢,其实这两种形式的字符串字面值"123"本身在运行时既不是存放在栈上,也不是存放在堆上,他们是存放在方法区中的某个常量区,并且对于相同的字符串字面值在内存中只保留一份.下面我们将以实例来分析. 1.==运算符作用在两个字符串引用比较的两个案例: p

java程序运行时内存分配详解_java

一. 基本概念    每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈.进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享.Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量.虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也

Java内存分配、管理小结

首先是概念层面的几个问题: Java中运行时内存结构有哪几种?Java中为什么要设计堆栈分离?Java多线程中是如何实现数据共享的?Java反射的基础是什么? 然后是运用层面: 引用类型变量和对象的区别?什么情况下用局部变量,什么情况下用成员变量?数组如何初始化?声明一个数组的过程中,如何分配内存?声明基本类型数组和声明引用类型的数组,初始化时,内存分配机制有什么区?在什么情况下,我们的方法设计为静态化,为什么?(上次胡老师问文奇,问的哑口无言,当时想回答,却老感觉表述不清楚,这里也简单说明一下