Java 内存区域

程序计数器
当前线程所执行字节码的信号指示器;此内存区域是唯一不会有 OOM 的区域
虚拟机栈
生命周期与线程相同;
虚拟机栈描述的是 java 方法执行的内存模型;每个方法执行都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;每个方法从调用直至执行完成的过程就对应着一个栈帧在虚拟键栈中入栈到出栈的过程
很多人吧 java 内存简单分为"栈"、"堆"这样的分法比较粗糙;可能大家只关心与程序最相关的2块;"栈"就是"虚拟机栈";
存储局部变量表
- 存储了熟悉的各种基本类型:
boolean(1位)、int(32位)、long(64位)、float(32位)、double(64位)、char(16位)、byte(8位)、short(16位) - 对象引用 reference类型
java 虚拟机中对这个区域规定了2中异常情况:
- 如果线程请求的
栈深度大于虚拟机所允许的深度;抛出StackOverflowError - 如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存;抛出
OutOfMemoryError
本地方法栈
本地方法栈跟虚拟机栈非常相似;主要的区别是虚拟机栈为虚拟机执行java方法服务、本地方法栈为虚拟机使用到的 Native方法服务
相同的也会抛出StackOverflowError、OutOfMemoryError异常
堆
存放对象实例,几乎所有的对象实例都放在这里分配内测
方法区
用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等
会抛出OutOfMemoryError
运行时常量池是方法区的一部分 会抛出
OutOfMemoryError
直接内存
不是 java 虚拟机的一部分,但是这块内存被频繁的使用也会抛出OutOfMemoryError
jdk1.4后加入了NIO一种基于通道(channel)与缓冲区(buffer)的 I/O方式,可使用 Native 函数库直接分配堆外内存,然后通过一个存储在 java堆中的 DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了 java堆和 native堆中来回复制数据
自动内存管理机制
Q:垃圾收集器在对堆进行回收前,如何判断
对象已死 (即不可能再被任何途径使用的对象)
引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时计数器+1;引用失效计数器-1;任何时刻计数器为0的对象就是不可能再被使用
但是这个针对引用计数算法很难去解决对象之间相互循环引用的问题

可达性分析算法 (虚拟机就是用这个算法)
通过一系列的称为GCRoot的对象作为起点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GCRoot没有任何引用链相链接时,则认为该对象不可用的
Q: 当一个对象到
GCRoot没有任何引用链的时候,这个对象就"非死不可"吗?
可达性分析不可达对象也不是"非死不可",这个时候的他们处于"缓刑"期,要真正宣告一个对象死亡,至少要经历2次标记过程;如果对象"A"在经历可达性分析之后发现没有与 GCRoot 相链接的引用链,那么"A"会被第一次标记并且进行一次筛选,筛选的条件是对象"A"是否有必要执行finalize()方法。"A"没有重写finalize()或者finalize()已经被虚拟机调用过,虚拟机视这2种情况为"没有必要执行"
如果对象"A"被判断有必要执行 finalize()方法,那么"A"将会被放置在一个叫F-Queue队列中,并且稍后由虚拟机去执行,但是这个执行是指虚拟机触发这个方法(A.finalize()),但是虚拟机不会承诺等待它(A.finalize())运行结束。
finlize()方法是对象逃离死亡命运的最后一次机会,稍后 GC 将对F-Queue中进行第二次小规模的标记,如果A想要在finlize中成功拯救自己-->只要重新与引用链的任何一个对象建立关联。比如把自己(this关键字)赋值给某个类的变量或者对象的成员变量;
引用
在JDK1.2之后引用可分为:强引用(Strong Reference)、软引用(soft Reference)、弱引用(weak Reference)、虚引用(Phantom Reference)
- 强引用(
Strong Reference)
比较普遍,类似Object obj = new Object() 这类的引用,只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象 - 软引用(
soft Reference)
用来描述一些还有用但是非必须的对象;
在系统将要发生内存泄露之前,将会把这些对象列进行回收范围之中进行二次回收。如果这次回收还没有足够内存,才会抛出异常 - 弱引用(
weak Reference)
用来描述非必须的对象;
被引用关联的对象只能生存到下一次垃圾收集发生前.当垃圾收集器工作时,无论当前内存是否足够都会回收掉只被弱引用关联的对象 - 虚引用(
Phantom Reference)
用的少基本没用到
垃圾回收算法
标记-清除
首先标记所有需要回收对象,在标记完之后统一回收所有被标记的对象
缺点
- 效率问题,标记、清除的效率都不高
- 空间问题,会产生大量不连续的内存碎片

复制算法 (新生代)
将内存划分相等的2块,每次只使用一块,当一块用完了就将存活的对象移动另一块,然后在把已经使用的那块一次清理掉
缺点:代价是将内存为原来的一半
虚拟机中新生代就是用这种算法,因为新生代中的对象98%都是"朝生夕死",新生代中的内存分配是eden、survivor1、survivor2;默认情况下他们的比例是8:1:1,每次使用 eden 和一个 survivor。回收的时候将 eden 和 survivor 存活的对象复制到另一个 survivor上,最后清理掉 eden 和使用的 survivor
标记整理算法 (老年代)
过程和标记-清除相似,但是后续的步奏是:让所有存活的对象都向一端移动,然后直接清理掉端边界的内存
