“Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有的区域则是依赖用户线程的启动和结束而建立和销毁。”
Java 虚拟机运行时的数据区:

补充:
线程隔离的数据区:
线程共享的数据区:
程序计数器(Program Counter Register)
程序计数器 是 用于存放下一条(Java 虚拟机)字节码指令所在单元的地址 的地方,该区域所在的内存 属于 “线程私有”内存。
如果 线程 正在执行的是一个 Java 方法,这个计数器的值为正在执行的 (Java 虚拟机)字节码指令的地址;如果 线程 正在执行的是本地(Native)方法,则这个计数器的值为空。
虚拟机栈(VM Stack)
虚拟机栈 是 用于存放 栈帧(Stack Frame) 的地方,每当一个(Java)方法被执行时,Java 虚拟机 就会创建一个 栈帧,栈帧 存储着 局部变量表、操作数栈、动态连接、方法出口等信息,一个(Java)方法 从 调用 到 执行完毕 的过程 对应着 栈帧 入栈 到 出栈 的过程,该区域所在的内存 属于 “线程私有” 内存。
本地方法栈(Native Method Stack)
本地方法栈 的功能与 虚拟机栈 相似,唯一不同的是 虚拟机栈 服务于 虚拟机执行 Java 方法,本地方法栈 服务于 虚拟机执行本地方法,该区域所在的内存 属于 “线程私有” 内存。
堆(Heap)
堆 是 用于存放(Java)对象实例 的地方,该区域所在的内存 属于 “线程共享” 内存。
堆 是 垃圾收集器 管理的内存区域,因此也被称为 “GC堆”。
堆的大小 可以通过 (Java 虚拟机)参数 -Xmx 和 -Xms 进行设定。
方法区(Method Area)
方法区 是用于存放 被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据 的地方,该区域所在内存 属于 “线程共享” 内存。
扩展:
运行时常量池(Runtime Constant Pool)
运行时常量池 是 方法区 的一部分,用于存放 编译期生成的各种 字面量 与 符号引用。
直接内存(Direct Memory)
直接内存 并不属于 Java 内存区域,但这部分内存也被频繁地使用到,例如:通过 Native 函数库直接分配 堆外内存,减少数据传输损耗,提高性能。
直接内存 只受本机内存限制。
在 Java 语言中,创建一个对象仅仅需要一个关键字 new ,但在 Java 虚拟机 中,创建一个对象则需要经过以下几个步骤。
1、当 Java 虚拟机 遇到一条字节码 new 指令时,首先会去检查 这个指令的参数 是否能在 常量池 中定位到 一个类的符号引用,并且检查这个 符号引用代表的类 是否 已被加载、解析和初始化过,如果没有就先执行 该类的类加载过程。
2、在类加载检查通过后,虚拟机 通过 “指针碰撞”(或 “空闲列表”)分配方式 在 堆 中为 该对象分配内存空间。
3、内存空间分配完成之后,虚拟机 必须将 分配到的内存空间(但不包括对象头)都初始化为 零值。
4、接下来,虚拟机 还将对 对象 进行必要的设置,如:该对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码(延后至 Object:hashCode() 的调用 才进行计算)、对象的 GC 分代年龄等,这些信息均存放于 对象的对象头中。
5、至此,从虚拟机的角度来看,一个新的对象已经诞生,但从 Java 程序的角度来看,对象的创建才刚刚开始(构造函数还没有执行),接下来即是 执行构造函数(在虚拟机中体现为 方法)完成对 对象的初始化,正式完成对 对象的构造。
对象 在 堆 中的内存布局可以划分为三个部分:
Java 程序在使用对象时,会通过 栈 上的 reference 数据来操作 堆 中的具体对象,而 reference(引用) 访问定位 堆 中的对象可分两种方式:
句柄访问
堆 中划分出一块内存作为句柄池(句柄包含 对象实例数据地址 与 类型数据地址),reference 中存储的就是 对象的句柄地址。
优势:reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针。

直接指针访问
reference 中直接存储 对象地址。

优势:相较于句柄方式 节省了一次指针定位的时间开销,提高了访问速度。
强引用(Strongly Reference)
指 在程序代码中的引用赋值( Object obj = new Object() ),只要 强引用关系 还存在,垃圾收集器 就永远不会回收掉 被引用的对象。
软引用(Soft Reference)
指 一些还有用、但非必须的对象,在系统将要发生 内存溢出异常 前,垃圾收集器 会将这些对象进行回收。
JDK1.2 后,提供 SoftReference 类 实现 软引用。
弱引用(Weak Reference)
指 一些非必须的对象,当 垃圾收集器 开始工作时,无论当前内存是否充足,都会将这些对象进行回收。
JDK1.2 后,提供 WeakReference 类 实现 弱引用。
虚引用(Phantom Reference,又称 “幽灵引用” 或 “幻影引用”)
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例,它存在的唯一目的只是:为了能在这个对象被 垃圾收集器 回收时收到一个系统通知。
JDK1.2 后,提供 PhantomReference 类 实现 虚引用。
当一个对象 “死亡” 时,GC(Garbage Collection,垃圾收集)会将该对象清除 并 回收分配给该对象的内存空间。那么,GC 怎么判断一个对象是否 “死亡” 了呢?
GC 判断一个对象是否 “死亡” 通常有以下两种算法:
引用计数算法
在 对象 中添加一个 引用计数器,每当有一个地方引用它时,计数器值加一;每当有一个地方的引用失效时,计数器值减一;任何时刻计数器值为 零 的对象就是 “已死亡” 的对象(即 该对象不可能再被使用)。
可达性分析算法
以 “GC Roots”(根对象)作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径被称为 “引用链(Reference Chain)”,如果 某个对象 到 “GC Roots” 间没有任何 引用链 相连(或者 用图论的话来说就是 从 “GC Roots” 到 这个对象不可达)时,该对象 “已死亡”(即 该对象不可能再被使用)。
扩展:Java 中的 “GC Roots”

补充:
当一个对象在 可达性分析算法 中判定为 不可达对象时,该对象会进入 “缓刑” 阶段:检测该对象是否有必要执行 finalize() 方法。
如果对象没有覆盖 finalize() 方法(或 finalize() 方法已经被虚拟机调用过),则 虚拟机 认为该对象 没必要执行 finalize() 方法,真正宣布其 “死亡”;
否则,虚拟机 认为该对象 有必要执行 finalize() 方法,并将该对象放入 F-Queue 队列中,然后由虚拟机 自动建立的、低调度优先级 的 Finalizer 线程 去执行它的 finalize() 方法(这里的 “执行” 是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束)。
当对象在 finalize() 方法中 重新与引用链上的任何一个对象建立关联,如:把自己(this 关键字)赋值给某个类变量或者对象的成员变量,即 该对象获得 “重生”(移出 “即将回收” 的集合)。
相反,如果该对象在 finalize() 方法中 没能重新与引用链上的任何一个对象建立关联,该对象也将面临 “死亡”。
分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说和一条经验法则之上。
弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的
强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡
跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数
基于 分代收集理论,垃圾收集器 一致的设计原则为:收集器应该将 Java 堆划分出不同的区域,然后将回收对象依据其年龄(即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
补充:
Java 堆的分代
分代收集
算法分为 “标记” 和 “清除” 两个阶段:首先标记出所有需要回收的对象,在标记完成之后,统一回收掉所有被标记的对象(或统一回收掉所有未被标记的对象)。
缺点:产生碎片空间。

将内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另一块上,然后再把已使用过的内存空间一次性清理掉。
缺点:可用内存减半。

算法的标记过程 与 “标记 - 清除算法” 一致,整理过程则是:将所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
缺点:移动 大对象 可能造成较久的停顿。

以上全部内容均来自于对 周志明老师的 《深入理解 Java 虚拟机》的部分内容的总结。
我的总结于这本书的内容相比,无亦于水滴同大川,真心推荐大家去细读这本书。
以上内容仅用于个人学习。