【从0到1学习JVM · 25】同样都要停顿所有线程,Parallel比Serial到底强在哪

前言

学完垃圾回收算法后,很多同学一看到 Serial 和 Parallel 收集器就容易困惑:既然都是停顿整个应用,多线程到底强在哪?多核时代单线程为什么还没被淘汰?本文对比这两代经典收集器的底层机制与选型权衡。


文章目录

    • 前言
    • [一、单人打扫房间的 Serial 单线程设计](#一、单人打扫房间的 Serial 单线程设计)
      • [1.1 新生代与老年代的分工](#1.1 新生代与老年代的分工)
      • [1.2 为什么单线程能统治早期 Java](#1.2 为什么单线程能统治早期 Java)
    • [二、多核时代的保洁团队 Parallel](#二、多核时代的保洁团队 Parallel)
      • [2.1 同样是 STW,多线程快在哪里](#2.1 同样是 STW,多线程快在哪里)
      • [2.2 为什么 Java 8 会把它选为默认收集器](#2.2 为什么 Java 8 会把它选为默认收集器)
      • [2.3 吞吐量优先背后的自适应调节](#2.3 吞吐量优先背后的自适应调节)
    • 三、两代经典收集器的核心差异与真实取舍
      • [3.1 核心机制与参数对照](#3.1 核心机制与参数对照)
      • [3.2 并行并不等于不卡顿](#3.2 并行并不等于不卡顿)
      • [3.3 什么时候单线程反而比多线程更快](#3.3 什么时候单线程反而比多线程更快)
    • 四、写在最后

一、单人打扫房间的 Serial 单线程设计

在 Java 诞生的早期,硬件环境与今天截然不同。当时的服务器通常只有单核 CPU,内存规模也普遍在几十兆到几百兆之间。在这样的硬件条件下,垃圾收集的设计理念以简单、轻量为核心。

Serial 收集器就是这一时期的代表。

1.1 新生代与老年代的分工

Serial 实际上是一对按内存代际配合工作的组合:

  • Serial(新生代) :负责新生代内存回收,采用复制算法。在新生代中划分出 Eden 区以及两块大小相等的 Survivor 区(S0 和 S1)。回收时将 Eden 和 From Survivor 区中的存活对象一次性复制到 To Survivor 区中。
  • Serial Old(老年代) :负责老年代内存回收,采用标记-整理算法。回收时首先标记出老年代中所有存活的对象,然后把存活对象向内存的一端移动,最后直接清理掉端边界以外的内存。

#mermaid-svg-gWAtB7R6fvb3dEZG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gWAtB7R6fvb3dEZG .error-icon{fill:#552222;}#mermaid-svg-gWAtB7R6fvb3dEZG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gWAtB7R6fvb3dEZG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gWAtB7R6fvb3dEZG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gWAtB7R6fvb3dEZG .marker.cross{stroke:#333333;}#mermaid-svg-gWAtB7R6fvb3dEZG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gWAtB7R6fvb3dEZG p{margin:0;}#mermaid-svg-gWAtB7R6fvb3dEZG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gWAtB7R6fvb3dEZG .cluster-label text{fill:#333;}#mermaid-svg-gWAtB7R6fvb3dEZG .cluster-label span{color:#333;}#mermaid-svg-gWAtB7R6fvb3dEZG .cluster-label span p{background-color:transparent;}#mermaid-svg-gWAtB7R6fvb3dEZG .label text,#mermaid-svg-gWAtB7R6fvb3dEZG span{fill:#333;color:#333;}#mermaid-svg-gWAtB7R6fvb3dEZG .node rect,#mermaid-svg-gWAtB7R6fvb3dEZG .node circle,#mermaid-svg-gWAtB7R6fvb3dEZG .node ellipse,#mermaid-svg-gWAtB7R6fvb3dEZG .node polygon,#mermaid-svg-gWAtB7R6fvb3dEZG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gWAtB7R6fvb3dEZG .rough-node .label text,#mermaid-svg-gWAtB7R6fvb3dEZG .node .label text,#mermaid-svg-gWAtB7R6fvb3dEZG .image-shape .label,#mermaid-svg-gWAtB7R6fvb3dEZG .icon-shape .label{text-anchor:middle;}#mermaid-svg-gWAtB7R6fvb3dEZG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gWAtB7R6fvb3dEZG .rough-node .label,#mermaid-svg-gWAtB7R6fvb3dEZG .node .label,#mermaid-svg-gWAtB7R6fvb3dEZG .image-shape .label,#mermaid-svg-gWAtB7R6fvb3dEZG .icon-shape .label{text-align:center;}#mermaid-svg-gWAtB7R6fvb3dEZG .node.clickable{cursor:pointer;}#mermaid-svg-gWAtB7R6fvb3dEZG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gWAtB7R6fvb3dEZG .arrowheadPath{fill:#333333;}#mermaid-svg-gWAtB7R6fvb3dEZG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gWAtB7R6fvb3dEZG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gWAtB7R6fvb3dEZG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gWAtB7R6fvb3dEZG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gWAtB7R6fvb3dEZG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gWAtB7R6fvb3dEZG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gWAtB7R6fvb3dEZG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gWAtB7R6fvb3dEZG .cluster text{fill:#333;}#mermaid-svg-gWAtB7R6fvb3dEZG .cluster span{color:#333;}#mermaid-svg-gWAtB7R6fvb3dEZG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gWAtB7R6fvb3dEZG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gWAtB7R6fvb3dEZG rect.text{fill:none;stroke-width:0;}#mermaid-svg-gWAtB7R6fvb3dEZG .icon-shape,#mermaid-svg-gWAtB7R6fvb3dEZG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gWAtB7R6fvb3dEZG .icon-shape p,#mermaid-svg-gWAtB7R6fvb3dEZG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gWAtB7R6fvb3dEZG .icon-shape .label rect,#mermaid-svg-gWAtB7R6fvb3dEZG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gWAtB7R6fvb3dEZG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gWAtB7R6fvb3dEZG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gWAtB7R6fvb3dEZG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Serial 收集器组合分工
老年代堆空间
Serial Old 收集器

采用标记-整理算法
标记存活对象
向一端移动整理内存
新生代堆空间
Serial 收集器

采用复制算法
Eden 区
Survivor 区

1.2 为什么单线程能统治早期 Java

Serial 和 Serial Old 最本质的特征就是单线程执行。

当发生垃圾回收时,JVM 会触发 Stop The World(STW)机制。无论当前有多少个业务工作线程在运行,JVM 都会将这些业务线程全部强制暂停。接着,JVM 只启动一个独立的垃圾收集线程,孤身一人去遍历对象图、复制存活对象或整理老年代内存。直到这个单线程把垃圾清理完毕,所有业务线程才获准恢复执行。
单个 GC 线程 (Serial) JVM 控制中心 业务工作线程 单个 GC 线程 (Serial) JVM 控制中心 业务工作线程 #mermaid-svg-SK2DYHKuag4aYwUn{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-SK2DYHKuag4aYwUn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-SK2DYHKuag4aYwUn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-SK2DYHKuag4aYwUn .error-icon{fill:#552222;}#mermaid-svg-SK2DYHKuag4aYwUn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-SK2DYHKuag4aYwUn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-SK2DYHKuag4aYwUn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-SK2DYHKuag4aYwUn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-SK2DYHKuag4aYwUn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-SK2DYHKuag4aYwUn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-SK2DYHKuag4aYwUn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-SK2DYHKuag4aYwUn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-SK2DYHKuag4aYwUn .marker.cross{stroke:#333333;}#mermaid-svg-SK2DYHKuag4aYwUn svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-SK2DYHKuag4aYwUn p{margin:0;}#mermaid-svg-SK2DYHKuag4aYwUn .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SK2DYHKuag4aYwUn text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-SK2DYHKuag4aYwUn .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SK2DYHKuag4aYwUn .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-SK2DYHKuag4aYwUn .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-SK2DYHKuag4aYwUn .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-SK2DYHKuag4aYwUn #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-SK2DYHKuag4aYwUn .sequenceNumber{fill:white;}#mermaid-svg-SK2DYHKuag4aYwUn #sequencenumber{fill:#333;}#mermaid-svg-SK2DYHKuag4aYwUn #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-SK2DYHKuag4aYwUn .messageText{fill:#333;stroke:none;}#mermaid-svg-SK2DYHKuag4aYwUn .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SK2DYHKuag4aYwUn .labelText,#mermaid-svg-SK2DYHKuag4aYwUn .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-SK2DYHKuag4aYwUn .loopText,#mermaid-svg-SK2DYHKuag4aYwUn .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-SK2DYHKuag4aYwUn .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-SK2DYHKuag4aYwUn .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-SK2DYHKuag4aYwUn .noteText,#mermaid-svg-SK2DYHKuag4aYwUn .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-SK2DYHKuag4aYwUn .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SK2DYHKuag4aYwUn .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SK2DYHKuag4aYwUn .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-SK2DYHKuag4aYwUn .actorPopupMenu{position:absolute;}#mermaid-svg-SK2DYHKuag4aYwUn .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-SK2DYHKuag4aYwUn .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-SK2DYHKuag4aYwUn .actor-man circle,#mermaid-svg-SK2DYHKuag4aYwUn line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-SK2DYHKuag4aYwUn :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务线程全部挂起等待 (卡顿) 正常运行业务代码,分配对象 1 触发 STW,暂停所有业务线程! 2 唤醒唯一个 GC 线程开始回收 3 遍历对象图、复制存活对象 / 标记整理 4 垃圾回收完毕 5 解除 STW,恢复业务工作线程 6

在多核 CPU 普及之前,这种"单人打扫"的设计其实是极其聪明的选择:

  1. 没有任何线程同步开销:单线程不需要多线程之间的任务分配队列,不需要互斥锁,也没有 CAS 原子操作带来的 CPU 总线同步开销。
  2. 零线程上下文切换:不需要在多个 GC 线程之间来回切上下文,CPU 缓存的命中率极高。
  3. 极简的代码实现与内存占用:收集器本身几乎不需要维护复杂的数据结构,元数据开销极低。

在单核机器上,单线程的 Serial 收集器由于没有线程交互开销,其垃圾回收专心度最高,回收速度往往比多线程方案更干脆。

二、多核时代的保洁团队 Parallel

后来服务器硬件迎来爆发期:单核 CPU 迅速退出历史舞台,4 核、8 核甚至更多核心成为主流;Java 应用的堆内存也从早期的几十兆一路增长到几 GB 甚至几十 GB。

硬件环境的变化,彻底打破了 Serial 的舒适区。

在拥有 32 个 CPU 核心的服务器上,如果继续使用单线程的 Serial 收集器,当 STW 触发时,就会出现极其滑稽的场面:唯独 1 个 CPU 核心在满负荷做垃圾回收,其余 31 个核心全部闲置围观。随着堆内存变大,单线程打扫的时间从原本的十几毫秒拉长到几秒甚至十几秒,整个应用直接陷入长时间假死。

于是,JVM 引入了专门面向多核处理器的并行收集器组合:Parallel Scavenge 与 Parallel Old。

2.1 同样是 STW,多线程快在哪里

很多人容易产生一个误解,以为多线程收集器就是在业务运行的同时"边跑边扫"。

这是一个概念混淆。在 JVM 术语中:

  • Parallel(并行) :指的是多个垃圾收集线程同时工作,但在执行垃圾收集时,用户线程依然处于暂停状态(依然是 100% 的 STW)。
  • Concurrent(并发):指的是垃圾收集线程与用户业务线程在同一个时间段内同时运行(如 CMS 或 G1 的并发标记阶段)。

Parallel Scavenge 与 Parallel Old 依然属于并行收集器。这意味着它们在触发垃圾回收时,同样必须进行全盘挂起的 STW。

它的核心升级在于:不再让一个人孤零零地打扫整个足球场,而是当宣布清场(STW)后,瞬间放进来一个保洁团队,几名保洁员同时对内存空间进行并行清理。
GC 线程 n GC 线程 2 GC 线程 1 JVM 控制中心 业务工作线程 GC 线程 n GC 线程 2 GC 线程 1 JVM 控制中心 业务工作线程 #mermaid-svg-9e15mMBkQanWQkdq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9e15mMBkQanWQkdq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9e15mMBkQanWQkdq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9e15mMBkQanWQkdq .error-icon{fill:#552222;}#mermaid-svg-9e15mMBkQanWQkdq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9e15mMBkQanWQkdq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9e15mMBkQanWQkdq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9e15mMBkQanWQkdq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9e15mMBkQanWQkdq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9e15mMBkQanWQkdq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9e15mMBkQanWQkdq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9e15mMBkQanWQkdq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9e15mMBkQanWQkdq .marker.cross{stroke:#333333;}#mermaid-svg-9e15mMBkQanWQkdq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9e15mMBkQanWQkdq p{margin:0;}#mermaid-svg-9e15mMBkQanWQkdq .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9e15mMBkQanWQkdq text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-9e15mMBkQanWQkdq .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9e15mMBkQanWQkdq .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-9e15mMBkQanWQkdq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-9e15mMBkQanWQkdq .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-9e15mMBkQanWQkdq #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-9e15mMBkQanWQkdq .sequenceNumber{fill:white;}#mermaid-svg-9e15mMBkQanWQkdq #sequencenumber{fill:#333;}#mermaid-svg-9e15mMBkQanWQkdq #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-9e15mMBkQanWQkdq .messageText{fill:#333;stroke:none;}#mermaid-svg-9e15mMBkQanWQkdq .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9e15mMBkQanWQkdq .labelText,#mermaid-svg-9e15mMBkQanWQkdq .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-9e15mMBkQanWQkdq .loopText,#mermaid-svg-9e15mMBkQanWQkdq .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-9e15mMBkQanWQkdq .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9e15mMBkQanWQkdq .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9e15mMBkQanWQkdq .noteText,#mermaid-svg-9e15mMBkQanWQkdq .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-9e15mMBkQanWQkdq .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9e15mMBkQanWQkdq .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9e15mMBkQanWQkdq .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9e15mMBkQanWQkdq .actorPopupMenu{position:absolute;}#mermaid-svg-9e15mMBkQanWQkdq .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-9e15mMBkQanWQkdq .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9e15mMBkQanWQkdq .actor-man circle,#mermaid-svg-9e15mMBkQanWQkdq line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-9e15mMBkQanWQkdq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务线程全盘挂起 (STW) par 多线程并行回收 正常运行业务代码,分配对象 1 触发 STW,暂停所有业务线程! 2 并行复制 / 标记整理分块 1 3 并行复制 / 标记整理分块 2 4 并行复制 / 标记整理分块 n 5 完成 6 完成 7 完成 8 所有 GC 线程完成,解除 STW,恢复业务 9

在算法层面:

  • Parallel Scavenge(新生代) :同样采用复制算法,但将 Eden 和 Survivor 区的存活对象复制任务切分成多个工作分块,由多个 GC 线程并行复制与指针更新。
  • Parallel Old(老年代) :在 JDK 1.6 开始提供,用于替代早期只能搭配 Serial Old 的尴尬局面。采用多线程并行的标记-整理算法,多个线程协同对老年代进行分段标记与紧凑整理。

2.2 为什么 Java 8 会把它选为默认收集器

在 Java 8 中,JVM 默认开启的垃圾收集器正是 Parallel Scavenge + Parallel Old。

原因在于其核心设计目标:吞吐量优先(Throughput Collector)。

吞吐量的计算公式非常直观:

吞吐量 = 运行用户代码时间 运行用户代码时间 + 垃圾收集时间 \text{吞吐量} = \frac{\text{运行用户代码时间}}{\text{运行用户代码时间} + \text{垃圾收集时间}} 吞吐量=运行用户代码时间+垃圾收集时间运行用户代码时间

例如,系统运行了 100 分钟,其中垃圾收集花掉了 1 分钟,那么吞吐量就是 99%。

低延迟收集器(如 CMS)为了降低单次停顿时间,需要在后台与业务线程抢占 CPU 算力,这会导致整体计算耗时拉长,吞吐量反而下降。

而 Parallel 收集器反其道而行之:在日常运行中,完全不干扰业务线程;一旦内存耗尽,利用所有的 CPU 算力以最高并行度全力以赴完成清理,把总的垃圾收集时间压缩到最短。

对于后台计算、大数据批处理、离线分析以及科学运算等业务,单次停顿 100 毫秒还是 300 毫秒并不敏感,最在乎的是这批数据能否在最短的时间内算完。Parallel 收集器凭借强悍的 CPU 利用率和极低的管理额外开销,成为这一时期的吞吐量王者。

2.3 吞吐量优先背后的自适应调节

Parallel Scavenge 收集器有一个非常突出的特性,叫做自适应调节策略(UseAdaptiveSizePolicy)。

在开启这个策略后,开发者甚至不需要手动指定新生代大小(-Xmn)、Eden 与 Survivor 的比例(-XX:SurvivorRatio)以及晋升老年代的对象年龄阈值(-XX:MaxTenuringThreshold)。JVM 会根据当前的运行监控情况,动态调整这些细节参数,以便在吞吐量与停顿时间之间寻求最优解。

JVM 提供了三个核心控制参数来指导收集器的行为:

  1. -XX:MaxGCPauseMillis :最大垃圾收集停顿时间目标。参数值为大于 0 的毫秒数。收集器会尽量把每次停顿控制在该值以内。但要注意,这个参数不是魔法,收集器为了达到更短的停顿,通常会牺牲吞吐量,通过悄悄缩小新生代空间来实现(新生代变小,存活对象少,单次复制快,但 GC 触发频率会大幅增加)。
  2. -XX:GCTimeRatio :吞吐量大小目标。参数值为 0 到 100 的整数,默认值为 99。公式为 1 / ( 1 + n ) 1 / (1 + n) 1/(1+n)。如果设置为 19,表示允许最大 5%( 1 / 20 1 / 20 1/20)的时间用于垃圾回收。
  3. -XX:+UseAdaptiveSizePolicy:开启自适应调节策略(Java 8 下配合 Parallel 收集器默认开启)。

#mermaid-svg-W81rpkxz9lvhtVc7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-W81rpkxz9lvhtVc7 .error-icon{fill:#552222;}#mermaid-svg-W81rpkxz9lvhtVc7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-W81rpkxz9lvhtVc7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-W81rpkxz9lvhtVc7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-W81rpkxz9lvhtVc7 .marker.cross{stroke:#333333;}#mermaid-svg-W81rpkxz9lvhtVc7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-W81rpkxz9lvhtVc7 p{margin:0;}#mermaid-svg-W81rpkxz9lvhtVc7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-W81rpkxz9lvhtVc7 .cluster-label text{fill:#333;}#mermaid-svg-W81rpkxz9lvhtVc7 .cluster-label span{color:#333;}#mermaid-svg-W81rpkxz9lvhtVc7 .cluster-label span p{background-color:transparent;}#mermaid-svg-W81rpkxz9lvhtVc7 .label text,#mermaid-svg-W81rpkxz9lvhtVc7 span{fill:#333;color:#333;}#mermaid-svg-W81rpkxz9lvhtVc7 .node rect,#mermaid-svg-W81rpkxz9lvhtVc7 .node circle,#mermaid-svg-W81rpkxz9lvhtVc7 .node ellipse,#mermaid-svg-W81rpkxz9lvhtVc7 .node polygon,#mermaid-svg-W81rpkxz9lvhtVc7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-W81rpkxz9lvhtVc7 .rough-node .label text,#mermaid-svg-W81rpkxz9lvhtVc7 .node .label text,#mermaid-svg-W81rpkxz9lvhtVc7 .image-shape .label,#mermaid-svg-W81rpkxz9lvhtVc7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-W81rpkxz9lvhtVc7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-W81rpkxz9lvhtVc7 .rough-node .label,#mermaid-svg-W81rpkxz9lvhtVc7 .node .label,#mermaid-svg-W81rpkxz9lvhtVc7 .image-shape .label,#mermaid-svg-W81rpkxz9lvhtVc7 .icon-shape .label{text-align:center;}#mermaid-svg-W81rpkxz9lvhtVc7 .node.clickable{cursor:pointer;}#mermaid-svg-W81rpkxz9lvhtVc7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-W81rpkxz9lvhtVc7 .arrowheadPath{fill:#333333;}#mermaid-svg-W81rpkxz9lvhtVc7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-W81rpkxz9lvhtVc7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-W81rpkxz9lvhtVc7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W81rpkxz9lvhtVc7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-W81rpkxz9lvhtVc7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W81rpkxz9lvhtVc7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-W81rpkxz9lvhtVc7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-W81rpkxz9lvhtVc7 .cluster text{fill:#333;}#mermaid-svg-W81rpkxz9lvhtVc7 .cluster span{color:#333;}#mermaid-svg-W81rpkxz9lvhtVc7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-W81rpkxz9lvhtVc7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-W81rpkxz9lvhtVc7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-W81rpkxz9lvhtVc7 .icon-shape,#mermaid-svg-W81rpkxz9lvhtVc7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-W81rpkxz9lvhtVc7 .icon-shape p,#mermaid-svg-W81rpkxz9lvhtVc7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-W81rpkxz9lvhtVc7 .icon-shape .label rect,#mermaid-svg-W81rpkxz9lvhtVc7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-W81rpkxz9lvhtVc7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-W81rpkxz9lvhtVc7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-W81rpkxz9lvhtVc7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Parallel 自适应调节流程
否,单次停顿偏长
否,吞吐量偏低
是
设置目标参数

-XX:MaxGCPauseMillis 或 -XX:GCTimeRatio
运行应用并统计 GC 耗时与频率
是否达到预期目标?
自动微调堆结构:

适度缩减新生代或调整 SurvivorRatio
自动扩容堆或推迟 GC 触发时机
保持当前代际比例继续运行

三、两代经典收集器的核心差异与真实取舍

理解了单线程与多线程的底层运作机制,我们可以将这两组经典收集器放在一起进行横向比对。

3.1 核心机制与参数对照

两组收集器在分代算法、线程模型与适用场景上有着清晰的界限:

对比维度 Serial + Serial Old Parallel Scavenge + Parallel Old
年轻代收集器及算法 Serial(单线程复制算法) Parallel Scavenge(多线程并行复制算法)
老年代收集器及算法 Serial Old(单线程标记-整理算法) Parallel Old(多线程并行标记-整理算法)
GC 线程数量 始终为 1 个线程 默认通常与 CPU 核心数相同(可配置)
是否触发 STW 是,全盘暂停用户线程 是,全盘暂停用户线程
核心优化目标 简单轻量、零并发开销、最小内存占用 最大化利用多核 CPU 算力,提升整体系统吞吐量
启用参数 -XX:+UseSerialGC -XX:+UseParallelGC
最适合的硬件环境 单核或双核 CPU、小内存(几十 MB 到几百 MB) 多核 CPU(4 核及以上)、中大堆内存(GB 级别)

在 Java 代码中,我们可以通过获取运行时管理接口来验证当前正在运行的垃圾收集器:

java 复制代码
package com.crayontech.jvm;

import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.List;

public class GarbageCollectorChecker {

    public static void main(String[] args) {
        List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
        System.out.println("当前 JVM 启用的垃圾收集器如下:");
        for (GarbageCollectorMXBean gcBean : gcBeans) {
            System.out.println("名称: " + gcBean.getName() 
                    + ",负责内存池: " + String.join(", ", gcBean.getMemoryPoolNames()));
        }
    }
}

当我们使用不同参数启动该程序时:

  • 添加启动参数 -XX:+UseSerialGC 时,控制台打印出年轻代为 Copy,老年代为 MarkSweepCompact(即 Serial Old 的底层名称)。
  • 添加启动参数 -XX:+UseParallelGC 时,控制台打印出年轻代为 PS Scavenge,老年代为 PS MarkSweep。

3.2 并行并不等于不卡顿

在面试或架构选型中,经常有人把 Parallel 的"快"理解为"无感"。

必须清醒地认识到:Parallel 收集器并没有缩短单次回收必须处理的工作总量。

一个 4 GB 的堆内存里如果有 500 MB 的存活对象需要移动,该遍历的引用链一条不少,该拷贝的内存字节一个不少。Parallel 的快,是用多个 CPU 核心同时分摊了原本属于一个人的搬运苦力。

但在并发要求极高的在线即时交易、Web 前端网关等交互场景中,Parallel 的表现往往差强人意。一旦老年代发生一次完整的 Full GC,即使 8 个核心全力狂飙,由于标记-整理算法需要多轮扫描和对象指针修正,停顿几百毫秒甚至上秒依然不可避免。

这就是为什么后来发展出了 CMS 和 G1:它们宁愿牺牲一部分 CPU 算力让出吞吐量,也要把长停顿切碎,换取更平稳的响应延迟。

3.3 什么时候单线程反而比多线程更快

既然 Parallel 在多核机器上优势明显,那 Serial 还有存在的价值吗?

答案是肯定的。在以下两种现实场景中,Serial 的实际性能甚至会直接击败 Parallel:

  1. 单核或 1 vCPU 规格的容器环境 :
    在微服务与 Serverless 盛行的今天,许多边缘服务、轻量批处理任务会被部署在云厂商规格为 1 核 512 MB 或 1 核 1 GB 的轻量 Docker 容器中。
    在单物理核的环境下,如果强行启用 Parallel 收集器,JVM 会试图初始化多个 GC 线程协同工作。这不仅会白白消耗内存去维护线程栈与协调锁,CPU 还会因为不同 GC 线程之间的频繁时间片轮转而产生高昂的上下文切换损耗。此时,没有任何额外负担的 Serial 反而跑得最快。
  2. 堆内存极小(低于 100 MB)的客户端或工具类程序 :
    对于一些小型的本地命令行工具、嵌入式 Java 程序,堆中存活的对象总共也就十几兆。一个单线程只需两三毫秒就能扫完整个堆,根本不需要调动整个团队大动干戈。

#mermaid-svg-Vtw5LW65bdEAUJu2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Vtw5LW65bdEAUJu2 .error-icon{fill:#552222;}#mermaid-svg-Vtw5LW65bdEAUJu2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Vtw5LW65bdEAUJu2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .marker.cross{stroke:#333333;}#mermaid-svg-Vtw5LW65bdEAUJu2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Vtw5LW65bdEAUJu2 p{margin:0;}#mermaid-svg-Vtw5LW65bdEAUJu2 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .cluster-label text{fill:#333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .cluster-label span{color:#333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .cluster-label span p{background-color:transparent;}#mermaid-svg-Vtw5LW65bdEAUJu2 .label text,#mermaid-svg-Vtw5LW65bdEAUJu2 span{fill:#333;color:#333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .node rect,#mermaid-svg-Vtw5LW65bdEAUJu2 .node circle,#mermaid-svg-Vtw5LW65bdEAUJu2 .node ellipse,#mermaid-svg-Vtw5LW65bdEAUJu2 .node polygon,#mermaid-svg-Vtw5LW65bdEAUJu2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Vtw5LW65bdEAUJu2 .rough-node .label text,#mermaid-svg-Vtw5LW65bdEAUJu2 .node .label text,#mermaid-svg-Vtw5LW65bdEAUJu2 .image-shape .label,#mermaid-svg-Vtw5LW65bdEAUJu2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Vtw5LW65bdEAUJu2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Vtw5LW65bdEAUJu2 .rough-node .label,#mermaid-svg-Vtw5LW65bdEAUJu2 .node .label,#mermaid-svg-Vtw5LW65bdEAUJu2 .image-shape .label,#mermaid-svg-Vtw5LW65bdEAUJu2 .icon-shape .label{text-align:center;}#mermaid-svg-Vtw5LW65bdEAUJu2 .node.clickable{cursor:pointer;}#mermaid-svg-Vtw5LW65bdEAUJu2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .arrowheadPath{fill:#333333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Vtw5LW65bdEAUJu2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vtw5LW65bdEAUJu2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Vtw5LW65bdEAUJu2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vtw5LW65bdEAUJu2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Vtw5LW65bdEAUJu2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Vtw5LW65bdEAUJu2 .cluster text{fill:#333;}#mermaid-svg-Vtw5LW65bdEAUJu2 .cluster span{color:#333;}#mermaid-svg-Vtw5LW65bdEAUJu2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Vtw5LW65bdEAUJu2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Vtw5LW65bdEAUJu2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Vtw5LW65bdEAUJu2 .icon-shape,#mermaid-svg-Vtw5LW65bdEAUJu2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Vtw5LW65bdEAUJu2 .icon-shape p,#mermaid-svg-Vtw5LW65bdEAUJu2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Vtw5LW65bdEAUJu2 .icon-shape .label rect,#mermaid-svg-Vtw5LW65bdEAUJu2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Vtw5LW65bdEAUJu2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Vtw5LW65bdEAUJu2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Vtw5LW65bdEAUJu2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1 核 / 单 vCPU 容器
4 核及以上服务器
后台数据计算 / 离线批处理 / 追求极致吞吐
在线交易 / Web API / 严控接口响应延迟
评估应用场景与运行环境
CPU 核心数是多少?
优先选择 Serial GC

(-XX:+UseSerialGC)

无线程切换开销,最轻量
业务核心诉求是什么?
优先选择 Parallel GC

(-XX:+UseParallelGC)

榨干多核 CPU,总耗时最短
建议转向 G1 或 ZGC

以部分吞吐为代价,换取低延迟

四、写在最后

垃圾收集器的演进并不是简单的"新架构碾压旧架构"。

从 Serial 的单线程单打独斗,到 Parallel 的多核并行冲锋,背后反映的是底层硬件多核化、内存海量化与业务需求多元化的必然结果。

单线程的 Serial 凭借纯粹的结构与零同步损耗,在微型容器和单核受限设备中依然保有一席之地;多线程并行的 Parallel 凭借强悍的 CPU 吞吐转化率,至今依然是离线批处理和后台计算场景的骨干。

弄清楚它们各自的代价与适用边界,在实际调优与选型时就不会盲目跟风。

如果你觉得本文对你理清这两组经典垃圾收集器的来龙去脉有所启发,记得点个关注,后续我们继续深入拆解低延迟时代的 CMS 与 G1 收集器。

相关推荐
wuminyu1 小时前
JVM虚拟线程的底层实现原理分析
java·linux·c语言·jvm·c++
M78佐菲1 小时前
ARM学习笔记(9)
linux·arm开发·笔记·嵌入式硬件·学习
阳光九叶草LXGZXJ2 小时前
达梦数据库-学习-67-SSL加密认证
linux·运维·数据库·sql·学习·ssl
弹简特2 小时前
【Java项目-企悦抽】13-奖品管理模块-奖品列表和创建奖品的实现
java·开发语言·网络·springboot
東隅已逝,桑榆非晚2 小时前
priority_queue的介绍和使用
c++·经验分享·笔记·学习
小蒜学长2 小时前
基于Spring Boot+Vue的“禾源”农产品销售系统设计与实现(代码+数据库+LW)
java·spring boot·后端·农产品销售系统·产品溯源
光年20002 小时前
Mock-Server开箱即用的Mock服务
java·maven·mock·jetty
弹简特2 小时前
【Java项目-企悦抽】14-活动管理模块01-创建活动的实现
java·开发语言·springboot
量子-Alex2 小时前
【大模型强化学习后训练】强化学习后训练算力应投向何处?模型规模、搜索、学习与反馈
人工智能·学习