JVM部分数据整理
一、运行时数据区域
Java内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【Java堆、方法区】、直接内存(不受JVM GC管理)
1、线程私有部分
1.1、程序计数器
程序计数器(Program Counter Register)一块较小的内存空间,是当前线程所执行的字节码的行号指示器。
1.2、虚拟机栈
生命周期与线程相同。Java虚拟机栈(Java Virtual Machine Stacks)是描述Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
1.3、本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,区别是虚拟机栈为执行Java方法服务,而本地方法栈则为Native方法服务。HotSpot VM将本地方法栈和虚拟机栈合二为一。
2、线程共享
2.1、堆区(Heap)
创建的对象和数组都保存在Java堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。现代VM基本都采用分代收集算法,所以Java堆中还可以细分为:新生代(Eden区、From Survivor区和To Survivor区)和老年代。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
2.2、方法区/永久代(元空间)
永久代(Permanent Generation)用于存储被JVM加载的类信息、常量、静态变量、JIT编译后的代码等数据, HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样HotSpot的垃圾收集器就可以像管理Java堆一样管理这部分内存, 而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小);
在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所代替。区别在于:元空间并不在虚拟机中,而是使用本地内存。
2.3、运行时常量池
运行时常量池(Runtime Constant Pool)是属于方法区的一部分。Class文件中除了有类的版 本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加 载后存放到方法区的运行时常量池中。 Java虚拟机对Class文件的每一部分(自然也包括常量 池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会 被虚拟机认可、装载和执行。
2.4、直接内存
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。
二、垃圾回收与算法
1、如何确定垃圾?
1.1、引用计数法
通过引用计数(Reference Counting)来判断一个对象是否可以回收
1.2、可达性分析
Java通过可达性分析(Reachability Analysis)来判定对象是否存活的。通过一系列的“GC Roots”对象作为起点搜索。如果一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。【ps:不可用对象(不可达对象)不等价于可回收对象,不可用对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收】
2、标记清除算法
“标记-清除”(Mark-Sweep)算法,算法分为“标记”和“清除”两个阶段;标记阶段标记处所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
不足之处:
- 效率
- 空间问题
3、复制算法
“复制”(Copying)算法,将内存按容量划分为大小相等的两块,每次只适用其中的一块。当这一块的内存用完了,就将还存在着的对象复制到另外一块上面,然后再把已使用过的内存空间一次性清理掉。
4、标记整理算法
“标记-整理”(Mark-Compact)算法,标记过程与Mark-Sweep算法一样,后续步骤是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
5、分代收集算法
“分代收集”(Generational Collection)算法,是根据对象存活周期的不同将内存划分为几块。根据各个年代的特点采用最适当的收集算法。一般在新生代中采用复制算法,老年代中采用“标记-清除”或者“标记-整理”算法进行回收
三、Java中四种引用类型
1、强引用(Strong Reference)
普遍存在于程序中,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
2、软引用(Soft Reference)
用来描述一些还有用但并非必需的对象,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。
3、弱引用(Weak Reference)
也是用来描述非必需对象的,对于弱引用的对象来说,只要垃圾收集器一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存。
4、虚引用(Phantom Reference)
被称为幽灵引用或者幻影引用,必须和引用队列联合使用,唯一目的就是能在这个对象被收集器回收时收到一个系统通知。(跟踪对象被垃圾回收的状态)
四、垃圾收集器
垃圾收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。Java堆内存被划分为新生代和老年代两部分,新生代主要使用 Copying 和 Mark-Sweep 垃圾回收算法;老年代主要使用 Mark-Compact 垃圾回收算法。
1、Serial收集器
是一个单线程的收集器(新生代采用 Copying 算法,老年代采用 Mark-Compact 算法),在进行垃圾回收的时候,必须暂停其他所有的工作线程,直到它收集结束。这项工作由虚拟机在后台自动发起和自动完成。
2、ParNew 垃圾收集器
是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集室外,其余的行为和Serial 收集器完全一样。
3、Parallel Scavenge 收集器
属于新生代收集器,也使用复制算法,同样是使用多线程,他的关注点是程序达到一个可控制的吞吐量(Throughput),被称为“吞吐量优先”的收集器。
4、Serial Old 收集器
是Serial收集器的老年代版本,同样是单线程收集器,使用“Mark-Compact”算法。
5、Parallel Old 收集器
是 Parallel Scavenge 收集器的老年代版本,使用多线程和“Mark-Compact”算法。
6、CMS 收集器
Concurrent mark sweep(CMS)收集器是一种以获取最短回收停顿时间为目标的收集器,基于“Mark-sweep”算法,
分为以下四个步骤:
-
初始标记
只是标记以下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
-
并发标记
进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
-
重新标记
修正在并发标记期间因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录。
-
并发清除
清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。
7、G1收集器
G1收集器是一款面向服务端应用的垃圾收集器
具备的特点:
- 并行与并发
- 分代收集
- 空间整合
- 可预测的停顿
使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region)
G1收集器的运作大致流程:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收