0%

什么是DH?

Diffie-Hellman密钥交换算法是在1976年由这两个人发明的算法。它可以在不安全的网络中,通过交换一些公开的信息协商出共享密钥,使用此共享密钥建立安全通讯。它实际上并没有直接交换密钥,而是通过数学计算,得出共享密钥。

在DH算法之前 - RSA密钥交换算法

我们构建这么一种场景,服务器配置有公钥+私钥,客户端是离散的。

1
2
3
4
5
6
7
8
9
10
RSA算法流程文字描述如下:

(1):任意客户端对服务器发起请求,服务器首先发回复自己的公钥到客户端(公钥明文传输)。

(2):客户端使用随机数算法,生成一个密钥S,使用收到的公钥进行 加密,生成C,把C发送到服务器。

(3):服务器收到C,使用公钥对应的私钥进行解密,得到S。

(4):上述交换步骤后,客户端和服务器都得到了S,S为密钥(预主密钥)。

RSA算法的问题

前向安全问题

就是如果私钥泄漏,即私钥被第三方知道,那么第三方就能从C中解密得到S,即只要保存所有的A和B的报文,等到私钥被泄漏的那一天,或者有办法快从C中计算S的方法出现(量子计算机分解大素数),那么A和B就没有什么私密性可言了。

这就是所谓的前向不安全,私钥参与了密钥交换,安全性取决于私钥是否安全保存。

一句话说DH

利用临时生成(固定的)的一对公私钥,完成master key的交换

DHE是什么? - Ephemeral

  • 静态DH:通信双方有一方的私有密钥是固定的,另一方临时生成,一般是服务器端固定。但是这样随着时间的验证,很容易被破解,而一旦被破解,之前所有的通讯数据都很容易被解密,不具备前向安全的特性。目前静态DH基本不同。

  • 既然静态DH算法中,一方固定一方临时生成的方式不安全,那么我们就双方都不固定。通讯双方的私有密钥都采用临时生成的方式,这中DH算法称之为DHE算法(E是指Ephemeral, 临时的)。

用一句话说,唯一的区别就是DHE是由随机值生成密钥

提要

在JDK8中,有多种垃圾回收算法,比如Prallel GC,CMS,G1

概述

G1收集器是同时包含了年轻代,和老年代收集的,是ZGC更新前的垃圾回收算法

应用场景

就目前而言、CMS还是默认首选的GC策略、可能在以下场景下G1更适合:

  • 服务端多核CPU、JVM内存占用较大的应用(至少大于4G)
  • 应用在运行过程中会产生大量内存碎片、需要经常压缩空间
  • 想要更可控、可预期的GC停顿周期,防止高并发下应用雪崩现象

内存模型

图 1

G1将堆内存切分成很多个固定大小的区域,每个是连续范围的虚拟内存。
堆内存中一个区域的大小可以通过 -XX:G1HeapRegionSize参数指定(最小1M,最大32M)

默认情况下堆内存均分成2048份

分代方法

对于每个region,通过不同标记从逻辑上映射成Eden,Survivor和老年代。

除了这几种分代外,还有第四种类型,被称为巨型区域,Humongous Regions,用于存储超过50%标准region大小的喜爱那个设计的。如果一个H区装不下一个巨型对象,那么G1会寻找连续的H分区来存储。为了能找到连续的H区,有时候不得不启动Full GC。

回收阶段(global concurrent marking)

  1. 初始标记 inital Marking

    这个阶段是STW(Stop the World )的,所有应用线程会被暂停,标记出从GC Root开始直接可达的对象。

    tips: 哪些对象是GC Root(static 变量,栈上的变量(类型参数,局部变量,临时值),JNI),GC的过程是找出活的对象,并把其余空间认定为“无用”,而不是找出所有死掉的对象

  2. G1执行的第二阶段:并发标记

    从1阶段搜索出来的GC ROOTS开始对对象进行可达性分分析,找出存活对象

  3. 最终标记(标记那些在并发标记阶段发生变化的对象,将被回收)

  4. 筛选回收(首先对各个Regin的回收价值和成本进行排序,根据用户所期待的GC停顿时间指定回收计划,回收一部分Region)

    最后,G1中提供了两种模式垃圾回收模式,Young GC和Mixed GC,两种存在Stop The World(STW)的阶段。

GC模式

Young GC

当Eden region使用达到最大阈值并且无法申请最够内存时,会触发young GC,每次younggc会回收所有Eden以及Survivor区,并且将存活对象复制到Old区以及另一部分的Survivor区。

YoungGC的回收过程如下:

  • 根扫描,跟CMS类似,Stop the world,扫描GC Roots对象。
  • 处理Dirty card,更新RSet.
  • 扫描RSet,扫描RSet中所有old区对扫描到的young区或者survivor去的引用。
  • 拷贝扫描出的存活的对象到survivor2/old区
  • 处理引用队列,软引用,弱引用,虚引用

mixed gc

当越来越多的对象晋升到olg region后,虚拟机会触发mixed gc,回收整个young region和一部分old region。这里需要注意,是一部分老年,也不是全部老年代,从而可以对垃圾回收的耗时进行控制

G1没有full GC的概念,需要full GC时,调用 serial Old gc 进行全堆扫描。所以Mixed GC不是Full GC,它只能回收部分老年代的Region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(Full GC)来收集整个GC heap。 所以本质上,G1是不提供Full GC的。

应用场景

如果应用程序使用CMS或ParallelOld垃圾回收器具有一个或多个以下特征,将有利于切换到G1:

  • Full GC持续时间太长或太频繁
  • 对象分配率或年轻代升级老年代很频繁
  • 不期望的很长的垃圾收集时间或压缩暂停(超过0.5至1秒)

最佳实践

停顿(STW)预测模型

G1收集器突出表现出来的一点是通过一个停顿预测模型根据用户配置的停顿时间来选择CSet的大小,从而达到用户期待的应用程序暂停时间。通过 -XX:MaxGCPauseMillis 参数来设置。这一点有点类似于ParallelScavenge收集器。关于停顿时间的设置并不是越短越好。

设置的时间越短意味着每次收集的CSet越小,导致垃圾逐步积累变多,最终不得不退化成Serial GC。
停顿时间设置的过长,那么会导致每次都会产生长时间的停顿,影响了程序对外的响应时间

当Mixed GC赶不上对象产生的速度的时候就退化成Full GC,这一点是需要重点调优的地。

不需要手工设置新生代和老年代的大小

G1收集器在运行的时候会调整新生代和老年代的大小。通过改变代的大小来调整对象晋升的速度以及晋升年龄,从而达到我们为收集器设置的暂停时间目标。
设置了新生代大小相当于放弃了G1为我们做的自动调优。我们需要做的只是设置整个堆内存的大小,剩下的交给G1自己去分配各个代的小即可。

原理 – todo

Rset和卡表

三色标记算法和SATB

问题背景

在生成服务火焰图的时候,发现了服务在getConnection的时候出现了平台,其中会瓶颈点在Thread.getStackTrace()

Druid 版本 1.0.12

看下源码

可以看到如果,isRemoveAbandoned()为true时,会调用getStackTrace()获取当前线程信息,记录在类中,这个是一个比较慢的操作,也是慢的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
for (;;) {
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
notFullTimeoutRetryCnt++;
if (LOG.isWarnEnabled()) {
LOG.warn("not full timeout retry : " + notFullTimeoutRetryCnt);
}
continue;
}
throw ex;
}

if (isTestOnBorrow()) {
boolean validate = testConnectionInternal(poolableConnection.getConnection());
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}

Connection realConnection = poolableConnection.getConnection();
discardConnection(realConnection);
continue;
}
} else {
Connection realConnection = poolableConnection.getConnection();
if (realConnection.isClosed()) {
discardConnection(null); // 传入null,避免重复关闭
continue;
}

if (isTestWhileIdle()) {
final long currentTimeMillis = System.currentTimeMillis();
final long lastActiveTimeMillis = poolableConnection.getConnectionHolder().getLastActiveTimeMillis();
final long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.getTimeBetweenEvictionRunsMillis();
if (timeBetweenEvictionRunsMillis <= 0) {
timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
}

if (idleMillis >= timeBetweenEvictionRunsMillis) {
boolean validate = testConnectionInternal(poolableConnection.getConnection());
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}

discardConnection(realConnection);
continue;
}
}
}
}

if (isRemoveAbandoned()) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.setConnectStackTrace(stackTrace);
poolableConnection.setConnectedTimeNano();
poolableConnection.setTraceEnable(true);

synchronized (activeConnections) {
activeConnections.put(poolableConnection, PRESENT);
}
}

if (!this.isDefaultAutoCommit()) {
poolableConnection.setAutoCommit(false);
}

return poolableConnection;
}
}

removeAbandoned 功能

这个功能是干什么的呢?
实际上是Druid用于定时检测是不是有申请但是没有归还的链接的,如果一个链接拿走了,但是超过时间没有归还,那就这个功能会自动回收。本质是为了防止连接泄漏的。

优化

这个功能在公司组件里是默认开启的,如果开启了这个功能,在每次获取链接的时候都会尝试获取当前线程栈,获取当前线程栈的调用是比较消耗性能的。

理论上目前大多数应用都是基于Mybatis或者Spring框架使用DataSource的,一般不会有裸用dataSource的情况,所以也用不上这个功能,徒增了消耗,所以当前功能也不是很有必要。想不通为啥会默认开启…

在官方的实现里是默认关闭,并且在issue中还有建议是在出现泄漏问题或者排查问题的时候在开启,所以如果没有必要的话,关闭掉这个功能就好了~~

单个拿链接的时间可以缩短到十分之一~