[关闭]
@TryLoveCatch 2018-10-19T06:50:41.000000Z 字数 4831 阅读 1663

Android基础之Java内存模型

android android基础


什么是JVM内存

Java源代码文件(.java)会被Java编译器编译为字节码文件(.class),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。

JVM在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。


了解清楚JVM的内存结构会更有助于我们理解Java的内存模型。

我们可以把上图的运行时数据区分为线程私有共享数据区两大类。

本地区(native area)

这个区域有两种异常情况:
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度
OutOfMemoryError:虚拟机栈扩展到无法申请足够的内存时

栈帧

每一个栈帧都包括了局部变量表操作数栈动态连接方法返回地址和一些额外的附加信息。
在编译代码的时候,栈帧中需要多大的局部变量表多深的操作数栈都已经完全确定了,并且写入到了方法表Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。

一个线程中的方法调用链可能会很长,很多方法都同时处理执行状态。对于执行引擎来讲,活动线程中,只有虚拟机栈顶栈帧才是有效的,称为当前栈帧(Current Stack Frame),这个栈帧所关联的方法称为当前方法(Current Method)。执行引用所运行的所有字节码指令都只针对当前栈帧进行操作。栈帧的概念结构如下图所示:

方法区(method area)

可以这样理解,方法区存的是类的模版。比如说方法,其实就是行为,一个类的行为都是一致的,所以存在方法区;而变量,就是数据,每一个对象的数据都是不一样的,所以存在堆里

方法区有时被称为持久代(PermGen)

堆(heap space)

所有的对象在实例化后的整个运行周期内,都被存放在堆内存中。堆内存又被划分成不同的部分:年轻代(Young Generation Space)老年代(Old Generation Space)
年轻代又可以划分为:伊甸区(Eden)幸存者区域(Survivor Sapce)

总结

整体如下图所示:

例子

  1. import java.text.SimpleDateFormat;
  2. import java.util.Date;
  3. import org.apache.log4j.Logger;
  4. public class HelloWorld {
  5. private static Logger LOGGER = Logger.getLogger(HelloWorld.class.getName());
  6. public void sayHello(String message) {
  7. SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.YYYY");
  8. String today = formatter.format(new Date());
  9. LOGGER.info(today + ": " + message);
  10. }
  11. }

这段程序的数据在内存中的存放如下:

垃圾回收

是在 JVM 启动时创建的,主要用来维护运行时数据,如运行过程中创建的对象和数组都是基于这块内存空间。Java 堆是非常重要的元素,如果我们动态创建的对象没有得到及时回收,持续堆积,最后会导致堆空间被占满,内存溢出。

因此,Java 提供了一种垃圾回收机制,在后台创建一个守护进程。该进程会在内存紧张的时候自动跳出来,把堆空间的垃圾全部进行回收,从而保证程序的正常运行。

也就是说,我们负责创建对象,GC负责来回收,那么如何认定是否是垃圾呢?

哪些是垃圾呢?

引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

可达性分析算法

这种方案是目前主流语言里采用的对象存活性判断方案。基本思路是把所有引用的对象想象成一棵树,从树的根结点 GC Roots 出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。其余的对象则被视为“死亡”的“不可达”对象,或称“垃圾”。

上图中,object5, object6object7 便是不可达对象,视为“死亡状态”,应该被垃圾回收器回收。

在Java语言里,可作为GC Roots对象的包括如下几种:

如何来回收这些垃圾呢?

参考下图,黑色的表示垃圾,灰色表示存活对象,绿色表示空白空间。

标记-清理

结果如下:

这便是 标记-清理 方案,简单方便 ,但是容易产生内存碎片

标记-整理

既然上面的方法会产生内存碎片,那好,我在清理的时候,把所有 存活 对象扎堆到同一个地方,让它们待在一起,这样就没有内存碎片了。

结果如下:

这两种方案适合 存活对象多,垃圾少 的情况,它只需要清理掉少量的垃圾,然后挪动下存活对象就可以了。

复制

这种方法比较粗暴,直接把堆内存分成两部分,一段时间内只允许在其中一块内存上进行分配,当这块内存被分配完后,则执行垃圾回收,把所有 存活 对象全部复制到另一块内存上,当前内存则直接全部清空。

起初时只使用上面部分的内存,直到内存使用完毕,才进行垃圾回收,把所有存活对象搬到下半部分,并把上半部分进行清空。

这种做法不容易产生碎片,也简单粗暴;但是,它意味着你在一段时间内只能使用一部分的内存,超过这部分内存的话就意味着堆内存里频繁的 复制清空。

这种方案适合存活对象少,垃圾多 的情况,这样在复制时就不需要复制多少对象过去,多数垃圾直接被清空处理。

Java中采用那种方法呢?

我们先来回忆一下,一块 Java 堆空间一般分成三部分,这三部分用来存储三类数据:

也就是说,常规的 Java 堆至少包括了 新生代老年代 两块内存区域,而且这两块区域有很明显的特征:

新生代-复制 回收机制

对于新生代区域,由于每次 GC 都会有大量新对象死去,只有少量存活。因此采用 复制 回收算法,GC时把少量的存活对象复制过去即可。

新生代区域分成8:1:1,依次取名为 EdenSurvivor ASurvivor B 区,其中 Eden 意为伊甸园,形容有很多新生对象在里面创建;Survivor区则为幸存者,即经历 GC 后仍然存活下来的对象。

工作原理如下:

  1. 首先,Eden区最大,对外提供堆内存。当 Eden 区快要满了,则进行 Minor GC,把存活对象放入 Survivor A 区,清空 Eden 区;
  2. Eden区被清空后,继续对外提供堆内存;
  3. Eden 区再次被填满,此时对 Eden 区和 Survivor A 区同时进行 Minor GC,把存活对象放入 Survivor B 区,同时清空 Eden 区和Survivor A 区;
  4. Eden区继续对外提供堆内存,并重复上述过程,即在 Eden区填满后,把 Eden 区和某个 Survivor 区的存活对象放到另一个 Survivor 区;
  5. 当某个 Survivor 区被填满,且仍有对象未被复制完毕时,或者某些对象在反复 Survive 15 次左右时,则把这部分剩余对象放到Old 区;
  6. Old 区也被填满时,进行 Major GC,对 Old 区进行垃圾回收。

那么,所谓的 Old 区垃圾回收,或称Major GC,应该如何执行呢?

老年代-标记整理 回收机制

根据上面我们知道,老年代一般存放的是存活时间较久的对象,所以每一次 GC 时,存活对象比较较大,也就是说每次只有少部分对象被回收。

因此,根据不同回收机制的特点,这里选择 存活对象多,垃圾少标记整理 回收机制,仅仅通过少量地移动对象就能清理垃圾,而且不存在内存碎片化。

总结

至此,我们已经了解了 Java 堆内存分代原理,并了解了不同代根据各自特点采用了不同的回收机制,即 新生代 采用 复制回收 机制,老年代采用 标记整理机制。

拾遗一 StackOverflowError & OutOfMemoryError

StackOverflowError

栈溢出抛出java.lang.StackOverflowError错误,出现此种情况是因为方法调用层次太深,请求的栈深度大于虚拟机所允许的深度

  1. public class SOFTest {
  2. public void stackOverFlowMethod(){
  3. stackOverFlowMethod();
  4. }
  5. public static void main(String[] args) {
  6. SOFTest sof = new SOFTest();
  7. sof.stackOverFlowMethod();
  8. }
  9. }

通过递归调用方法,不停的产生栈帧,一直把栈空间堆满,直到抛出异常 :

OutOfMemoryError

参考

JAVA的内存模型及结构
Android面试一天一题(Day 44:实战美团--Java内存模型)
Android中高效的显示图片 - Bitmap的内存模型
Java虚拟机的堆、栈、堆栈如何去理解?
Java-技术之垃圾回收机制
JVM内存模型解析
深入理解Java虚拟机笔记---运行时栈帧结构
深入探究JVM | Java的内存区域解析
Java 虚拟机面试题全面解析
深入理解JVM03--判断对象是否存活(引用计数算法、可达性分析算法,最终判定)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注