一、运行时数据区域
方法区method area、堆heap、虚拟机栈VM stack、本地方法栈native method stack、程序计数器program counter register。
除了上述区域,实际上还有不受java虚拟机管理的直接内存(堆外内存)direct memory
1.分类
从结构上分为:堆、栈
从是否共享分为:线程共享、线程隔离
堆 (同时是线程共有):方法区、堆
栈 (线程隔离的区域):虚拟机栈、本地方法栈、程序计数器
2.各区域详解
程序计数器:当前程序执行的行号指示器
java虚拟机栈:线程私有,生命周期与线程相同。每个方法的执行会创建一个栈帧,用于存储局部变 量表、操作数栈、动态链接、方法出口
本地方法栈:就是native方法的虚拟机栈
java堆:存放对象实例。线程共享。也是GC的主要区域。
方法区:存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。
直接内存(堆外内存):通过native函数库直接操作内存,通过存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。避免了java堆和native堆中来回复制数据。
二、垃圾收集器
java运行时数据分为堆、栈,其中栈随着线程生,随着线程死,内存的申请和回收,跟随线程来走。但是,不同于栈,堆时线程共享的,分配和回收都是动态的,垃圾收集器关注的就是这一块区域。
1.判断对象已死
引用计数法: 对象添加计数器,每有一个引用+1,引用失效-1,当计数为0,则对象已死。但是java中并没有用这个,主要原因在于无法解决循环引用问题。
可达性分析算法:通过一系列GC root作为起点,向下搜索,当一个对象到GC root没有引用链时,此对象不可用。GC root包括:
- 虚拟机栈(栈帧中的本地变量表)引用的对象
- 方法去中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(一般说的Native方法)引用的对象
引用:
- 强引用:Object obj = new Object()即时强引用。在我们的代码中普遍存在,只要存在强引用就不会被垃圾回收。
- 软引用:JDK1.2提供了SoftReference类,来实现软引用。在内存溢出前,将会把这些对象收紧范围进行回收,若未能回收足够内存,则抛出异常。
- 弱引用:JDK1.2提供了WeakReference类,被关联对象只能生存到下一次垃圾回收,一旦发生垃圾回收,就会收回引用。
- 虚引用:JDK1.2提供了PhantomRefernce类,无法通过虚引用取得对象实例,其唯一作用,在对象被回收器回收时,收到一条系统通知。
2.垃圾回收方法
标记清理法:标记-清理 2个阶段,缺点,效率低、会产生大量内存碎片
复制算法:内存分为2半,每次回收完放到另一半里。缺点,浪费内存
标记整理法:标记->向一端整理
分代回收:分成多个代,比如新生代、老年代。每个代用适当的回收算法。
3.收集器
- Serial收集器,单线程收集器,标记整理算法
- ParNew收集器,Serial的多线程版本
- Parallel Scavenge收集器,新生代收集器,复制算法,多线程收集器,停顿时间短
- Serial Old收集器,Serial老年代版本,单线程收集器,标记整理算法
- CMS(Concurrent Mark Sweep)收集器,并发标记清理算法,停顿短
- G1(Garbage First)收集器,并发,分代,标记整理算法,停顿可预测,G1跟踪每个region中垃圾回收的价值,选择回收价值最大的优先回收。通过CardTable,把引用记录到所属region的Remembered Set中,GC时,在GC根节点枚举范围加入Remembered Set即可。
4.内存分配策略
对象主要分配在新生代Eden上,如果启动了本地线程分配缓冲,将按线程有限在TLAB上分配,少数分配在老年代。
对象优先在Eden分配,Eden不够,发起一次Minor GC。如果,GC后,足够放入to Survior,放进去即可;如果不够,那么通过分配担保机制,提前转移到老年代。
大对象直接进入老年代。
长期存活对象,进入老年代。一次Minor GC长一岁,超过MaxTenuringThreshold,则进入老年代。
动态年龄判定:除了年纪超过MaxTenuringThreshold,若survivor中相同年龄的对象,超过survivor空间的一半,也可进入老年代。
分配担保机制:MinorGC前,若老年代可用最大连续空间,大于,新生代对象总和,MinorGC是安全的。
若不安全,则FullGC;
如果安全,那么检查老年代最大可用连续空间,是否大于历次晋升老年代对象的平均大小,
若大于,则Minor GC
若小于,则FullGC。