Java技术体系:
一、Oracle官方定义的java技术体系包括:
(1)Java程序设计语言
(2)各种硬件平台上的Java虚拟机
(3)Class文件格式
(4)Java API类库
(5)来自商业机构和开源社区的第三方Java类库
Java程序设计语言、Java虚拟机、Java API类库这三部分统称为JDK(Java Development Kit),JDK是用于支持程序开发的最小环境;
把Java API类库中的 Java SE API子集和Java虚拟机这两部分统称为JRE(Java Runtime Environment),JRE是支持Java程序运行的标准环境。
以上是根据各个组成部分的功能来进行划分。如果按照技术服务的领域来划分,或者说按照Java技术关注的重点业务领域来划分,Java技术体系可以分为4个平台,分别为:
Java Card:支持一些Java小程序(Applets)运行在小内存设备(如智能卡)上的平台;
Java ME(Micro Edition):支持Java程序运行在移动终端(手机、PDA)上的平台,对Java API有所精简,并加入了针对移动终端的支持,这个版本以前成为J2ME;
Java SE(Standard Edition):支持面向桌面应用(如Windows下的应用程序)的Java平台,提供了完整的Java核心API,这个版本以前称为J2EE;
Java EE(Enterprise Ediiton):支持使用多层架构的企业应用(如ERP、CRM应用)的Java平台,除了提供Java SE API外,还对其做了大量的扩充,并提供了相关的部署支持,这个版本以前称为J2EE。
二、JDK:用于支持Java程序开发的最小环境。
内容包括:
1. Java程序设计语言
2.Java虚拟机
3.Java API类库
三、JRE:支持Java程序运行的标准环境。
内容包括:
1.Java API类库中的Java SE API子集
2.Java虚拟机
什么是JVM:
1.JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算机设备的规范,可用不同的方式(软件或硬件)加以实现。
它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
2.JVM包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域。
3.Java虚拟机是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台(Linux, macOS, windows)的机器指令执行。
每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里。
4.引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
JVM在哪里 :
1. JDK,JRE,JVM三者关系 JDK :(Java Development Kit),Java 开发工具包。JDK 是整个 Java 开发的核心,集成了 JRE 和javac.exe,java.exe,jar.exe 等工具。 JRE :(Java Runtime Environment),Java 运行时环境。主要包含两个部分,JVM 的标准实现和 Java 的一些基本类库。它相对于 JVM 来说,多出来的是一部分的 Java 类库。 三者的关系是:一层层的嵌套关系。JDK>JRE>JVM
2.JVM 与操作系统之间的关系: JVM 上承开发语言,下接操作系统,它的中间接口就是字节码。
JVM跨平台及原理 :
1。跨平台:由Java编写的程序可以在不同的操作系统上运行,一次编写,多处运行。
2. 原理:编译之后的字节码文件和平台无关,只需要在不同的操作系统上安装一个对应版本的虚拟机(JVM)。
3.Java虚拟机有自己完善的硬件架构,如处理器、堆栈等,还具有相应的指令系统。
4.Java虚拟机本质上就是一个程序,当它在命令行上启动的时候,就开始执行保存在某字节码文件中的指令。Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。
5.字节码:要运行一段Java源码,必须先将源码转换为class文件,class文件就是编译器编译之后供虚拟机解释执行的二进制字节码文件,可以通过IDE工具或者命令行去将源码编译成class文件。Class文件中包含了Java虚拟机指令集(或者称为字节码、Bytecodes)和符号集,还有一些其他辅助信息。
JVM体系结构:
1.JVM总体上是由类装载子系统(ClassLoader)、运行时数据区、执行引擎、内存回收这四个部分组成。 其中我们最为关注的运行时数据区。
2.JVM的内存部分则是由方法区(Method Area)、JAVA堆(Heap)、虚拟机栈(Stack)、程序计数器、本地方法栈这几部分组成;
3.除此以外,在概念中还有一个直接内存的概念,事实上这部分内存并不属于虚拟机规范中定义的内存区域。 但是因为在JDK1.4+后新加的NIO类,以及JDK1.8+后的Metaspace的关系,所以在讨论JVM时也经常会被放到一起讨论。
运行时数据区域:
1.JVM运行时数据区域: 程序计数器、 虚拟机栈、 本地方法栈、 堆、 方法区。
2.有的区域随着虚拟机进程的启动而存在,有的区域则依赖用户进程的启动和结束而创建和销毁。
JDK8 之前的内存区域图如下:
JDK8 之后的内存区域图如下:
Java 8 中 PermGen 为什么被移出 HotSpot JVM 了?两个主要原因:
1.由于 PermGen 内存经常会溢出,引发恼人的 java.lang.OutOfMemoryError: PermGen,因此 JVM 的开发者希望这一块内存可以更灵活地被管理,不要再经常出现这样的 OOM
2.移除 PermGen 可以促进 HotSpot JVM 与 JRockit VM 的融合,因为 JRockit 没有永久代。
根据上面的各种原因,PermGen 最终被移除,方法区移至 Metaspace,字符串常量移至 Java Heap。
方法区(Method Area)
方法区与堆有很多共性:线程共享、内存不连续、可扩展、可垃圾回收,同样当无法再扩展时会抛出OutOfMemoryError异常。
正因为如此相像,Java虚拟机规范把方法区描述为堆的一个逻辑部分,但目前实际上是与Java堆分开的(Non-Heap)。
方法区个性化的是,它存储的是已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
方法区的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是回收确实是有必要的。
程序计数器(Program Counter Register)
关于程序计数器我们已经得知:占用内存较小,现成私有。它是唯一没有OutOfMemoryError异常的区域。
程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变计数器的值来选取下一条字节码指令。其中,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器来完成。
Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。
因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)。
虚拟机栈(JVM Stacks)
虚拟机栈线程私有,生命周期与线程相同。
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。
**局部变量表(Local Variable Table)**是一组变量值存储空间,用于存放方法参数和方法内定义的局部变量。包括8种基本数据类型、对象引用(reference类型)和returnAddress类型(指向一条字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间(Slot),其余的数据类型只占用1个。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈动态扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
**操作数栈(Operand Stack)**也称作操作栈,是一个后入先出栈(LIFO)。随着方法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给方法调用者,也就是出栈/入栈操作。
动态链接:Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态链接(Dynamic Linking)。
方法返回:无论方法是否正常完成,都需要返回到方法被调用的位置,程序才能继续进行。
本地方法栈(Native Method Stacks)
本地方法栈(Native Method Stacks)与虚拟机栈作用相似,也会抛出StackOverflowError和OutOfMemoryError异常。
区别在于虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈是为虚拟机使用到的Native方法服务。
引用计数算法:
1.给对象添加一个引用计数器,每当有一个地方引用他时,计数器值就加一,当一个引用失效时,计数器值就减一。 任何时刻计数器为零的对象就是不可在被使用的。
2.分析:
客观的说,引用计数器算法(Reference Counting)的实现简单,判定效率很高,在大部分情况下,都是一个不错的算法。 但是,主流的Java 虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是他很难解决对象之间相互循环引用的问题。例如:objA.instance = objB;及 objB.instance = objA;除此之外两个对象再无其他引用,实际上这两个对象已经不可能再被访问,但是他们因为互相引用着对方,导致他们的引用计数都不为零,于是引用计数算法无法通知GC收集器回收他们。
可达性分析算法:
1.通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots 没有任何引用链相连(用图论的话来说就是从GC Roots 到这个对象不可达)时,则证明此对象是不可引用的。
2.在Java 语言中,可作为GC Roots的对象包括下面几种:
a.虚拟机栈(栈帧中的本地变量表)中引用的对象;
b.方法区中类静态属性引用的对象;
c.方法区中常量引用的对象;
d.本地方法栈中JIT(即一般说的Native方法)引用的对象。
JVM堆内存结构:
1. 新生代(Young Generation):
也有叫做“年轻代”的,这里使用《深入理解JAVA虚拟机》中的叫法,下同。
其实看名称就能看出来,一般情况下,新创建的对象都会存放到新生代中(大对象除外)。
新生代中对象的特点是:很快就会被GC回收掉的或者不是特别大的对象。
为了方便垃圾收集,新生代又分出了一个Eden区,两个 Survivor区。
JVM 每次只会使用 Eden区 和其中的一块 Survivor 区域来为对象服务,另一块Survivor区域是空的,用于垃圾回收。
举个例子,第一次回收的时候,虚拟机会将 Eden区+Survivor(from)区域的存活对象复制到Survivor(to)上(存活对象小于Survivor(to)的空间),清空Survivor(from),虚拟机使用Eden区+Survivor(to);
第二次回收的时候,虚拟机再将Eden区+Survivor(to)存活的对象复制到Survivor(from),清空Survivor(to)。
这三个区域默认情况下是按照8:1:1分配,也可以手动配置。
2. 老年代(Old Generation):
在新生代每进行一次垃圾收集后,就会给存活的对象“加1岁”,当年龄达到一定数量的时候就会进入老年代(默认是15,可以通过-XX:MaxTenuringThreshold来设置)。
另外,比较大的对象也会进入老年代,可以-XX:PretenureSizeThreshold进行设置。
如-XX:PretenureSizeThreshold3M,那么大于3M的对象就会直接就进入老年代。
因此,老年代中存放的都是一些“生命周期较长”的对象或者“特别大”的对象。
3. 永久代(Permanent Generation ):
即JVM的方法区。在这里存放着一些被虚拟机加载的类信息(别忘了还有动态生成的类)的静态文件,这就导致了这个区中的东西比老年代和新生代更不容易回收。
永久代大小通过-XX:MaxPermSize=进行设置。
4. 元空间(Metaspace):
从JDK 8开始,Java开始使用“元空间”取代“永久代”,元空间并不在虚拟机中,而是直接使用本地内存。
那么,默认情况下,元空间的大小仅受本地内存限制。当然,也可以对元空间的大小手动的配置。
同样,对上图呈现内容汇总分析。
第一,堆的GC操作采用分代收集算法。
第二,堆区分了新生代和老年代;
第三,新生代又分为:Eden空间、From Survivor(S0)空间、To Survivor(S1)空间。
Java虚拟机规范规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
也就是说堆的内存是一块块拼凑起来的。要增加堆空间时,往上“拼凑”(可扩展性)即可,但当堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
jvm内存结构总结:
标记-清除算法:
1.执行过程:
当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。
2. 标记: Collector从引用根结点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象。
3.清除: Collector对堆内存从头到尾进行遍历,如果发现某个对象在其Header中没有标记为可达对象,则将其回收。
缺点:
1. 效率不算高;
2. 在进行GC的时候,需要停止整个应用程序,导致用户“体验差”;
3.这种方式清理出来的空闲内存是不连续的,产生内存“碎片”。
优点:
1.逻辑简单,空间开销小;
2.不用移动对象;
说明:
何为清除? 这里所谓的清除并不是真的置空,而是把需要清除的对象地址保存在空闲的地址列表里。下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放。
标记-复制算法:
标记-复制算法 (来自《深入理解Java虚拟机》,有的地方叫“复制算法”)
1.原理:
将堆内存空间分为两块,每次只使用其中一块。在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的“所有对象”,再交换两个内存的角色,最后完成垃圾回收。(适用于新生代)
2、由于空闲的内存是规整的,所以创建对象时分配内存,采用的时是指针碰撞。
3、优点:
(1)没有清除过程,实现简单,运行高效(是建立在每次存活的对象都不是很多的情况)
(2)复制过去以后保证空间的连续性,不会出现“碎片”问题。
4、缺点:
(1)此算法的缺点也是很明显的,就是需要“两倍”的内存空间。
(2)逻辑相对复杂。对于G1这种分拆成为大量region的GC,复制而不是移动,意味着GC需要维护region之间对象引用关系,不管是内存占用或者时间开销也不小,毕竟是要把全部存活的对象都复制一遍,对象的地址也要跟着变化,引用它的对象的引用地址也要做相应的地址调整。
5、适用区域:存活对象少、垃圾对象多的内存区域。
6、特别的:
如果系统中的垃圾对象很多,复制算法就不会很理想,因为复制算法需要复制的存活对象数量并不会太大,或者说非常低才高效。而堆中新生代的S0 、S1区的对象就满足这样的特点,一次回收就不会去复制太多的对象,那么效率也就很高了。
标记-整理(压缩)算法 :
叫做标记 - 压缩算法,或者,标记 - 整理算法
1、背景:
标记-复制算法的高效性是建立在存活对象少、垃圾对象多的前提下的。这种情况在新生代经常发生,但是在“老年代“,更常见的情况是大部分对象都是存活对象。
如果依然使用复制算法,由于存活对象较多,复制的成本也将很高。因此,基于老年代垃圾回收的特性,需要使用其他的算法。
标记一清除算法的确可以应用在老年代中,但是该算法不仅执行”效率低“下,而且在执行完内存回收后还会产生”内存碎片“,所以JVM的设计者需要在此基础之上进行改进。
标记 - 压缩(Mark - Compact) 算法由此诞生。
2、原理:
第一阶段和标记 - 清除算法的标记过程一样,从根节点开始标记所有被引用对象。
第二阶段将所有的存活对象“压缩到内存的一端”,按顺序排放。
第三阶段,清理边界外所有的空间。
3、优点:
(1)消除了标记-清除算法当中,垃圾回收后内存区域分散的缺点。没有内存碎片。我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可。
(2)消除了复制算法当中,”内存减半“的高额代价。
4、缺点:
(1)从效率上来说,标记-整理算法要低于标记-复制算法。
(2)移动对象的同时,如果对象被其他对象引用,则还需要”调整引用“的地址。
(3)移动过程中,需要全程暂停用户应用程序。即: STW
垃圾收集器整体分类:
Serial和Serial Old收集器:
一、Serial回收器
1.Serial收集器是最基本、历史最悠久的垃圾收集器了。JDK1.3之前是新生代的唯一选择。
2.Serial收集器作为HotSpot中Client模式下的默认新生代垃圾收集器。
3.Serial收集器采用了"复制算法"、”串行回收“和“Stop-The-World"机制的方式执行内存回收。
4.除了年轻代之外,Serial收集器还提供用于执行老年代垃圾收集的Serial Old收集器。Serial Old收集器同样采用了”串行回收“和“Stop-The-World"机制,只不过内存回收算法使用的是"标记-整理算法"。
(Serial Old是运行在Client模式下默认的老年代的垃圾回收器。Serial Old在Server模式下主要有两个用途:1.与新生代的Parallel Scavenge配合使用 。2.作为老年代CMS收集器的后备垃圾收集方案。)
二、优势:
1.简单而高效;
2.在用户的桌面应用场景中,可用内存一般不大,可以在较短时间内完成垃圾收集,只要不频繁发生,使用串行回收器是可以接受的。
3.在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以指定年轻代和老年代都使用串行收集器。
ParNew 收集器:
1.工作原理:停止工作线程,停止创建对象,使用“复制算法””多线程“回收,默认线程数等于cpu数量。
2.优点:
在多CPU时,比Serial效率高。
3. 缺点:
收集过程暂停所有应用程序线程(Stop the world),单CPU时比Serial效率差。
4. 使用算法:“复制算法”
5. 适用范围:新生代 。
6.应用:运行在Server模式下的虚拟机中首选的新生代收集器。
7、应用场景 :
在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作; 但在单个CPU环境中,不会比Serail收集器有更好的效果,因为存在线程交互开销。
Parallel Scavenge 和 Parallel Old 收集器 :
1.Parallel Scavenge 收集器是一个新生代收集器,采用"复制算法",并且是”多线程“收集器;
2.Parallel Scavenge 收集器的关注点与其他收集器不同,ParNew、CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge 收集器的目标则是达到一个可控制的吞吐量(Throughput)。
3.这里所谓的吞吐量是指CPU用于运行用户代码的时间与CPU总消耗时间的比值,既吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉了1分钟,那么吞吐量就是99%。
4.停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验,而高吞吐量则可以高效的利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。 由于与吞吐量关系密切,Parallel Scavenge 收集器也经常称为“吞吐量优先”收集器。
5.Parallel Old 收集器是Parallel Scavenge 收集器的老年代版本,使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源敏感的场合,可以优先考虑Parallel Scavenge 加 Parallel Old 收集器。
CMS(Concurrent Mark Sweep)收集器:
一.算法:标记-清除,(来自《深入理解Java虚拟机》);
二.原理:
1)初始标记:标记“GC Roots”直接引用的对象(由本地方法栈、静态变量、常量等直接引用的对象),会停止用户程序(stop the world),但是速度会很快;
2)并发标记:对老年代所有对象进行GC Roots追踪,就是标记“间接引用”对象。这个过程是是比较耗时的,但由于与用户线程是“并发执行”的,所以对性能影响并不大;
3)重新标记:修正在并发标记期间由用户线程执行导致标记出现变动,或者出现新对象的那部分进行标记。该过程仍然会停止用户线程(stop the world)。由于对少量对象进行标记,所以速度也很快。
4)并发清除:清理之前标记的垃圾对象。虽然耗时,但是与用户线程一起“并发执行”,所以对用户影响不大。
三、应用场景:
适用于注重服务的响应速度,希望停顿时间短,给用户更好的体验的场景。例如web服务,b/s服务。
G1区域划分:
1.G1的堆结构就是把一整块堆内存区域切分成多个固定大小的块。
2.在JVM启动的时候来决定每个小块(也就是region)的大小。
JVM一般是把一整块堆切分成2000个小region,每个小region的大小 从 1 到 32 Mb 不等。
3.和以往的垃圾收集器一样,G1中也有Eden、Survivor、Old。
进行完一次垃圾收集后,存活下来的对象会被虚拟机从一个小region移动到另一个小region中。
4.这些小块的垃圾回收是并行并且“并发进行”的,期间其他应用的线程的照常工作。
G1的GC过程:
G1的GC分为以下4个大的步骤:(来自《深入理解Java虚拟机》,G1的垃圾收集具体还可以分为2种模式,Young GC和Mix GC,每种模式的收集过程不一样)
1.初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象。这个阶段需要停顿用户线程,但是耗时很短。
2.并发标记(Concurrent Marking):从GC Roots开始对堆中的对象进行可达性分析,递归扫码整个堆里的对象图,找出要回收的对象。这个阶段耗时较长,但可以与用户程序并发执行。当对象图扫描完成以后,(这个过程中用户进程与GC)并发时有引用变动会产生“漏标”问题,G1中会使用SATB(snapshot-at-the-beginning)算法来解决。
3.最终标记(Final Marking):对用户线程做一个短暂的暂停,用于处理并发标记阶段仍然遗留下拉的“漏标对象”。
4.筛选回收(Live Data Counting and Evacuation):负责更新各个堆分块区域统计数据,对各个区域的“回收价值”和“成本”进行排序,根据用户所期望的“停顿时间”来制定回收计划,可以自由选择任意多个区域构成“回收集”。然后把决定回收的那一部分区域的存活对象“复制”到空的区域中,再清理掉整个“回收集”的全部空间。这里的操作涉及到对象的移动,是必现暂停用户线程,由多个收集线程“并行”完成。
垃圾收集器对比总结:
垃圾收集器 | 分类 | 作用位置 | 使用算法 | 特点 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行运行 | 作用于新生代 | 复制算法 | 响应速度优先 | 适用于单CPU环境下的client模式 |
ParNew | 并行运行 | 作用于新生代 | 复制算法 | 响应速度优先 | 多CPU环境Server模式与CMS配合使用 |
Parallel | 并行运行 | 作用于新生代 | 复制算法 | 吞吐量优先 | 使用于后台运算而不需要太多交互的场景 |
Serial Old | 串行运行 | 作用于老年代 | 标记-压缩算法 | 响应速度优先 | 适用于单CPU环境下的Client模式 |
Parallel Old | 并行运行 | 作用于老年代 | 标记-压缩算法 | 吞吐量优先 | 适用于后台运算而不需要太多交互的场景 |
CMS | 并发运行 | 作用于老年代 | 标记-清除算法 | 响应速度优先 | 适用于物联网或B/S业务 |
G1 | 并发,并行运行 | 作用于新生代,老年代 | 标记-压缩算法,复制算法 | 响应速度优先 | 面向服务端应用 |
垃圾收集的并发(Concurrent)与并行(Parallel)
并发和并行,在谈论垃圾收集器的上下文语境中,他们可以解释如下:
一、并发(Concurrent ):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行)。垃圾收集线程在执行时不会停顿用户程序的运行。
1.在现代多CPU情况下,垃圾收集时垃圾收集程序在一个CPU上运行,用户程序在其它多个CPU上运行;
2.例如:CMS、G1垃圾收集器;
二、并行(Parallel):指多条垃圾收集线程并行工作,但是此时用户线程可以处于等待状态。
1.例如:ParNew、Parallel Scavenge、Parallel Old收集器;
补充说明:
并发: 一个并发程序是具备处理多个任务的能力。并发并不需要有多个CPU,单个CPU通过时间片的方式,不同时间片处理不同任务,可以让程序“看起来”是都在执行的。 并行: 并行表示在同一个时间点,有多个任务都在进行当中。并行是需要多个CPU才可以完成的。如果说每一个CPU都在各自运行不同的程序,那么4个CPU就可以让4个程序“并行”运算。
JDK版本的默认垃圾回收器
版本 | 年轻代 | 老年代 | 备注 |
JDK6 | PSScavenge(Parallel Scavenge) | PSMarkSweep (Parallel Scavenge) | 简称:PSPS |
JDK7 | PSScavenge(Parallel Scavenge) | PSParallelCompact(Parallel Old) | 简称:PSPO |
JDK8 | PSScavenge(Parallel Scavenge) | PSParallelCompact(Parallel Old) | 简称:PSPO |
JDK11 | G1 | G1 | --- |
鸣谢:特别感谢所有在CSDN等网站热爱技术、乐于分享的工程师们。
说明:本文只是个人学习之用。