Java 虚拟机在执行 java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。可以分为:线程私有,线程共享两个区域。
线程私有的数据区域的生命周期与线程生命周期相同,依赖于用户线程的“启动、结束”而“创建、销毁”。因此线程私有内存与线程紧密相关。
-
程序计数器
一块较小的内存空间,是当前线程所执行的字节码的行号指示器。 每个线程都有一个单独,独立的程序计数器。程序计数器是唯一一个没有 OutOfMemoryError 的区域。 -
虚拟机栈
描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接,方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程。
栈帧随着方法调用而创建,随着方法结束而销毁。无论方法是否正常完成亦或是异常,都被视为方法结束。
- 本地方法栈
本地方法栈与虚拟机栈作用类似,虚拟机栈为虚拟机执行 java 方法服务,而本地方法栈则为虚拟机使用 Native 方法服务。
线程共享区域随着虚拟机的 “启动/关闭” 而 “创建/销毁”
-
堆
Java 堆被所有线程共享的一块内存区域,在虚拟机启动时创建。Java 堆是来及回收器管理的主要区域。
Java 堆可以细分成:新生代与老年代。
Java 对可以处于物理上不连续的内存空间中,只需要逻辑上连续。 -
方法区
方法区忧郁哥别名:Non-Heap(非堆)其用于存被虚拟机加载的类信息,常量,静态变量,即时编译器变异后的代码等。 -
运行时常量池
运行时常量池是方法区的一部分。Class 文件中除了有类的版
本、字段、方法、接口等描述等信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
Java 对从 GC 的角度可以被细分为:新生代和老年代。
-
新生代
一般占据堆的 1/3 空间,用来存放新生的对象。
新生代可分成 Eden,From Survivor,To Survivor 三个区域。
新生代会频繁的出发 GC 进行垃圾回收。
新生代主要采用GC 复制算法。-
Eden
Java 新对象的出生地。当内存不够时,会出发 GC 操作进行垃圾回收。如果新对象占用的内存很大,可直接分配到老年代区域。 -
From Survivor
当 Eden 区满时,还存活的对象将被复制到 From Survivor。当进行复制操作后,此时 From Survivor 就成了 Survivor To。 -
Survivor To
当进行 GC 操作时,存活的对象将从 Eden 和 From Survivor 区域复制到 Survivor To,此时 Survivor To 就变成了了 From Survivor 。
-
-
老年代
老年代主要存放生命周期较长的对象。
老年代内存不会频繁的 GC 执行。
老年代主要采用标记-清除算法。
对象存活的判断
-
引用计数法
给对象添加一个引用计数器,每一次被引用,计数器 +1,当引用失效,计数器 -1,当计数器为 0,该对象将不可能再被使用,即这个对象可以被回收了。 -
可达性分析算法
通过一些列 “GC roots” 的对象作为起始点,从这个节点开始向下搜索,如果一个对象到 GC roots 没有任何应用链相连,则证明对象不可达,即对象不可用。
垃圾回收算法
- GC 复制算法:新生代
- Eden 与 From Survivor 区域中还存活的对象(年龄达到存活标准的对象)复制到 Survivor To 区域,同时将对象的年龄 +1。如果对象的年龄达到了老年代的标准,就将存活的对象复制到老年代。
- 清空 Eden 和 From Survivor。
- 将 From Survivor 与 Survivor To 的名称互换。原先的 From Survivor (内存被清除,内存为空)变成 Survivor To,而 Survivor To 变成了 From Survivor。
- 标记-清除算法
该算法分为两个阶段:标记和清除。第一步是将内存区域的死亡对象进行标记,然后再对所有被标记的对象进行清除。这个算法会导致内存验证的碎片化。
- 标记-整理算法:老年代
该算法分为三个阶段:标记,移动和清除。第一步是将内存区域的死亡对象进行标记,然后将所有被标记的对象移动到内存的一段,最后再对所有被标记的对象进行清除。