Java内存机制以及Android内存优化
来源:互联网 发布:算法高清完整版pdf 编辑:程序博客网 时间:2024/05/19 22:02
Java内存机制
1. 虚拟机运行时数据区
基本概念
虚拟机
模拟某种计算机体系结构,执行特定指令集的软件。包括进程虚拟机和系统虚拟机(VMWare)
- 进程虚拟机:JVM、Adobe Flash Player、FC模拟器
- 高级语言虚拟机:JVM、.NET CLR、P-Code
- Java语言虚拟机:JVM、Apache Harmony
- Java(TM)虚拟机
Java(TM)虚拟机并不是只能执行Java程序
三大商用JVM:Oracle Hotspot、Oracle JRockit Vm、IBM J9 VM- Oracle HotSpot虚拟机
Oracle JDK自带的虚拟机。HotSpot命名来自它的“热点代码探测”技术。
每一个Java程序都对应一个Java虚拟机实例。
- Oracle HotSpot虚拟机
- Java(TM)虚拟机
- Java语言虚拟机:JVM、Apache Harmony
- 高级语言虚拟机:JVM、.NET CLR、P-Code
公有设计,私有实现
《Java虚拟机规范》(JVMS)定义了概念模型,不约束虚拟机的具体实现。
Java虚拟机运行时数据区
在《Java虚拟机规范》中定义了若干种程序运行期间会使用到的存储不同类型数据的区域。
有一些区域是全局共享的,随着虚拟机启动而创建,随着虚拟机退出而销毁。有一些区域是线程私有的,随着线程开始和结束而创建和销毁。
是所有Java虚拟机共同的内存区域概念模型。
既然虚拟机作为一个虚拟的计算机, 来执行我们的程序, 那么在执行的过程中, 必然要有地方存放我们的代码(class文件); 在执行的过程中, 总会创建很多对象, 必须有地方存放这些对象; 在执行的过程中, 还需要保存一些执行的状态, 比如, 将要执行哪个方法, 当前方法执行完成之后, 要返回到哪个方法等信息, 所以, 必须有一个地方来保持执行的状态。 上面的描述中, “地方”指的当然就是内存区域, 程序运行起来之后, 就是一个动态的过程, 必须合理的划分内存区域, 来存放各种数据。 所以, 在本文中, 将会详细介绍JVM的运行时数据区。
运行时数据区的划分
分为程序计数器、Java堆、Java虚拟机栈、本地方法栈、方法区
- 线程私有的数据区包含程序计数器、虚拟机栈、本地方法栈。
- 全局共享的是Java堆、方法区(包括常量池)和直接内存
- 需要自动内存管理(GC)的区域:Java堆、方法区(不规定但实现了GC)、直接内存
- 可能出现OOM的区域:Java堆、Java虚拟机栈、本地方法栈、方法区、直接内存
- 可能出现StackOverFlow的区域:Java虚拟机栈和本地方法栈
程序计数器
这是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。
如果当前线程正在执行Java方法,指数器记录的是正在执行的虚拟机字节码指令的地址;如果是native方法,指数器为空。
这是唯一一个没有OOM规定的区域。
Java虚拟机栈
虚拟机栈描述的是Java方法执行的内存模型:每个方法(不包括native方法)在执行时都会创建一个栈帧,用于存储局部变量,操作数栈,动态链接,方法出口等信息。每一个方法从调用到执行结束的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
在Java虚拟机规范中,对该区域内存规定了两种异常状况:
- StackOverFlowError:当线程请求栈深度超出虚拟机栈所允许的深度时抛出
- OutOfMemoryError:当Java虚拟机动态扩展到无法申请足够内存时抛出
本地方法栈
本地方法栈则为虚拟机使用到的Native方法提供内存空间。有些虚拟机的实现直接把本地方法栈和虚拟机栈合二为一,比如非常典型的Sun HotSpot虚拟机。
和虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
栈帧
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。
它被用于存储数据和部分过程结果的数据结构,同时也被用来处理动态链接、方法返回值和异常分派。
在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。
对于执行引擎来讲,活动线程中,只有虚拟机栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。执行引用所运行的所有字节码指令都只针对当前栈帧进行操作。
一个完整的栈帧包括:局部变量表、操作数栈、动态连接信息、方法正常完成和异常完成信息
- 局部变量表
是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。其大小以slot为最小单位(32位)。在Java程序编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量。它存放了编译期可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double),对象引用,以及returnAddress类型(指向了一条字节码指令地址)
局部变量表用于方法间参数传递,以及方法执行过程中存储基础数据类型的值和对象的引用。 - 操作数栈
操作数栈也常被称为操作栈,它是一个后入先出栈,由若干个Entry组成。同局部变量表一样,操作数栈的最大深度也是编译的时候被写入到方法表的Code属性的max_stacks数据项中。
在方法执行过程中,栈帧用于存储计算参数和计算结果;在方法调用时,操作数栈也用来准备调用方法的参数以及接收方法返回结果。
举例
Java堆
- 全局共享
- 通常是Java虚拟机中最大的一片内存区域
- 是Java对象的存储区域
- 需要实现自动内存管理,即GC,但不限制实现方式
- 可能出现OOM异常
Java堆、栈的关联过程:两种方式
方式一的优点是访问速度更快,因为方式二需要两次指针定位。方式二的优点是,Java堆中的对象经常会变化(GC),此时只需要修改句柄池中的引用即可,比较方便。目前第一种方式比较流行。
方法区和运行时常量池
- 全局共享
- 作用是存储类的结构信息
- JVMS不要求进行内存管理,但商用Java虚拟机都实现了自动内存管理
- 运行时常量区是方法区的一部分,作用是存储Java类文件常量池中的符号信息
- 可能出现OOM异常
直接内存
- 并非JVMS定义的标准内存区域
- 随JDK1.4加入的NIO引入,目的是避免在Java堆和Native堆中来回复制数据带来的性能损耗
- 全局共享
- 能被自动管理,但检测手段比较简陋
- 可能出现OOM异常
2. 对象判定和回收算法
可回收对象的判定方法
1. 引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,引用计数器的值加1;引用失效时,值减1;计数器为0的对象可以被回收。
缺点:循环引用
OC语言的解决方式:强引用和弱引用。弱引用不会增加引用计数。
2. 可达性分析算法
由于引用计数算法的缺陷,Java虚拟机通常使用这种判定方法。
通过一系列称为“GC Root”的对象作为起始点,从这些起点向下搜索,搜索路线称为“引用链”,如果一个对象到GC Roots没有任何引用链,则判定不可用。
Java中GC Roots:
- 虚拟机栈中(本地变量表中)引用的对象
- 方法区的类静态属性引用的对象
- 方法区中的常量引用的对象
- 本地方法栈中JNI引用的对象
垃圾收集算法
1. 标记-清除算法
首先标记出所有需要回收的对象,标记完成后统一清除。
缺陷:产生大量不连续的内存碎片
2. 复制算法
将可用内存分为相等的两块,每次只使用其中一块,当一块内存用完了,就将还活着的对象复制到另一块内存中,再把这块内存中的所有对象清除掉。
缺陷:将内存缩小了一半、复制开销
改进:不一定需要分为大小相等的两块,可以适当提高可用内存比例。
3. 标记-整理算法
标记过程与“标记-清理算法”一样,然后让所有存活对象向一端移动,最后清理掉边界以外的对象。
缺陷:系统停顿时间更长(移动消耗)
4. 分代算法
当今商用Java虚拟机共同采用的算法。
根据对象存活周期的不同将内存划分为几块。一般把Java堆分为新生代和老年代,然后在不同内存区域采用不同的垃圾收集算法。比如新生代,会产生大量垃圾对象,适合采用复制算法(需要复制的对象较少),而老年代可以采用标记-清理或者标记-整理算法。
HotPot虚拟机的算法实现
GC Roots枚举
安全点和安全区
Android内存优化
1. Android内存管理机制
Android系统内存分配和回收
- 一个APP通常就是一个进程对应一个虚拟机
- GC只有在Heap剩余空间不够时才触发垃圾回收
- GC触发时,所有线程都会被暂停(可能导致内存抖动)
APP内存限制机制
- 不同设备分配的内存限制不同
- 吃内存大户:图片
切换应用时后台APP清理机制
- APP切换的LRU cache:最近使用的APP最不可能被清理
- onTrimMemory()回调
监控内存的方法
- Android方法
- AS monitor工具
2. Android内存优化方法
数据结构优化
- 字符串拼接使用StringBuilder:时间和空间性能都极大优于String
- ArrayMap、SparseMap替换HashMap:时间和空间优化
- 内存抖动:重复大量申请对象
- 再小的class会耗费0.5kb
- HashMap一个entry需要额外占用32b
对象复用
- 复用系统自带的资源
- listview、GridView复用
3.避免在onDraw中创建对象(因为onDraw经常会调用,如果创建对象耗时、耗内存,会造成卡顿)
避免内存泄漏
内存泄漏会导致可用内存越来越少,频繁造成GC
- 单例模式使用了Activity的context(单例对象常驻内存),应该使用Application的context
- 静态变量引用Activity、view等对象没有及时释放(静态变量常驻内存)
- 非静态内部类和匿名内部类会持有外部类(Activity)对象,因此内部类里不能有静态引用或耗时任务。否则不能用这两种内部类。
- Handler:message可能等待很长时间,在处理之前,message都持有handler引用,而handler持有Activity引用。导致泄漏。解决方式是将handler声明为静态,并将Activity的弱引用传给它。或者在Activity结束时同时移除message。
- 资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。参考http://waylenw.github.io/Android/android-bitmap-memory-yh/
3. OOM优化
当APP申请的内存空间大于系统为APP分配的空间时会出现OOM
- 调整图像大小后再放入内存
- 采用强引用+软引用2级缓存,提高加载性能
- 及时回收图像
- 不要创建过多的静态变量
参考文章
- Java运行时数据区
- Java内存机制以及Android内存优化
- Android 优化二 Java内存分配机制及内存泄漏
- Android内存优化及内存机制学习
- Java内存以及回收机制
- java 内存溢出问题以及 java内存简单机制
- Java 内存区域和GC机制以及JVM(Java虚拟机)优化大全和案例实战
- Android内存回收机制以及适配
- Java垃圾回收机制以及内存泄露
- Java垃圾回收机制以及内存泄漏
- Java垃圾回收机制以及内存泄露
- Java垃圾回收机制以及内存泄漏
- JAVA内存模型以及垃圾回收机制
- Android——OOM以及内存优化
- android 内存管理以及优化 粗略方案
- Java中的堆栈机制以及堆内存和栈内存
- Java内存回收、泄漏以及性能优化
- android 内存优化以及性能优化相关问题
- Android&java优化---(2)---java内存
- 简明Python基础教程一
- app打包上架
- linux 自动获取FTP服务器文件shell
- Linux系统--常用压缩/解压缩命令总结
- arm芯片,远程空中升级程序技术方案
- Java内存机制以及Android内存优化
- [HDU4010]Query on The Trees-动态树LCT(Link Cut Tree)
- -JAVA-集合(一)
- Github入门教程
- ZOJ3704-I am Nexus Master!
- 蓝桥杯——数字排列(dfs)
- 数据库的导出与导入
- Android odex反编译为dex
- linux内核编程(hello world示例程序)