Android内存、性能是程序永恒的话题

内存、性能是程序永恒的话题,实际开发中关于卡顿、OOM也经常是打不完的两只老虎,关于卡顿、OOM的定位方法和工具比较多,这篇文章也不打算赘述了,本章主要是来整理一下JVM的内存模型以及Java对象的生与死。

生存空间(内存区域)

Java程序运行在JVM之上,如果Java对象是一个有血有肉的生灵,那么它生存环境是怎样的呢?很多人把Java内存分为堆内存(Heap)和栈内存(Stack),实际上这种划分比较出粗糙和片面。比较细致的划分是这样的:

分为程程计数器、虚拟机栈、本地方法栈、方法区和堆。

程序计数器

理解程序计数器之前,我们先来理解一下线程的并行:

感官上两条线程是同时执行的,但是在一个CPU上实际上是切换轮流执行的,在同一时刻,CPU不会同时执行一个线程,线程的“并行”通过CPU的高速切换来实现的。

现在的问题是:线程切换回来之后是如何确定当前线程之前执行的位置和状态?
答案是:使用程序计数器。每一条线程需要一个独立的程序计数器来记录线程执行的状态,各个线程之间的计数器互不影响,所以程序计数器是线程私有的内存区域。所以说线程越多开销越大。

Java 虚拟机栈


拟机栈也是线程私有的内存区域,用于存储方法执行过程中的局部变量、操作数栈、方法出入口等信息。线程执行每一个方法都会创建一个栈帧,栈帧中就包含了局
部变量表、操作数栈、方法出入口等信息,局部变量表存放基本类型的临时变量,包含boolean、byte、char、short、int、float、
long、doubble和对象应用类型(reference,对象地址)。例如下面一段代码:

假如某线程执行方法a(),那么该线程的栈内存大概是这样的:

假如方法执行到15行,方法b()的栈帧创建并入栈:

执行完15行到16行,方法b的栈帧出栈:

Java虚拟机栈,就是我们常说的栈内存,如果线程请求的深度超过虚拟机运行的深度就会抛出 StackOverflowError 的异常。

本地方法栈

本地方法栈和虚拟机栈是类似的,虚拟机栈是为虚拟机执行 Java 代码服务,本地方法栈是为虚拟机使用Native 方法服务。在 HotSpot 虚拟机中本地方法栈也会抛出 StackOverflowError 的异常。

对象栖息之地-Java堆

堆内存,大部分人都比较熟悉了,Java 堆是虚拟机中站内存最大的一块区域,是所有对象实例的土壤。堆内存是线程共享的,所有线程产生的对象都要在这块区域中划分内存。

Java 堆是垃圾回收器主要管理的区域,又叫 “GC堆”,从垃圾回收器的角度来看 Java 堆又分为新生代和老生代(和垃圾回收器的分代算法相关,见:《《 Java 对象之死》);
从线程共享的情况来看,Java 堆还可能划分为每个线程划分一个下的内存区域作为线程使用的缓存区域(Thread Local Allocation
Buffer,简称TLAB,后面会进一步说明)。无论怎么划分,当堆中没有足够的空间来存放新的实例时就会抛出
OutOfMemoryError(OOM) 异常。

方法区

和 Java 堆一样,也是线程共享的,这部分内存用于存储类信息、常量、静态变量和即时编译的代码数据。

类型信息,指的是类型和其指针的对应关系,在创建和访问对象的时候得到,用于查找区分类型。例如有两个类,class A 和 class B ,假如这两个类都加载了,那么方法区大概是这样记录的:

常量中还有一部分叫做运行时常量池,这部分并不是在编译和加载期间产生的,而是运行期间产生的,例如:

String a = "abc";
String b = "def";
String c = a+b;

上述代码产生的 “abc” 和 “def” 会被存放到运行时常量池中。方法区的内存使用超过限制会抛出 StackOverflowError 的异常。

对象的“出生”

前面介绍了对象的生存环境:内存的区域和各个区域的作用。接下来说说对象的“出生”,一个 new 关键字到底包含了那些“不为人知”的过程?

当程序执行遇到一个 new 关键字之后,首先会去方法区参赛定位到这个类符合的引用,查询到是什么类之后再去检查这个类有没有被加载,如果没有执行类加载过程,类加载过程也是一个比较复杂的过程,这里不展开论述。

在 类加载完成(或者已经加载过)之后,接下来就开始为新的对象分配内存了,为对象分配内存一般有两种方式:“指针碰撞”和“空闲列表”。如果 Java 堆中正在使用的内存和空闲内存分别都是连续的规整的,中间临界点存放一个指针作为分界标识,为新对象分配内存的时候该指针移动和这个对象大小相等的一段距 离就行了,所以叫“指针碰撞”。如果 Java 堆中正在使用的内存和空闲内存不是连续的,那么就没有办法是用指针碰撞这种方式分配内存了,虚拟机就必须维护一个列表来记录那些内存是空闲的,在分配内存 的时候就冲空闲列表中找一份足够大的空间类分配给对象,这种方式称为“空闲列表”。是用“指正碰撞”还是“空闲列表”取决 Java 堆内存是否规整,而 Java 堆内存是否规整取决于垃圾回收器使用的回收算法(参考《 Java 对象之死》)是否带有压缩功能。

前 面提到 Java 堆内存是线程共享的,多线程同时在堆内存中分配内存,就要保证内存划分的原子行。如何保证内存分配的线程安全?一般有两种方案,第一种方案就是实用同步控 制处理,第二种实用 Thread Local Allocation Buffer(TLAB)方式。第一种用多说了很好理解,不用过多解释。TLAB,即线程本地缓冲,就是预先为每个线程分配一个 TLAB ,当线程需要使用内存的时候就在自己的 TLAB 上分配就好了。

内存分配玩之后需要对对象进行必要的设置,例如对象类型信息、元数据
对象哈希码、GC年龄等。

对象长啥样子

通过上面的介绍我们知道对象的“出生”过程了,对象“长啥样”呢?对象在内存中可以分为3个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

象头包括哈希码、GC代年龄、锁状态、线程持有锁、偏向线程ID、时间戳等,另外还包含类型指针。类型指针的作用是JVM使用这个类型指针来查找这个对象
是属于那个类的。实例数据部分才是真正存储对象信息那部分。对齐补充部分不是必然存在的,仅仅用于站位,因为 HotSpot VM
要求对象的大小必须是8字节的整数倍,对象头的长度已经是8字节的整数倍,实例数据大小不固定,所以使用Padding部分来填充。

和它握手

对象已经创建了,并且已经知道它的大概模样,如何访问它呢。在 HotSpot 中使用直接指针访问的。前面介绍过虚拟机栈,方法执行过程中创建的栈帧中局部变量表中存储了方法的局部变量,包含基本类型和引用类型,其中引用类型其实就是一个指向对象内存地址的指针。

推荐:

时间: 2024-10-22 03:31:33

Android内存、性能是程序永恒的话题的相关文章

Android内存性能优化(内部资料总结)

刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成.其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统的gc,很容易就造成了内存的浪费.   Java基于垃圾回收的内存机制 Java的内存管理机制会自动回收无用对象所占用的内存,减轻手工管理内存的负担 1.C/C++: 从申请.使用.释放都需要手工管理 2.Java:无用的对象的内存会被自动回收 什么样的对象是无用的对象 1.Java通过引用来操作一

Android内存性能优化(内部资料总结) eoe转载

刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成.其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统的gc,很容易就造成了内存的浪费. Java基于垃圾回收的内存机制 Java的内存管理机制会自动回收无用对象所占用的内存,减轻手工管理内存的负担 1.C/C++: 从申请.使用.释放都需要手工管理 2.Java:无用的对象的内存会被自动回收 什么样的对象是无用的对象 1.Java通过引用来操作一个具

Android内存性能优化(内部资料总结) 转

刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成.其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统的gc,很容易就造成了内存的浪费. Java基于垃圾回收的内存机制 Java的内存管理机制会自动回收无用对象所占用的内存,减轻手工管理内存的负担 1.C/C++: 从申请.使用.释放都需要手工管理 2.Java:无用的对象的内存会被自动回收 什么样的对象是无用的对象 1.Java通过引用来操作一个具

android内存优化大全_上

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读.(本文最后我会尽量列出所参考的文章). 内存简介: RAM(random acc

ANDROID内存优化以及原理(大汇总——上)

写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在ANDROID开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读.(本文最后我会尽量列出所参考的文章). 内存简介: RAM(random access memory)随机存取存储器.说白了就是内存. 一般Java在内存分配时会涉及到以下区域: 寄存器(R

Android app性能优化大汇总之内存性能优化

写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上把网上搜集的各种内存零散知识点进行汇总.挑选.简化后整理而成. 所以我将本文定义为一个工具类的文章,如果你在Android开发中遇到关于内存问题,或者马上要参加面试,或者就是单纯的学习或复习一下内存相关知识,都欢迎阅读.(本文最后我会尽量列出所参考的文章). 内存简介: RAM(random access memory)随机存取存储器.说白了就是内存. 一般Java在内存分配时会涉及到以下区域: 寄存器(R

[Android Pro] Android应用性能测试之CPU和内存占用(转载)

首先稍做分析一下测试环境:我们知道CPU和内存占用是一个实时变化的状态,而市面上还没有具体的哪款android应用能做到实时监控CPU和内存占用并使用log日志保存.考虑到android的底层框架是基于Linux的平台,所有我们可以通过Linux的资源监控命令来实现对android平台的资源实时监控. 要做到上边的测试环境的实现,需要具备以下几点: 1.被测试的手机具备root权限:因为涉及到底层的linux命令,需要读取或执行相应的文件.至于如何root你的手机,不同型号的手机root的方法不

Android应用性能优化系列视图篇——隐藏在资源图片中的内存杀手

图片加载性能优化永远是Android领域中一个无法绕过的话题,经过数年的发展,涌现了很多成熟的图片加载开源库,比如Fresco.Picasso.UIL等等,使得图片加载不再是一个头疼的问题,并且大幅降低了OOM发生的概率.然而,在图片加载方面我们是否可以就此放松警惕了呢? 开源图片加载库能为我们解决绝大部分有关图片的问题,然而并不是所有! 首先,图片从来源上可以分成三大类:网络图片.手机图片.APK资源图片.网络图片和手机图片都在图片加载库功能的覆盖范围内,基本上不用开发者太操心,但是APK资源

Android 性能篇 -- 带你领略Android内存泄漏的前世今生

基础了解 什么是内存泄漏? 内存泄漏是当程序不再使用到的内存时,释放内存失败而产生了无用的内存消耗.内存泄漏并不是指物理上的内存消失,这里的内存泄漏是指由程序分配的内存但是由于程序逻辑错误而导致程序失去了对该内存的控制,使得内存浪费. Java 内存分配策略 Java 程序运行时的内存分配策略有三种,分别是 静态分配 . 栈式分配 和 堆式分配 ,对应的三种存储策略使用的内存空间主要分别是 静态存储区(也称方法区) . 栈区 和 堆区 . ?? 静态存储区(方法区):主要存放 静态数据 . 全局