内存、性能是程序永恒的话题,实际开发中关于卡顿、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 中使用直接指针访问的。前面介绍过虚拟机栈,方法执行过程中创建的栈帧中局部变量表中存储了方法的局部变量,包含基本类型和引用类型,其中引用类型其实就是一个指向对象内存地址的指针。

推荐:

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

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

    eoe上看到的一个很好的文章 摘抄了下来留着自己看看 刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成.其实Java中没有 ...

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

    刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成.其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统 ...

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

    刚入门的童鞋肯能都会有一个疑问,Java不是有虚拟机了么,内存会自动化管理,我们就不必要手动的释放资源了,反正系统会给我们完成.其实Java中没有指针的概念,但是指针的使用方式依然存在,一味的依赖系统 ...

  4. Android应用性能优化(转)

    人类大脑与眼睛对一个画面的连贯性感知其实是有一个界限的,譬如我们看电影会觉得画面很自然连贯(帧率为24fps),用手机当然也需要感知屏幕操作的连贯性(尤其是动画过度),所以Android索性就把达到这 ...

  5. ANDROID内存优化(大汇总——上)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  6. 浅谈Android应用性能之内存

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...

  7. android app性能优化大汇总(内存性能优化)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

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

    图片加载性能优化永远是Android领域中一个无法绕过的话题,经过数年的发展,涌现了很多成熟的图片加载开源库,比如Fresco.Picasso.UIL等等,使得图片加载不再是一个头疼的问题,并且大幅降 ...

  9. android 内存优化以及性能优化相关问题

    最近做一个android 的应用程序 总是出现内存高 和cpu高的问题困扰了好多天. 下面为自己从网上总结的和自己找到的问题. 1. WebView  控件: 使用了 WebView 控件一定要注意清 ...

随机推荐

  1. ANDROID_MARS学习笔记_S01原始版_019_SERVICE之Transact

    一.代码1.xml(1)activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/ ...

  2. 展讯CEO:低毛利生存 由中低端转向高端

    最近一两年来,芯片市场的热闹有从细分.垂直的圈子向整个大社会场景发酵的迹象. 备受各界关注的高通发垄断案,国家大基金的成立,以及展讯.锐迪科等私有化等等,都意味着这个行业的热度在快速上升.这里面既有芯 ...

  3. jquery parent()和parents()区别

    parent(exp) 取得一个包含着所有匹配元素的唯一父元素的元素集合. 你可以使用可选的表达式来筛选. 查找段落的父元素中每个类名为selected的父元素. HTML 代码: <div&g ...

  4. ActionBar官方教程(11)自定义ActionBar的样式(含重要的样式属性表及练习示例)

    Styling the Action Bar If you want to implement a visual design that represents your app's brand, th ...

  5. Getting Started Tutorial

    Getting Started Tutorial The topics contained in this section are intended to give you quick exposur ...

  6. 更换手机号或者更换手机后QQ设备锁的设置问题

    更换手机号 一步到位,更改密保手机号,OK了 更换手机 老卡插入 登录QQ,OK了 更换手机号和手机 老卡插入新手机 登录QQ 新卡插入新手机 更改密保手机号,OK了

  7. ☀【JS】eval

    避免使用 eval <!doctype html> <html lang="zh-CN"> <head> <meta charset=&q ...

  8. Xcode7工程改名

    0: 先把原工程压缩个zip包备份一份. 1:在Project navigator中, 选择旧工程名字, 再次点击, 修改. 会弹出提示框. 直接点击确认. (Xcode7.1.1中经常导致Xcode ...

  9. Apache搭建多个站点

    如何用Apache搭建的网站系统上运行多个站点呢?最平常的大概有3种方法. 第一种:单IP不同端口 第二种:多IP同端口(独立IP的虚拟空间) 第三种:域名绑定根目录的方式(共享IP的虚拟空间) 下面 ...

  10. ZOJ3732 Graph Reconstruction Havel-Hakimi定理

    分析: 给定一个非负整数序列{dn},若存在一个无向图使得图中各点的度与此序列一一对应,则称此序列可图化. 进一步,若图为简单图,则称此序列可简单图化 (来自百度百科) 可简单图化的判定可以用Have ...