mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-26 13:16:41 +08:00
1359 lines
36 KiB
HTML
1359 lines
36 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>19 GC 日志解读与分析(实例分析中篇).md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/01 阅读此专栏的正确姿势.md.html">01 阅读此专栏的正确姿势.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/02 环境准备:千里之行,始于足下.md.html">02 环境准备:千里之行,始于足下.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/03 常用性能指标:没有量化,就没有改进.md.html">03 常用性能指标:没有量化,就没有改进.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/04 JVM 基础知识:不积跬步,无以至千里.md.html">04 JVM 基础知识:不积跬步,无以至千里.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/05 Java 字节码技术:不积细流,无以成江河.md.html">05 Java 字节码技术:不积细流,无以成江河.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/06 Java 类加载器:山不辞土,故能成其高.md.html">06 Java 类加载器:山不辞土,故能成其高.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/07 Java 内存模型:海不辞水,故能成其深.md.html">07 Java 内存模型:海不辞水,故能成其深.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/08 JVM 启动参数详解:博观而约取、厚积而薄发.md.html">08 JVM 启动参数详解:博观而约取、厚积而薄发.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/09 JDK 内置命令行工具:工欲善其事,必先利其器.md.html">09 JDK 内置命令行工具:工欲善其事,必先利其器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/10 JDK 内置图形界面工具:海阔凭鱼跃,天高任鸟飞.md.html">10 JDK 内置图形界面工具:海阔凭鱼跃,天高任鸟飞.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/11 JDWP 简介:十步杀一人,千里不留行.md.html">11 JDWP 简介:十步杀一人,千里不留行.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/12 JMX 与相关工具:山高月小,水落石出.md.html">12 JMX 与相关工具:山高月小,水落石出.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/13 常见的 GC 算法(GC 的背景与原理).md.html">13 常见的 GC 算法(GC 的背景与原理).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/14 常见的 GC 算法(ParallelCMSG1).md.html">14 常见的 GC 算法(ParallelCMSG1).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/15 Java11 ZGC 和 Java12 Shenandoah 介绍:苟日新、日日新、又日新.md.html">15 Java11 ZGC 和 Java12 Shenandoah 介绍:苟日新、日日新、又日新.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/16 Oracle GraalVM 介绍:会当凌绝顶、一览众山小.md.html">16 Oracle GraalVM 介绍:会当凌绝顶、一览众山小.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/17 GC 日志解读与分析(基础配置).md.html">17 GC 日志解读与分析(基础配置).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/18 GC 日志解读与分析(实例分析上篇).md.html">18 GC 日志解读与分析(实例分析上篇).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/JVM 核心技术 32 讲(完)/19 GC 日志解读与分析(实例分析中篇).md.html">19 GC 日志解读与分析(实例分析中篇).md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/20 GC 日志解读与分析(实例分析下篇).md.html">20 GC 日志解读与分析(实例分析下篇).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/21 GC 日志解读与分析(番外篇可视化工具).md.html">21 GC 日志解读与分析(番外篇可视化工具).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/22 JVM 的线程堆栈等数据分析:操千曲而后晓声、观千剑而后识器.md.html">22 JVM 的线程堆栈等数据分析:操千曲而后晓声、观千剑而后识器.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/23 内存分析与相关工具上篇(内存布局与分析工具).md.html">23 内存分析与相关工具上篇(内存布局与分析工具).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/24 内存分析与相关工具下篇(常见问题分析).md.html">24 内存分析与相关工具下篇(常见问题分析).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/25 FastThread 相关的工具介绍:欲穷千里目,更上一层楼.md.html">25 FastThread 相关的工具介绍:欲穷千里目,更上一层楼.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/26 面临复杂问题时的几个高级工具:它山之石,可以攻玉.md.html">26 面临复杂问题时的几个高级工具:它山之石,可以攻玉.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/27 JVM 问题排查分析上篇(调优经验).md.html">27 JVM 问题排查分析上篇(调优经验).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/28 JVM 问题排查分析下篇(案例实战).md.html">28 JVM 问题排查分析下篇(案例实战).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/29 GC 疑难情况问题排查与分析(上篇).md.html">29 GC 疑难情况问题排查与分析(上篇).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/30 GC 疑难情况问题排查与分析(下篇).md.html">30 GC 疑难情况问题排查与分析(下篇).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/31 JVM 相关的常见面试问题汇总:运筹策帷帐之中,决胜于千里之外.md.html">31 JVM 相关的常见面试问题汇总:运筹策帷帐之中,决胜于千里之外.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/32 应对容器时代面临的挑战:长风破浪会有时、直挂云帆济沧海.md.html">32 应对容器时代面临的挑战:长风破浪会有时、直挂云帆济沧海.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>19 GC 日志解读与分析(实例分析中篇)</h1>
|
||
|
||
<h3>CMS 的 GC 日志解读</h3>
|
||
|
||
<p>CMS 也可称为“并发标记清除垃圾收集器”。其设计目标是避免在老年代 GC 时出现长时间的卡顿。默认情况下,CMS 使用的并发线程数等于 CPU 内核数的 1/4。</p>
|
||
|
||
<p>通过以下选项来指定 CMS 垃圾收集器:</p>
|
||
|
||
<pre><code class="language-shell">-XX:+UseConcMarkSweepGC
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>如果 CPU 资源受限,CMS 的吞吐量会比并行 GC 差一些。示例:</p>
|
||
|
||
<pre><code class="language-shell"># 请注意命令行启动时没有换行,此处是方便大家阅读。
|
||
|
||
java -XX:+UseConcMarkSweepGC
|
||
|
||
-Xms512m
|
||
|
||
-Xmx512m
|
||
|
||
-Xloggc:gc.demo.log
|
||
|
||
-XX:+PrintGCDetails
|
||
|
||
-XX:+PrintGCDateStamps
|
||
|
||
demo.jvm0204.GCLogAnalysis
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>和前面分析的串行 GC/并行 GC 一样,我们将程序启动起来,看看 CMS 算法生成的 GC 日志是什么样子:</p>
|
||
|
||
<pre><code class="language-shell">Java HotSpot(TM) 64-Bit Server VM (25.162-b12) 。。。
|
||
|
||
Memory: 4k page,physical 16777216k(1168104k free)
|
||
|
||
|
||
|
||
CommandLine flags:
|
||
|
||
-XX:InitialHeapSize=536870912 -XX:MaxHeapSize=536870912
|
||
|
||
-XX:MaxNewSize=178958336 -XX:MaxTenuringThreshold=6
|
||
|
||
-XX:NewSize=178958336 -XX:OldPLABSize=16 -XX:OldSize=357912576
|
||
|
||
-XX:+PrintGC -XX:+PrintGCDateStamps
|
||
|
||
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
|
||
|
||
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops
|
||
|
||
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
|
||
|
||
|
||
|
||
2019-12-22T00:00:31.865-0800: 1.067:
|
||
|
||
[GC (Allocation Failure)
|
||
|
||
2019-12-22T00:00:31.865-0800: 1.067:
|
||
|
||
[ParNew: 136418K->17311K(157248K),0.0233955 secs]
|
||
|
||
442378K->360181K(506816K),0.0234719 secs]
|
||
|
||
[Times: user=0.10 sys=0.02,real=0.02 secs]
|
||
|
||
|
||
|
||
2019-12-22T00:00:31.889-0800: 1.091:
|
||
|
||
[GC (CMS Initial Mark)
|
||
|
||
[1 CMS-initial-mark: 342870K(349568K)]
|
||
|
||
363883K(506816K),0.0002262 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.889-0800: 1.091:
|
||
|
||
[CMS-concurrent-mark-start]
|
||
|
||
2019-12-22T00:00:31.890-0800: 1.092:
|
||
|
||
[CMS-concurrent-mark: 0.001/0.001 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.01 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.092:
|
||
|
||
[CMS-concurrent-preclean-start]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-preclean: 0.001/0.001 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-abortable-preclean-start]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[GC (CMS Final Remark)
|
||
|
||
[YG occupancy: 26095 K (157248 K)]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[Rescan (parallel) ,0.0002680 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[weak refs processing,0.0000230 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[class unloading,0.0004008 secs]
|
||
|
||
2019-12-22T00:00:31.892-0800: 1.094:
|
||
|
||
[scrub symbol table,0.0006072 secs]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[scrub string table,0.0001769 secs]
|
||
|
||
[1 CMS-remark: 342870K(349568K)]
|
||
|
||
368965K(506816K),0.0015928 secs]
|
||
|
||
[Times: user=0.01 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-sweep-start]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-sweep: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-reset-start]
|
||
|
||
2019-12-22T00:00:31.894-0800: 1.096:
|
||
|
||
[CMS-concurrent-reset: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>这只是摘录的一部分 GC 日志。比起串行 GC/并行 GC 来说,CMS 的日志信息复杂了很多,这一方面是因为 CMS 拥有更加精细的 GC 步骤,另一方面 GC 日志很详细就意味着暴露出来的信息也就更全面细致。</p>
|
||
|
||
<h4><strong>Minor GC 日志分析</strong></h4>
|
||
|
||
<p>最前面的几行日志是清理年轻代的 Minor GC 事件:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.865-0800: 1.067:
|
||
|
||
[GC (Allocation Failure)
|
||
|
||
2019-12-22T00:00:31.865-0800: 1.067:
|
||
|
||
[ParNew: 136418K->17311K(157248K),0.0233955 secs]
|
||
|
||
442378K->360181K(506816K),0.0234719 secs]
|
||
|
||
[Times: user=0.10 sys=0.02,real=0.02 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>我们一起来解读:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>2019-12-22T00:00:31.865-0800: 1.067</code>:GC 事件开始的时间。</li>
|
||
|
||
<li><code>GC (Allocation Failure)</code>:用来区分 Minor GC 还是 Full GC 的标志。<code>GC</code> 表明这是一次“小型 GC”;<code>Allocation Failure</code> 表示触发 GC 的原因。本次 GC 事件,是由于年轻代可用空间不足,新对象的内存分配失败引起的。</li>
|
||
|
||
<li><code>[ParNew: 136418K->17311K(157248K),0.0233955 secs]</code>:其中 <code>ParNew</code> 是垃圾收集器的名称,对应的就是前面日志中打印的 <code>-XX:+UseParNewGC</code> 这个命令行标志。表示在年轻代中使用的“并行的标记—复制(mark-copy)”垃圾收集器,专门设计了用来配合 CMS 垃圾收集器,因为 CMS 只负责回收老年代。后面的数字表示 GC 前后的年轻代使用量变化,以及年轻代的总大小。<code>0.0233955 secs</code> 是消耗的时间。</li>
|
||
|
||
<li><code>442378K->360181K(506816K),0.0234719 secs</code>:表示 GC 前后堆内存的使用量变化,以及堆内存空间的大小。消耗的时间是 <code>0.0234719 secs</code>,和前面的 ParNew 部分的时间基本上一样。</li>
|
||
|
||
<li><code>[Times: user=0.10 sys=0.02,real=0.02 secs]</code>:GC 事件的持续时间。<code>user</code> 是 GC 线程所消耗的总 CPU 时间;<code>sys</code> 是操作系统调用和系统等待事件消耗的时间;应用程序实际暂停的时间 <code>real ~= (user + sys)/GC线程数</code>。我的机器是 4 核 8 线程,而这里是 6 倍的比例,因为总有一定比例的处理过程是不能并行执行的。</li>
|
||
|
||
</ol>
|
||
|
||
<p>进一步计算和分析可以得知,在 GC 之前,年轻代使用量为 136418K/157248K=86%。堆内存的使用率为 442378K/506816K=87%。稍微估算一下,老年代的使用率为:(442378K-136418K)/(506816K-157248K)=(305960K /349568K)=87%。这里是凑巧了,GC 之前 3 个比例都在 87% 左右。</p>
|
||
|
||
<p>GC 之后呢?年轻代使用量为 17311K ~= 17%,下降了 119107K。堆内存使用量为 360181K ~= 71%,只下降了 82197K。两个下降值相减,就是年轻代提升到老年代的内存量:119107-82197=36910K。</p>
|
||
|
||
<p>那么老年代空间有多大?老年代使用量是多少?正在阅读的同学,请开动脑筋,用这些数字算一下。</p>
|
||
|
||
<p>此次 GC 的内存变化示意图为:</p>
|
||
|
||
<p><img src="assets/13640c30-63ac-11ea-a283-8f19fc193c49" alt="4438116.png" /></p>
|
||
|
||
<p>哇塞,这个数字不得了,老年代使用量 98% 了,非常高了。后面紧跟着就是一条 Full GC 的日志,请接着往下看。</p>
|
||
|
||
<h4><strong>Full GC 日志分析</strong></h4>
|
||
|
||
<p>实际上这次截取的年轻代 GC 日志和 FullGC 日志是紧连着的,我们从间隔时间也能大致看出来,<code>1.067 + 0.02secs ~ 1.091</code>。</p>
|
||
|
||
<p>CMS 的日志是一种完全不同的格式,并且很长,因为 CMS 对老年代进行垃圾收集时每个阶段都会有自己的日志。为了简洁,我们将对这部分日志按照阶段依次介绍。</p>
|
||
|
||
<p>首先来看 CMS 这次 FullGC 的日志:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.889-0800: 1.091:
|
||
|
||
[GC (CMS Initial Mark)
|
||
|
||
[1 CMS-initial-mark: 342870K(349568K)]
|
||
|
||
363883K(506816K),0.0002262 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.889-0800: 1.091:
|
||
|
||
[CMS-concurrent-mark-start]
|
||
|
||
2019-12-22T00:00:31.890-0800: 1.092:
|
||
|
||
[CMS-concurrent-mark: 0.001/0.001 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.01 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.092:
|
||
|
||
[CMS-concurrent-preclean-start]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-preclean: 0.001/0.001 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-abortable-preclean-start]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[GC (CMS Final Remark)
|
||
|
||
[YG occupancy: 26095 K (157248 K)]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[Rescan (parallel) ,0.0002680 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[weak refs processing,0.0000230 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[class unloading,0.0004008 secs]
|
||
|
||
2019-12-22T00:00:31.892-0800: 1.094:
|
||
|
||
[scrub symbol table,0.0006072 secs]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[scrub string table,0.0001769 secs]
|
||
|
||
[1 CMS-remark: 342870K(349568K)]
|
||
|
||
368965K(506816K),0.0015928 secs]
|
||
|
||
[Times: user=0.01 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-sweep-start]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-sweep: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-reset-start]
|
||
|
||
2019-12-22T00:00:31.894-0800: 1.096:
|
||
|
||
[CMS-concurrent-reset: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>在实际运行中,CMS 在进行老年代的并发垃圾回收时,可能会伴随着多次年轻代的 Minor GC(想想是为什么)。在这种情况下,Full GC 的日志中可能会掺杂着多次 Minor GC 事件。</p>
|
||
|
||
<p><strong>阶段 1:Initial Mark(初始标记)</strong></p>
|
||
|
||
<p>前面章节提到过,这个阶段伴随着 STW 暂停。初始标记的目标是标记所有的根对象,包括 GC ROOT 直接引用的对象,以及被年轻代中所有存活对象所引用的对象。后面这部分也非常重要,因为老年代是独立进行回收的。</p>
|
||
|
||
<p>先看这个阶段的日志:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.889-0800: 1.091:
|
||
|
||
[GC (CMS Initial Mark)
|
||
|
||
[1 CMS-initial-mark: 342870K(349568K)]
|
||
|
||
363883K(506816K), 0.0002262 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>让我们简单解读一下:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>2019-12-22T00:00:31.889-0800: 1.091:</code>:时间部分就不讲了,参考前面的解读。后面的其他阶段也一样,不再进行重复介绍。</li>
|
||
|
||
<li><code>CMS Initial Mark</code>:这个阶段的名称为“Initial Mark”,会标记所有的 GC Root。</li>
|
||
|
||
<li><code>[1 CMS-initial-mark: 342870K(349568K)]</code>:这部分数字表示老年代的使用量,以及老年代的空间大小。</li>
|
||
|
||
<li><code>363883K(506816K),0.0002262 secs</code>:当前堆内存的使用量,以及可用堆的大小、消耗的时间。可以看出这个时间非常短,只有 0.2 毫秒左右,因为要标记的这些 Roo 数量很少。</li>
|
||
|
||
<li><code>[Times: user=0.00 sys=0.00,real=0.00 secs]</code>:初始标记事件暂停的时间,可以看到可以忽略不计。</li>
|
||
|
||
</ol>
|
||
|
||
<p><strong>阶段 2:Concurrent Mark(并发标记)</strong></p>
|
||
|
||
<p>在并发标记阶段,CMS 从前一阶段“Initial Mark”找到的 ROOT 开始算起,遍历老年代并标记所有的存活对象。</p>
|
||
|
||
<p>看看这个阶段的 GC 日志:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.889-0800: 1.091:
|
||
|
||
[CMS-concurrent-mark-start]
|
||
|
||
2019-12-22T00:00:31.890-0800: 1.092:
|
||
|
||
[CMS-concurrent-mark: 0.001/0.001 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.01 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>简单解读一下:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>CMS-concurrent-mark</code>:指明了是 CMS 垃圾收集器所处的阶段为并发标记(“Concurrent Mark”)。</li>
|
||
|
||
<li><code>0.001/0.001 secs</code>:此阶段的持续时间,分别是 GC 线程消耗的时间和实际消耗的时间。</li>
|
||
|
||
<li><code>[Times: user=0.00 sys=0.00,real=0.01 secs]</code>:<code>Times</code> 对并发阶段来说这些时间并没多少意义,因为是从并发标记开始时刻计算的,而这段时间应用线程也在执行,所以这个时间只是一个大概的值。</li>
|
||
|
||
</ol>
|
||
|
||
<p><strong>阶段 3:Concurrent Preclean(并发预清理)</strong></p>
|
||
|
||
<p>此阶段同样是与应用线程并发执行的,不需要停止应用线程。</p>
|
||
|
||
<p>看看并发预清理阶段的 GC 日志:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.891-0800: 1.092:
|
||
|
||
[CMS-concurrent-preclean-start]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-preclean: 0.001/0.001 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>简单解读:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>CMS-concurrent-preclean</code>:表明这是并发预清理阶段的日志,这个阶段会统计前面的并发标记阶段执行过程中发生了改变的对象。</li>
|
||
|
||
<li><code>0.001/0.001 secs</code>:此阶段的持续时间,分别是 GC 线程运行时间和实际占用的时间。</li>
|
||
|
||
<li><code>[Times: user=0.00 sys=0.00,real=0.00 secs]</code>:Times 这部分对并发阶段来说没多少意义,因为是从开始时间计算的,而这段时间内不仅 GC 线程在执行并发预清理,应用线程也在运行。</li>
|
||
|
||
</ol>
|
||
|
||
<p><strong>阶段 4:Concurrent Abortable Preclean(可取消的并发预清理)</strong></p>
|
||
|
||
<p>此阶段也不停止应用线程,尝试在会触发 STW 的 Final Remark 阶段开始之前,尽可能地多干一些活。</p>
|
||
|
||
<p>本阶段的具体时间取决于多种因素,因为它循环做同样的事情,直到满足某一个退出条件(如迭代次数、有用工作量、消耗的系统时间等等)。</p>
|
||
|
||
<p>看看 GC 日志:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-abortable-preclean-start]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[CMS-concurrent-abortable-preclean: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>简单解读:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>CMS-concurrent-abortable-preclean</code>:指示此阶段的名称:“Concurrent Abortable Preclean”。</li>
|
||
|
||
<li><code>0.000/0.000 secs</code>:此阶段 GC 线程的运行时间和实际占用的时间。从本质上讲,GC 线程试图在执行 STW 暂停之前等待尽可能长的时间。默认条件下,此阶段可以持续最长 5 秒钟的时间。</li>
|
||
|
||
<li><code>[Times: user=0.00 sys=0.00,real=0.00 secs]</code>:“Times”这部分对并发阶段来说没多少意义,因为程序在并发阶段中持续运行。</li>
|
||
|
||
</ol>
|
||
|
||
<p>此阶段完成的工作可能对 STW 停顿的时间有较大影响,并且有许多重要的<a href="https://blogs.oracle.com/jonthecollector/entry/did_you_know">配置选项</a>和失败模式。</p>
|
||
|
||
<p><strong>阶段 5:Final Remark(最终标记)</strong></p>
|
||
|
||
<p>最终标记阶段是此次 GC 事件中的第二次(也是最后一次)STW 停顿。</p>
|
||
|
||
<p>本阶段的目标是完成老年代中所有存活对象的标记。因为之前的预清理阶段是并发执行的,有可能 GC 线程跟不上应用程序的修改速度。所以需要一次 STW 暂停来处理各种复杂的情况。</p>
|
||
|
||
<p>通常 CMS 会尝试在年轻代尽可能空的情况下执行 final remark 阶段,以免连续触发多次 STW 事件。</p>
|
||
|
||
<p>这部分的 GC 日志看起来稍微复杂一些:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[GC (CMS Final Remark)
|
||
|
||
[YG occupancy: 26095 K (157248 K)]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[Rescan (parallel) ,0.0002680 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[weak refs processing,0.0000230 secs]
|
||
|
||
2019-12-22T00:00:31.891-0800: 1.093:
|
||
|
||
[class unloading,0.0004008 secs]
|
||
|
||
2019-12-22T00:00:31.892-0800: 1.094:
|
||
|
||
[scrub symbol table,0.0006072 secs]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[scrub string table,0.0001769 secs]
|
||
|
||
[1 CMS-remark: 342870K(349568K)]
|
||
|
||
368965K(506816K),0.0015928 secs]
|
||
|
||
[Times: user=0.01 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>一起来进行解读:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>CMS Final Remark</code>:这是此阶段的名称,最终标记阶段,会标记老年代中所有的存活对象,包括此前的并发标记过程中创建/修改的引用。</li>
|
||
|
||
<li><code>YG occupancy: 26095 K (157248 K)</code>:当前年轻代的使用量和总容量。</li>
|
||
|
||
<li><code>[Rescan (parallel) ,0.0002680 secs]</code>:在程序暂停后进行重新扫描(Rescan),以完成存活对象的标记。这部分是并行执行的,消耗的时间为 0.0002680 秒。</li>
|
||
|
||
<li><code>weak refs processing,0.0000230 secs</code>:第一个子阶段,处理弱引用的持续时间。</li>
|
||
|
||
<li><code>class unloading,0.0004008 secs</code>:第二个子阶段,卸载不使用的类,以及持续时间。</li>
|
||
|
||
<li><code>scrub symbol table,0.0006072 secs</code>:第三个子阶段,清理符号表,即持有 class 级别 metadata 的符号表(symbol tables)。</li>
|
||
|
||
<li><code>scrub string table,0.0001769 secs</code>:第四个子阶段, 清理内联字符串对应的 string tables。</li>
|
||
|
||
<li><code>[1 CMS-remark: 342870K(349568K)]</code>:此阶段完成后老年代的使用量和总容量。</li>
|
||
|
||
<li><code>368965K(506816K),0.0015928 secs</code>:此阶段完成后,整个堆内存的使用量和总容量。</li>
|
||
|
||
<li><code>[Times: user=0.01 sys=0.00,real=0.00 secs]</code>:GC 事件的持续时间。</li>
|
||
|
||
</ol>
|
||
|
||
<p>在这 5 个标记阶段完成后,老年代中的所有存活对象都被标记上了,接下来 JVM 会将所有不使用的对象清除,以回收老年代空间。</p>
|
||
|
||
<p><strong>阶段 6:Concurrent Sweep(并发清除)</strong></p>
|
||
|
||
<p>此阶段与应用程序并发执行,不需要 STW 停顿。目的是删除不再使用的对象,并回收他们占用的内存空间。</p>
|
||
|
||
<p>看看这部分的 GC 日志:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-sweep-start]
|
||
|
||
2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-sweep: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>简单解读:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>CMS-concurrent-sweep</code>:此阶段的名称,“Concurrent Sweep”,并发清除老年代中所有未被标记的对象、也就是不再使用的对象,以释放内存空间。</li>
|
||
|
||
<li><code>0.000/0.000 secs</code>:此阶段的持续时间和实际占用的时间,这是一个四舍五入值,只精确到小数点后 3 位。</li>
|
||
|
||
<li><code>[Times: user=0.00 sys=0.00,real=0.00 secs]</code>:“Times”部分对并发阶段来说没有多少意义,因为是从并发标记开始时计算的,而这段时间内不仅是并发标记线程在执行,程序线程也在运行。</li>
|
||
|
||
</ol>
|
||
|
||
<p><strong>阶段 7:Concurrent Reset(并发重置)</strong></p>
|
||
|
||
<p>此阶段与应用程序线程并发执行,重置 CMS 算法相关的内部数据结构,下一次触发 GC 时就可以直接使用。</p>
|
||
|
||
<p>对应的日志为:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.893-0800: 1.095:
|
||
|
||
[CMS-concurrent-reset-start]
|
||
|
||
2019-12-22T00:00:31.894-0800: 1.096:
|
||
|
||
[CMS-concurrent-reset: 0.000/0.000 secs]
|
||
|
||
[Times: user=0.00 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>简单解读:</p>
|
||
|
||
<ol>
|
||
|
||
<li><code>CMS-concurrent-reset</code>:此阶段的名称,“Concurrent Reset”,重置 CMS 算法的内部数据结构,为下一次 GC 循环做准备。</li>
|
||
|
||
<li><code>0.000/0.000 secs</code>:此阶段的持续时间和实际占用的时间</li>
|
||
|
||
<li><code>[Times: user=0.00 sys=0.00,real=0.00 secs]</code>:“Times”部分对并发阶段来说没多少意义,因为是从并发标记开始时计算的,而这段时间内不仅 GC 线程在运行,程序也在运行。</li>
|
||
|
||
</ol>
|
||
|
||
<p>那么问题来了,CMS 之后老年代内存使用量是多少呢?很抱歉这里分析不了,只能通过后面的 Minor GC 日志来分析了。</p>
|
||
|
||
<p>例如本次运行,后面的 GC 日志是这样的:</p>
|
||
|
||
<pre><code class="language-shell">2019-12-22T00:00:31.921-0800: 1.123:
|
||
|
||
[GC (Allocation Failure) 2019-12-22T00:00:31.921-0800: 1.123:
|
||
|
||
[ParNew: 153242K->16777K(157248K), 0.0070050 secs]
|
||
|
||
445134K->335501K(506816K),
|
||
|
||
0.0070758 secs]
|
||
|
||
[Times: user=0.05 sys=0.00,real=0.00 secs]
|
||
|
||
|
||
|
||
</code></pre>
|
||
|
||
<p>参照前面年轻代 GC 日志的分析方法,我们推算出来,上面的 CMS Full GC 之后,老年代的使用量应该是:445134K-153242K=291892K,老年代的总容量 506816K-157248K=349568K,所以 Full GC 之后老年代的使用量占比是 291892K/349568K=83%。</p>
|
||
|
||
<p>这个占比不低。说明什么问题呢? 一般来说就是分配的内存小了,毕竟我们才指定了 512MB 的最大堆内存。</p>
|
||
|
||
<p>按照惯例,来一张 GC 前后的内存使用情况示意图:</p>
|
||
|
||
<p><img src="assets/7fad0940-63ad-11ea-9c08-6f91e6eaabb6" alt="3110993.png" /></p>
|
||
|
||
<p>总之,CMS 垃圾收集器在减少停顿时间上做了很多给力的工作,很大一部分 GC 线程是与应用线程并发运行的,不需要暂停应用线程,这样就可以在一般情况下每次暂停的时候较少。当然,CMS 也有一些缺点,其中最大的问题就是老年代的内存碎片问题,在某些情况下 GC 会有不可预测的暂停时间,特别是堆内存较大的情况下。</p>
|
||
|
||
<blockquote>
|
||
|
||
<p>透露一个学习 CMS 的诀窍:参考上面各个阶段的示意图,请同学们自己画一遍。</p>
|
||
|
||
</blockquote>
|
||
|
||
<p>本节的学习到此就结束了,下一节我们继续介绍 G1 日志分析。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/18 GC 日志解读与分析(实例分析上篇).md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/JVM 核心技术 32 讲(完)/20 GC 日志解读与分析(实例分析下篇).md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70996fea58d03d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|