Java 堆从GC 的角度可以细分为: 新生代(Eden 区、From Survivor 区和To Survivor 区)和老年
代。
是用来存放新生的对象。一般占据堆的1/3 空间。由于频繁创建对象,所以young区会频繁触发MinorGC 进行垃圾回收。young区又分为 Eden 区、ServivorFrom、ServivorTo 三个区。
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到old区)。当Eden 区内存不够的时候就会触发MinorGC,对young区进行一次垃圾回收。正常对象创建所在区域,大多数对象“朝生夕死”
上一次GC 的幸存者,作为这一次GC 的被扫描者。
保留了一次MinorGC 过程中的幸存者。在同一个时间点上,ServivorFrom和ServivorTo只能有一个区有数据,另外一个是空的。
MinorGC 采用复制算法。
主要存放应用程序中生命周期长的内存对象。old区的对象比较稳定,所以MajorGC 不会频繁执行。在进行MajorGC 前一般都先进行了一次MinorGC,使得有young的对象晋身入old区,导致空间不够用时才触发。当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC 进行垃圾回收腾出空间。MajorGC 采用标记清除算法:首先扫描一次所有old区,标记出存活的对象,然后回收没有标记的对象。MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当old区也满了装不下的时候,就会抛出OOM(Out of Memory)异常。
插件下载链接:https://visualvm.github.io/pluginscenters.html
① 运行命令 jvisualvm
打开工具界面:
在插件中选择上面地址中下载的插件
从这个图中可以看出堆的区域划分,也证明了前面说的ServivorFrom和ServivorTo只能有一个区有数据,另外一个是空的。即图中的S0和S1.
堆内存溢出案例:
设置参数:-Xmx20M -Xms20M
测试代码:
public String heap() throws Exception{
while(true){
list.add(new Person());
}
}
方法区内存溢出案例:
设置参数:-XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M
测试代码:
public String heap(){
while(true){
list.addAll(MetaspaceUtil.createClasses());
}
}
public class MetaspaceUtil extends ClassLoader {
public static List<Class<?>> createClasses() {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (int i = 0; i < 10000000; ++i) {
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
"java/lang/Object", null);
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "" ,
"()V", null, null);
mw.visitVarInsn(Opcodes.ALOAD, 0);
mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
"" , "()V");
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(1, 1);
mw.visitEnd();
MetaspaceUtil test = new MetaspaceUtil();
byte[] code = cw.toByteArray();
Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
classes.add(exampleClass);
}
return classes;
}
}
虚拟机栈溢出案例:
测试代码:
public static void method(long i){
System.out.println(count++);
method(i);
}
最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清
除阶段回收被标记的对象所占用的空间。如图
从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可
利用空间的问题。
为了解决标记清除算法算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小
的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用
的内存清掉,如图:
这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原
本的一半。且存活对象增多的话,复制算法的效率会大大降低。
结合了以上两个算法,为了避免缺陷而提出。标记阶段和标记清除算法相同,标记后不是清
理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。如图:
分代收集法是目前大部分JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存
划分为不同的域,一般情况下将GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young
Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃
圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
目前大部分JVM的GC 对于新生代都采取复制算法,因为新生代中每次垃圾回收都要
回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1 来划分新生代。一般将新生代
划分为一块较大的Eden 空间和两个较小的Survivor 空间(From Space, To Space),每次使用
Eden 空间和其中的一块Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另
一块Survivor 空间中。
而老年代因为每次只回收少量对象,因而采用标记整理算法算法。
java 虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器,主要有:Serial、Serial Old、ParNew、Parallel、Parallel Old、CMS、G1。
Serial(英文连续)是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾
收集器。Serial 是一个单线程的收集器,它不但只会使用一个CPU 或一条线程去完成垃圾收集工
作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java 虚拟机运行在Client 模式下默认的新生代垃圾收集器。
ParNew 垃圾收集器其实是Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃
圾收集之外,其余的行为和Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也
要暂停所有其他的工作线程。ParNew 收集器默认开启和CPU 数目相同的线程数,可以通过
-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。【Parallel:平行的】ParNew虽然是除了多线程外和Serial 收集器几乎完全一样,但是ParNew垃圾收集器是很多java虚拟机运行在Server 模式下新生代的默认垃圾收集器。
Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃
圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU 用于运行用户代码
的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),
高吞吐量可以最高效率地利用CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而
不需要太多交互的任务。自适应调节策略也是ParallelScavenge 收集器与ParNew 收集器的一个
重要区别。相比ParNew,更加关注吞吐量
Serial Old 是Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client 默认的java 虚拟机默认的年老代垃圾收集器。在Server 模式下,主要有两个用途:
新生代Parallel Scavenge 收集器与ParNew 收集器工作原理类似,都是多线程的收集器,都使用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。
Parallel Old 收集器是Parallel Scavenge 的年老代版本,使用多线程的标记-整理算法,在JDK1.6
才开始提供。在JDK1.6 之前,新生代使用ParallelScavenge 收集器只能搭配年老代的Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和年老代Parallel Old 收集器的搭配策略。
Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。更加关注停顿时间。
CMS 工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下4 个阶段:
Garbage first 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS 收集器,G1 收集器两个最突出的改进是:
-XX: +UseSerialGC
-XX: +UseSerialoldGC
-XX: +UseParallelGC
-XX: +UseParalleloldGC
-XX: +UseConcMarkSweepGC
-XX: +UseG1GC