Full GC

Stop the world, clean your mind.


  • 首页

  • 归档

记一次Full GC频繁排查过程

发表于 2019-03-27

排查过程

线上一台服务器从周一下午开始告警,告警内容为“Full GC比较频繁”。一开始大家都没有在意,猜想可能是部分业务压力有点大了,等高峰期过了就好了。

然而告警邮件的提醒一直响到了第二天。

周二中午,脆骨首先开始了排查,登录跳板机后,通过jstat工具查看GC情况,发现该应用的老年代占用100%,大约每5秒触发一次Full GC,但Full GC后没有效果,老年代占用依然为100%,据此我们推测应该是有大量业务对象滞留在了内存中。我们开始第一次尝试使用jmap来dump堆,以期通过分析堆中的对象来定位问题,然而第一次仅dump出18M的内容,分析后没有发现有价值的内容,考虑到老年代长期占用100%已经使得该应用无法正确响应线上请求了,我们重启了该应用,观察启动后的GC情况是正常的。

周二晚上下班后,我和脆骨再次登录服务器排查该问题。21时11分,应用日志显示有一家店启动了某一类型的活动,接下来的几分钟里,应用的老年代占用率迅速上升,并导致开始频繁Full GC,告警邮件也再次出现。到此我们初步确定了问题是由该类型的活动导致的,并第二次使用jmap将堆dump出来进行分析,这次成功dump出4.3G的hprof文件。

阅读全文 »

集群与分布式的区别

发表于 2018-05-07 | 更新于 2019-03-27

集群两大关键特性集群指的是一组服务实体,它们共同工作以提供比单个服务实体更具可扩展性和可用性的服务平台。 在客户端看来,一个集群就像一个服务实体,但事实上,一个集群是由一组服务实体构成但。与单个服务实体相比,集群具有两大关键特性: 可扩展性:集群的性能不受单个服务实体的限制,可以通过动态添加新的服 ...

阅读全文 »

分布式一致性协议

发表于 2018-02-18 | 更新于 2019-03-27

背景

在传统的软件系统中,如果想要保证一次业务请求中的多次修改操作结果具有一致性,可以使用事务(Transaction)来控制操作整体的提交(Commit)或回滚(Rollback)。事务为业务操作提供了强一致性保证,其特性可总结为ACID,即:

  • 原子性(Atomicity):一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节;
  • 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏;
  • 隔离型(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致;
  • 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

互联网时代,分布式系统的应用越来越普遍,在动辄几百上千的研发团队中,软件系统如果仍然采用单一应用的开发模式,团队的协作成本是非常高的,因此服务化和微服务成为了目前行业的主流。然而,分布式系统带来低耦合,高可扩展性的同时,单个业务可能包含多个应用调用,无法使用传统的事务来为所有应用提供一致性保证。此时,有人提出并证明了CAP定理,即对于一个分布式计算系统来说,不可能同时满足以下三点:

  • 一致性(Consistency):即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致;
  • 可用性(Availability):即服务一直可用,而且是正常响应时间;
  • 分区容错性(Partition tolerance):分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

由于无法同时满足CAP三个特性,有人又提出了BASE理论,即:

  • 基本可用(Basically Available):基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用;
  • 软状态(Soft State):软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性;
  • 最终一致性(Eventual Consistency):最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。
阅读全文 »

“双重校验锁不可靠”声明

发表于 2017-11-10 | 更新于 2019-03-27

双重校验锁被广泛用作一种多线程环境中高效的懒加载实现。然而,当在Java中被以平台无关的形式实现时,在没有额外的同步措施的情况下,它将是不可靠的。当它在其他语言如C++中被实现时,它的可靠与否取决于处理器的内存模型,编译器进行的指令重排序以及编译器与同步库的交互。由于以上几点都未在语言规范中被明确定义(如C++),我们无法确定双重校验锁在什么情况下有效。在C++中,可以使用显式的内存屏障来使双重校验锁正确工作,但是这些屏障在Java中并不可用。

为了解释我们想要的行为,思考以下代码:

1
2
3
4
5
6
7
8
9
10
// Single threaded version
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null)
helper = new Helper();
return helper;
}
// other functions and members...
}

若以上代码被使用在多线程环境中,将会引发很多问题。最常见的是创建了两个或更多的Helper对象(后面会提出其他问题)。要解决这个问题,我们可以简单地给getHelper()方法加上同步:

1
2
3
4
5
6
7
8
9
10
// Correct multithreaded version
class Foo {
private Helper helper = null;
public synchronized Helper getHelper() {
if (helper == null)
helper = new Helper();
return helper;
}
// other functions and members...
}

阅读全文 »

深入理解Java虚拟机学习笔记(九)

发表于 2017-10-04 | 更新于 2019-03-27

线程安全与锁优化

线程安全

线程安全的定义

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。

Java语言中的线程安全

  • 不可变:不可变的对象只要被正确构建出来(没有发生this引用逃逸的情况),那其外部的可见状态永远不会改变。例如:String,java.lang.Integer等。
  • 绝对线程安全:完全满足线程安全的定义。注意:Java API中大多数标注自己是线程安全的类,都不是绝对线程安全的!
  • 相对线程安全:保证对于对象单独的操作是线程安全的,但是对于一些特定顺序的连续调用,需要在调用端使用同步手段来保证调用的正确性。例如同时对一个Vector进行remove(),get()和size()操作,在使用get()时,之前在size()范围内的序号可能已经失效了。Java中大部分线程安全类都属于这种类型。
  • 线程兼容:对象本身不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用。
  • 线程队里:无论是否采取了同步手段,都无法在多线程环境中并发使用的代码。如Thread.suspend()和Thread.resume()等。

线程安全的实现方法

  1. 互斥同步(悲观锁):只有一个线程能持有锁,其他线程在其释放锁之前只能挂起。如synchronized关键字和ReentrantLock。
  2. 非阻塞同步(乐观锁):不加锁,基于冲突检测的并发策略,假设操作没有冲突,如果有冲突就重试,直到成功为止。典型的方法是CAS操作,Java中JUC包中的原子数字类就是使用了CAS操作。
  3. 无同步方案:天生线程安全。
    • 可重入代码:可以在代码执行的任意时刻中断它,在控制权返回后,原来的程序不会出现任何错误。
    • 线程本地存储:将共享数据的代码限制在同一线程中执行。

      同步:指在多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用。

阅读全文 »

深入理解Java虚拟机学习笔记(七)

发表于 2017-10-04 | 更新于 2019-03-27

程序编译与代码优化

解释器与编译器

  • 解释器:对代码(字节码)解释执行,执行效率较低但可以省去编译的时间,快速启动程序。
  • 编译器:将代码编译成本地代码后执行,执行效率高,但需要花费时间在编译上。

Hotspot VM内的解释器与编译器

  1. 解释器(Interpreter)
  2. C1即时编译器(Client JIT Compiler)
  3. C2即时编译器(Server JIT Compiler)

分层编译

  • 第0层:程序解释执行,解释器不开启性能监控功能,可触发第1层编译。
  • 第1层:也称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要将加入性能监控的逻辑。
  • 第2层:也称为C2编译,也是将字节码编译为本地代码,但是会启用一些耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

热点探测

热点代码

  • 被多次调用的方法
  • 被多次执行的循环体

热点探测方式

  • 基于采样的热点探测:定期检查各线程的栈顶,统计出现在栈顶的方法,出现次数较多的认为是热点方法。优点:实现简单,高效,容易获取方法调用关系。缺点:难以精确统计方法的热度,易受线程阻塞或外界因素影响。
  • 基于计数器的热点探测:为方法或代码块建立计数器,统计方法执行的次数,执行次数超过一定阈值则认为是热点方法。优点:统计结果精确严谨。缺点:实现复杂,难以获取方法的调用关系。
阅读全文 »

深入理解Java虚拟机学习笔记(八)

发表于 2017-10-04 | 更新于 2019-03-27

Java内存模型与线程

Java内存模型

主内存与工作内存

  • 主内存:主要包括方法区和堆。
  • 工作内存:每个线程拥有一个工作内存,主要包括属于该线程私有的栈和对主内存部分变量拷贝的寄存器。

注意

  1. 所有的变量都存储在主内存中,主内存对所有线程共享。
  2. 线程对变量的所有操作必须在工作内存中进行,不能直接读写主内存中的变量。
  3. 不同线程之间也无法直接访问对方的工作内存,线程间变量值的传递均需要通过主内存来完成。

内存间交互操作

  • lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
  • load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
  • assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
  • write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。

以上8种操作都是原子的,不可再分的,且执行上述基本操作时必须满足下列规则:

阅读全文 »

深入理解Java虚拟机学习笔记(六)

发表于 2017-10-01 | 更新于 2019-03-27

虚拟机字节码执行引擎

栈帧

栈帧结构

  1. 局部变量表
  2. 操作数栈
  3. 动态连接
  4. 方法返回地址
  5. 附加信息

局部变量表

  1. 变量槽(Slot):每个变量槽应该能存放一个boolean, byte, char, short, int, float, reference或returnAddress类型的数据,Slot的实际长度可以随着处理器,操作系统或虚拟机的不同而变化。对于64位数据类型,long和double,存储时将以高位对齐的方式占用两个连续的变量槽。
  2. 索引定位:虚拟机通过索引定位的方式使用局部变量表,索引值的范围是从0开始至局部变量表最大的Slot数量。
  3. 参数传递:虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程,当执行实例方法时,第0位变量槽默认用于传递方法所属对象实例的引用,即”this”。
  4. 局部变量:参数列表分配完毕后,根据方法体内部定义变量的顺序和作用域分配变量槽。
  5. 变量槽的复用:当代码执行离开了局部变量A的作用域之后,如果有新的局部变量被定义,那么A使用的变量槽就可以被新的局部变量复用。
  6. 局部变量的初始化:局部变量未经初始化不能被使用,局部变量不会被自动赋予零值。

操作数栈

  1. 栈元素:32位数据类型元素占用一个容量,64位数据类型元素占用两个容量。
  2. 作用:供字节码指令执行的时候,写入和提取内容使用。
阅读全文 »

深入理解Java虚拟机学习笔记(五)

发表于 2017-09-20 | 更新于 2019-03-27

虚拟机类加载机制

类的生命周期

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化
  • 使用
  • 卸载

验证,准备,解析三个部分统称为连接。

类加载的过程

加载

  1. 通过一个类的全限定名来获取定义此类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生存一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

验证

  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证
阅读全文 »

深入理解Java虚拟机学习笔记(四)

发表于 2017-09-18 | 更新于 2019-03-27

类文件结构

Class 类文件的结构

  1. 魔数和Class文件的版本
  2. 常量池
  3. 访问标志
  4. 类索引,父类索引与接口索引集合
  5. 字段表集合
  6. 方法表集合
  7. 属性表集合
类型 名称 数量
u4 magic 1
u2 minor_version 1
u2 major_version 1
u2 constant_pool_count 1
cp_info constant_pool constant_pool_count-1
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interfaces interfaces_count
u2 fields_count 1
field_info fileds fields_count
u2 methods_count 1
method_info methods methods_count
u2 attributes_count 1
attribute_info attributes attributes_count
阅读全文 »
12
Hans

Hans

14 日志
3 标签
GitHub E-Mail
© 2019 Hans
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Gemini v7.0.1