一、JVM内存区域
1、程序计数器
当前线程执行的字节码的行号指示器
作用:
一个处理器只会执行一个线程,如果这个线程被分配的时间片执行完了(线程被挂起),处理器会切换到另外一个线程执行,当下次轮到执行被挂起的线程(唤醒线程)时,通过记录在程序计数器中的行号指示器即可知道,所以程序计数器的主要作用是记录线程运行时的状态,方便线程被唤醒时能从上一次被挂起时的状态继续执行。
2、java虚拟机栈
每个方法被执行时,java虚拟机都会同步创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口等信息。方法执行完出栈,出栈就相当于清空了数据,这块区域不需要进行 GC。
3、本地方法栈
主要区别在于虚拟机栈为虚拟机执行 Java 方法时服务,而本地方法栈为虚拟机执行本地方法时服务的。这块区域也不需要进行 GC。
4、java堆
java堆是被所有线程共享的一块内存区域。目的:存放对象实例,所有对象实例以及数组都应当在堆上分配。 GC 发生区域
5、方法区
也是各个线程共享的内存区域,用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
二、如何识别垃圾
方法:
1、引用计数器
对象被引用一次,记录该对象被引用一次。如果没有被引用(引用次数为0),则该对象可回收。
问题:不能解决循环引用的问题
2、可达性分析算法
可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,(这样通过 GC Root 串成的一条线就叫引用链),直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为「垃圾」,会被 GC 回收。
当对象不可达(可以回收时)不一定会被直接回收。
当发生GC时,会先判断该对象是否执行了finalize方法,如果未执行,会先执行finalize方法,将目前的GC Roots与当前对象关联,之后再进行一次可达性分析,如果不可达,则回收,若可达,不回收。
注意:
每个对象只有一次执行finalize方法的机会。
-
*哪些对象可以作为GC Roots?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
三、垃圾回收的几种方法
1、标记----清除算法
步骤:
- 使用可达性算法标记出可回收对象
- 回收可回收对象
问题:内存碎片
2、复制算法
把堆等分成A,B两块区域。A区域按照标记清除算法完成之后,将A区域存活的对象复制到B区域(存活对象都依次紧邻排列),再把A区域全部清除释放内存空间。
问题:内存利用率低,给堆分配了 500M 内存,结果只有 250M 可用。另外每次回收也要把存活对象移动到另一半,效率低下。
3、标记----整理算法
将所有的存活对象都往一端移动,紧邻排列,再清理掉另一端的所有区域,这样的话就解决了内存碎片的问题。
问题:频繁移动存活对象,效率低
*4、分代收集算法
根据对象存活周期不同,将对象分为老年代和新生代(2:1),新生代又分为 Eden 区, from Survivor 区(简称S0),to Survivor 区(简称 S1),三者的比例为 8: 1 : 1。
我们把新生代发生的 GC 称为 Young GC(也叫 Minor GC),老年代发生的 GC 称为 Old GC(也称为 Full GC)。
工作原理:
-
对象在新生代的分配和回收
首先,大部分对象在短时间内都会被回收。
新来的对象首先会被分配在Eden区,当Eden区将满时,触发Minor GC。Eden还存活的少数对象会被移到S0(或是S1区,这里用S0举例),清空Eden,同时对象年龄加一。
同样,当Eden区再次触发Minor GC时,将Eden区中的和S0中的存活对象一起移到S1中,同时清空 Eden 和 S0 的空间,年龄加一。
如上述,Eden区再再次触发Minor GC,将Eden区中的和S1中的存活对象一起移到S0中,同时清空 Eden 和 S1 的空间,年龄加一。S0和S1循环
采用复制算法 -
对象何时晋升为老年代
1.当对象的年龄达到了我们设定的阈值(比如15),则会从S0(或S1)晋升到老年代.
2.大对象 当某个对象分配需要大量的连续内存时,此时对象的创建不会分配在 Eden 区,会直接分配在老年代,因为如果把大对象分配在 Eden 区, Minor GC 后再移动到 S0,S1 会有很大的开销(对象比较大,复制会比较慢,也占空间),也很快会占满 S0,S1 区.
3.在 S0(或S1) 区相同年龄的对象大小之和大于 S0(或S1)空间一半以上时,则年龄大于等于该年龄的对象也会晋升到老年代。 -
空间分配担保
-
Stop The World
如果老年代满了,会触发 Full GC, Full GC 会同时回收新生代和老年代(即对整个堆进行GC),它会导致 Stop The World(简称 STW),造成挺大的性能开销。
所谓的 STW, 即在 GC(minor GC 或 Full GC)期间,只有垃圾回收器线程在工作,其他工作线程则被挂起。 -
Safe Point
由于 Full GC(或Minor GC) 会影响性能,所以我们要在一个合适的时间点发起 GC,这个时间点被称为 Safe Point
Safe Point 主要指的是以下特定位置:
- 循环的末尾
- 方法返回前
- 调用方法的 call 之后
- 抛出异常的位置
老年代一般使用标记整理算法。
四、垃圾收集器
- 在新生代工作的垃圾回收器:Serial, ParNew, ParallelScavenge
- 在老年代工作的垃圾回收器:CMS,Serial Old, Parallel Old
- 同时在新老生代工作的垃圾回收器:G1
简单介绍分类:
Serial
:单线程收集器,再单CPU,Client模式下应用,可与CMS,Serial Old配合工作。
ParNew
:多线程收集器,Server 模式,可与CMS,Serial Old配合工作。
ParallelScavenge
:使用复制算法,多线程,看起来功能和 ParNew 收集器一样,它有啥特别之处吗?
1、CMS 等垃圾收集器关注的是尽可能缩短垃圾收集时用户线程的停顿时间,而 Parallel Scavenge
目标是达到一个可控制的吞吐量(吞吐量 = 运行用户代码时间 / (运行用户代码时间+垃圾收集时间)),CMS
等垃圾收集器更适合用到与用户交互的程序,因为停顿时间越短,用户体验越好,而 Parallel Scavenge
收集器关注的是吞吐量,所以更适合做后台运算等不需要太多用户交互的任务。 2、自适应策略,Parallel Scavenge
收集器提供了两个参数来精确控制吞吐量,分别是控制最大垃圾收集时间的 -XX:MaxGCPauseMillis 参数及直接设置吞吐量大小的
-XX:GCTimeRatio(默认99%),第三个参数 -XX:UseAdaptiveSizePolicy,开启这个参数后,就不需要手工指定新生代大小,Eden 与 Survivor 比例(SurvivorRatio)等细节,只需要设置好基本的堆大小(-Xmx
设置最大堆),以及最大垃圾收集时间与吞吐量大小,虚拟机就会根据当前系统运行情况收集监控信息,动态调整这些参数以尽可能地达到我们设定的最大垃圾收集时间或吞吐量大小这两个指标。
CMS:
并发收集器,它第一次实现了垃圾收集线程与用户线程(基本上)同时工作,它采用的是传统的 GC 收集器代码框架,与 Serial,ParNew 共用一套代码框架.
Serial Old
:Serial Old 是工作于老年代的单线程收集器
Parallel Old
:使用多线程和标记整理法
G1(Garbage First) 收集器
:G1 收集器是面向服务端的垃圾收集器,主要有以下几个特点:
像 CMS 收集器一样,能与应用程序线程并发执行。
整理空闲空间更快。
需要 GC 停顿时间更好预测。
不会像 CMS那样牺牲大量的吞吐性能。 不需要更大的 Java Heap