内存、性能是程序永恒的话题,实际开发中关于卡顿、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. 汇编函数调用中bp和sp是指什么?

    bp为基址寄存器,一般在函数中用来保存进入函数时的sp的栈顶基址sp是栈顶指针,它每次指向栈顶.每次子函数调用时,系统在开始时都会保存这个两个指针并在函数结束时恢复sp和bp的值.像下面这样:在函数进 ...

  2. <算法竞赛入门经典> 第8章 贪心+递归+分治总结

    虽然都是算法基础,不过做了之后还是感觉有长进的,前期基础不打好后面学得很艰难的,现在才慢慢明白这个道理. 闲话少说,上VOJ上的专题训练吧:http://acm.hust.edu.cn/vjudge/ ...

  3. 【Linux安全】系统资源监控与进程终止

    linux系统允许多用户同时操作,当用户量非常大且占用系统资源非常严重的时候, 管理员想要分析一下资源的占用情况,而在linux中有没有类似于windows系统的 资源管理器一样的工具呢,答案是肯定的 ...

  4. Android开发之BroadcastReceiver的使用

    1.静态注册. 在manifest中注册. <receiver android:name="com.exce.learnbroadcastreceiver.MyReceiver&quo ...

  5. Hibernate级联操作

    cascade属性的可能值有 all: 所有情况下均进行关联操作,即save-update和delete. none: 所有情况下均不进行关联操作.这是默认值. save-update: 在执行sav ...

  6. 函数page_rec_get_next_const

    /************************************************************//** Gets the pointer to the next recor ...

  7. 从头开始编写一个Orchard网上商店模块(2) - 配置您的Orchard开发环境

    原文地址:http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-par ...

  8. ASP.NET MVC3学习心得-----表单和HTML辅助方法

    5.1表单的使用 5.1.1  action和method的特性 表单是包含输入元素的容器,包含按钮.复选框.文本框等元素,表单的这些输入元素使得用户能够向页面中输入信息,并把输入信息提交给服务器.A ...

  9. Oracle 新增表空间文件

    ALTER TABLESPACE users ADD DATAFILE 'D:/oracle/oradata/orcl/users.dbf' SIZE 500M AUTOEXTEND ON NEXT ...

  10. c++ 弧度值与角度值的转换

    Rad_to_deg --- 弧度_到_角度 的 比率Rad_to_deg = 45.0 / atan(1.0): 弧度值到角度值的转换 用 角度 = 弧度值* Rad_to_deg 角度值到弧度值的 ...