运行时数据区
方法区(Method Area)
运行时常量池
元空间(Meta Space)JDK 8 +
字符串常量池
静态变量
类型信息
溢出
-XX: MaxMetaspaceSize
-XX: MetaspaceSize
-XX: MinMetaspaceFreeRatio
虚拟机栈(VM Stack)
本地方法栈(Native Method Stack)
堆(Heap)
溢出(Java.lang.OutOfMemoryError、Java heap space)
可以通过Java内存(映像)分析工具(hprof文件分析工具)打开内存堆转储快照分析
程序计数器(Program Counter Register)
HotSpot不区分虚拟机栈和本地方法栈,同时也不支持栈的动态扩展,栈容量由`-Xss`参数设定,HotSpot的栈溢出抛出的异常全都是StackOverFlowError。
溢出
使用-Xss参数减少栈内存容量。
定义了大量的本地变量,增大此`方法帧`中`本地变量表`的长度。
通过不断建立线程的方式,在HotSpot上也是可以产生内存溢出异常的。
直接内存(Direct Memory)
溢出
使用unsafe直接分配本机内存,模拟内存溢出
由直接内存导致的内存溢出,一个明显的特征是在Heap Dump文件中不会看见有什么明显的异常情况,如果读者发现内存溢出之后产生的Dump文件很小,而程序中又直接或间接使用了DirectMemory(典型的间接使用就是NIO),那就可以考虑重点检查一下直接内存方面的原因了。
垃圾收集器
程序计数器
虚拟机栈
本地方法栈
Java堆
对象已死?
引用计数法
可达性分析算法(Reachability Analysis)
生存与死亡?(两次标记过程)
方法区
回收方法区的废弃常量和不再使用的类型(方法区垃圾收集的“性价比"通常也是比较低的)
对象存活判断算法和垃圾收集算法
对象消亡
“引用计数式垃圾收集”(Reference Counting GC)(直接垃圾收集)
“追踪式垃圾收集”(Tracing GC)(间接垃圾收集)
分代收集理论
弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的
强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡
什么样的对象才被定义为老年代?
收集类型
部分收集(Partial GC)
新生代收集(Minor GC/Young GC)
老年代收集(Major GC/Old GC)
混合收集(Mixed GC)
整堆收集(Full GC)
不同区域安排不同的算法
标记-清除算法(Mark-Sweep)
标记-复制算法
半区复制(Semispace Copying)
Appel式回收
Eden空间(默认占80%)
Survivor From空间(默认占10%)
Survivor To空间(默认占10%)
分配担保(Handle Promotion)
超大对象在Minor GC时无法被移步到Survivor时将被一直划分到老年代
标记-整理算法(移动式)
如果移动存活的对象太多,这将是一笔很大的开销
如果完全不考虑移动和整理存活对象的话,弥散于堆中的存活对象导致的空间碎片化问题就只能依赖更为复杂的内存分配器和内存访问器来解决。
“和稀泥式”解决方案
通常是需要短暂地暂停用户程序来做垃圾收集的
Java堆
新生代(Young Generation)
老年代(Old Generation)
跨代引用假说(Intergenerational Reference Hypothesis)
经典垃圾收集器
Serial收集器
ParNew收集器(退役)
Parallel Scavenge收集器(吞吐量优先收集器)
Serial Old收集器
CMS收集器(标记-清除算法)(关注停顿时间控制)(如今已经被官方声明为不推荐使用)
Garbage First收集器(G1)(关注停顿时间控制)(全功能的垃圾收集器)(CMS的接替者和继承人)
这是最古老,最基础的垃圾收集器,只会使用一个处理器单线程去做垃圾收集,而且它在进行垃圾收集期间,会停掉所有的用户工作线程
是Serial的多线程并行版本
Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。
适合较为关注服务的响应速度,希望系统停顿时间尽可能短,以给用户带来良好的交互体验的场景。
垃圾收集器组合
Parallel Scavenge
Parallel Old
适合注重吞吐量或者处理器资源较为稀缺的场合(该组合已经被官方宣告被G1取代)
低延迟垃圾收集器
重要指标
内存占用(Footprint)
吞吐量(Throughput)
延迟(Latency)
一款优秀的收集器通常最多可以同时达成其中的两项
G1
Shenandoah收集器(致力于要比G1更低延迟的更新一代搜集器,和G1有着一部分共同的代码)
ZGC收集器(具有实验性质的低延迟垃圾收集器)
ZGC和Shenandoah的目标是高度相似的,都希望在尽可能对吞吐量影响不太大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。
选择合适的垃圾收集器
Epsilon收集器
收集器的权衡
应用程序的关注点
吞吐量
如果是数据分析、科学计算类的任务、异步实时性追求不高的后台任务,目标是能尽快算出更多的结果,那吞吐量就是主要关注点
停顿时间
如果是SLA应用、服务影响时间要求高的应用、抢购服务等,那停顿时间直接影响服务质量,严重的甚至会导致事务超时,这样延迟就是主要关注点
内存占用
如果是客户端应用或者嵌入式应用、内存资源紧张的机器,那垃圾收集的内存占用则是不可忽视的
运行应用的基础设施如
硬件规格,要涉及的系统架构是x86-32/64、SPARC还是ARM/Aarch64
处理器的数量多少,分配内存的大小
选择的操作系统是Linux、Solaris 还是Windows等
使用JDK的发行商是什么
版本号是多少
对应哪个版本的《Java虚拟机规范》
虚拟机和垃圾收集器日志
JDK 9之前鱼龙混杂
JDK 9之后
-Xlog[:[selector][:[output][:[decorators][:output-options]]]]
如果客户应用只要运行数分钟甚至数秒,只要Java虚拟机能正确分配内存,在堆耗尽之前就会退出,那显然运行负载极小、没有任何回收行为的Epsilon便是很恰当的选择。
内存分配和策略回收
自动内存管理最根本的目标
自动给对象分配内存
自动回收分配给对象的内存
使用Serial和Serial Old客户端默认收集器组合做测试
对象优先分配在Eden空间,当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
大对象直接进入老年代
-XX: PretenureSizeThreshold
要尽量避免“短命大对象”
长期存活的对象将进入老年代
-XX:MaxTenuringThreshold
动态对象年龄判定
Survivor空间中的一批相同年龄的对象大小的总和是否大于Survivor空间的一半
空间分配担保
冒险
这3个区域随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的(尽管在运行期会由即时编译器进行一些优化,但在基于概念模型的讨论里,大体上可以认为是编译期可知的),因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟随着回收了。
两个区域则有着很显著的不确定性,一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理。
常用参数