Skip to content

Commit a192e7f

Browse files
committed
突破Java面试系列
0 parents  commit a192e7f

File tree

163 files changed

+26105
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

163 files changed

+26105
-0
lines changed

Diff for: Java/JAVA-Calendar.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Calendar.getInstance() 中所获得的实例就是一个 "GreogrianCalendar" 对象(与通过 new GregorianCalendar() 获得的结果一致)。
2+
3+
#Calendar 与 Date 的转换
4+
Calendar calendar = Calendar.getInstance();
5+
// 从一个 Calendar 对象中获取 Date 对象
6+
Date date = calendar.getTime();
7+
// 将 Date 对象反应到一个 Calendar 对象中,
8+
// Calendar/GregorianCalendar 没有构造函数可以接受 Date 对象
9+
// 所以我们必需先获得一个实例,然后设置 Date 对象
10+
calendar.setTime(date);
11+
#注意的事项:
12+
##1. Calendar 的 set() 方法
13+
14+
set(int field, int value) - 是用来设置"年/月/日/小时/分钟/秒/微秒"等值
15+
16+
field 的定义在 Calendar 中
17+
18+
set(int year, int month, int day, int hour, int minute, int second) 但没有
19+
20+
set(int year, int month, int day, int hour, int minute, int second, int millisecond) 前面 set(int,int,int,int,int,int) 方法不会自动将 MilliSecond 清为 0。
21+
22+
另外,月份的起始值为0而不是1,所以要设置八月时,我们用7而不是8。
23+
24+
calendar.set(Calendar.MONTH, 7);
25+
26+
我们通常需要在程序逻辑中将它清为 0, Calendar 不是马上就刷新其内部的记录
27+
28+
在 Calendar 的方法中,get() 和 add() 会让 Calendar 立刻刷新。Set() 的这个特性会给我们的开发带来一些意想不到的结果。
29+
##add() 与 roll() 的区别
30+
31+
add() 的功能非常强大,add 可以对 Calendar 的字段进行计算。如果需要减去值,那么使用负数值就可以了,如 add(field, -value)。
32+
33+
add() 有两条规则:
34+
35+
当被修改的字段超出它可以的范围时,那么比它大的字段会自动修正。如:
36+
Calendar cal1 = Calendar.getInstance();
37+
cal1.set(2000, 7, 31, 0, 0 , 0); //2000-8-31
38+
cal1.add(Calendar.MONTH, 1); //2000-9-31 => 2000-10-1,对吗?
39+
System.out.println(cal1.getTime()); //结果是 2000-9-30
40+
41+
另一个规则是,如果比它小的字段是不可变的(由 Calendar 的实现类决定),那么该小字段会修正到变化最小的值。
42+
43+
以上面的例子,9-31 就会变成 9-30,因为变化最小。
44+
45+
Roll() 的规则只有一条:
46+
当被修改的字段超出它可以的范围时,那么比它大的字段不会被修正。如:
47+
48+
Calendar cal1 = Calendar.getInstance();
49+
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
50+
cal1.roll(Calendar.WEEK_OF_MONTH, -1); //1999-6-1, 周二
51+
cal1.set(1999, 5, 6, 0, 0, 0); //1999-6-6, 周日
52+
cal1.add(Calendar.WEEK_OF_MONTH, -1); //1999-5-30, 周日
53+
WEEK_OF_MONTH 比 MONTH 字段小,所以 roll 不能修正 MONTH 字段。

Diff for: Java/JVM/JVM实战---内存模型.md

+403
Large diffs are not rendered by default.

Diff for: Java/JVM/JVM实战---垃圾收集器.md

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
使用分代垃圾收集器,基于以下观察事实(弱分代假设)
2+
- 大多数分配对象的存活时间短
3+
- 存活时间久的对象很少引用存活时间短的对象
4+
5+
由此, HotSpot VM 将堆分为两个物理区空间,这就是分代(永久代只存储元数据, eg. 类的数据结构,保留字符串( Interned String))
6+
7+
8+
根据新生代和老年代各自的特点,我们应该分别为它们选择不同的收集器,以提升垃圾回收效率.
9+
![](http://upload-images.jianshu.io/upload_images/4685968-3a367913acebef67.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
10+
11+
# 1 新生代垃圾收集器
12+
13+
## 1.1 Serial垃圾收集器
14+
一个主要应用于Y-GC的垃圾回收器,采用串行单线程的方式完成GC任务,其中“Stop The World"简称STW,即垃圾回收的某个阶段会暂停整个应用程序的执行
15+
F-GC的时间相对较长,频繁FGC会严重影响应用程序的性能
16+
![Serial 回收流程](https://upload-images.jianshu.io/upload_images/4685968-407a95f442c62819.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
17+
`单线程 Stop-The-World 式`
18+
![](http://upload-images.jianshu.io/upload_images/4685968-ebdf41221960630c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
19+
- 单线程
20+
只会使用一个CPU或一条GC线程进行GC,并且在GC过程中暂停其他所有的工作线程,因此用户的请求或图形化界面会出现卡顿
21+
- 适合Client模式
22+
一般客户端应用所需内存较小,不会创建太多的对象,而且堆内存不大,因此GC时间比较短,即使在这段时间停止一切用户线程,也不会感到明显停顿
23+
- 简单高效
24+
由于Serial收集器只有一条GC线程,避免了线程切换的开销
25+
- 采用"复制"算法
26+
## 1.2 ParNew垃圾收集器
27+
ParNew是Serial的多线程版本.
28+
![](http://upload-images.jianshu.io/upload_images/4685968-e4947c56e4c734f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
29+
## 2.1 多线程并行执行
30+
ParNew由多条GC线程并行地进行垃圾清理.
31+
但清理过程仍然需要暂停一切其他用户线程.
32+
但由于有多条GC线程同时清理,清理速度比Serial有一定的提升
33+
## 2.2 适合多CPU的服务器环境
34+
由于使用多线程,是许多运行在 server 模式下的虚拟机首选的新生代收集器
35+
- 与Serial性能对比
36+
ParNew和Serial唯一区别就是使用了多线程垃圾回收,在多CPU的环境下性能比Serial会有一定程度的提升
37+
但线程切换需要额外的开销,因此在单CPU环境中表现不如Serial,双CPU环境也不一定就比Serial高效
38+
默认开启的收集线程数与CPU数量相同
39+
## 2.3 采用"复制"算法
40+
## 2.4 追求"降低停顿时间"
41+
和Serial相比,ParNew使用多线程的目的就是缩短GC时间,从而减少用户线程被停顿的时间
42+
# 3 Parallel : 吞吐量为先!
43+
## 3.1 Scavenge垃圾收集器
44+
Parallel Scavenge和ParNew一样都是并行的多线程、新生代收集器,都使用"复制"算法(`Stop-The-World`)进行垃圾回收
45+
46+
但它们有个巨大不同点:
47+
- ParNew收集器追求降低GC时用户线程的停顿时间,适合交互式应用,良好的反应速度提升用户体验.
48+
- Parallel Scavenge追求可控的CPU吞吐量,能够在较短的时间内完成指定任务,适合不需太多交互的后台运算
49+
50+
>吞吐量是指用户线程运行时间占CPU总时间的比例.
51+
>CPU总时间包括 : 用户线程运行时间 和 GC线程运行的时间.
52+
> 因此,吞吐量越高表示用户线程运行时间越长,从而用户线程能够被快速处理完.
53+
54+
- 降低停顿时间的两种方式
55+
1.在多CPU环境中使用多条GC线程,从而垃圾回收的时间减少,从而用户线程停顿的时间也减少;
56+
2.实现GC线程与用户线程并发执行。所谓并发,就是用户线程与GC线程交替执行,从而每次停顿的时间会减少,用户感受到的停顿感降低,但线程之间不断切换意味着需要额外的开销,从而垃圾回收和用户线程的总时间将会延长。
57+
58+
- Parallel Scavenge提供的参数
59+
- -XX:GCTimeRadio
60+
直接设置吞吐量大小,GC时间占总时间比率.相当于是吞吐量的倒数.
61+
62+
- -XX:MaxGCPauseMillis
63+
设置最大GC停顿时间
64+
Parallel Scavenge会根据这个值的大小确定新生代的大小.
65+
这个值越小,新生代就越小,从而收集器就能以较短时间进行一次GC
66+
但新生代变小后,回收的频率就会提高,吞吐量也降下来了,因此要合理控制这个值
67+
- -XX:+UseAdaptiveSizePolicy
68+
开启GC **自适应的调节策略(区别于ParNew)**.
69+
我们只要设置最大堆(-Xmx)和MaxGCPauseMillis或GCTimeRadio,收集器会自动调整新生代的大小、Eden和Survior的比例、对象进入老年代的年龄,以最大程度上接近我们设置的MaxGCPauseMillis或GCTimeRadio
70+
## 3.2 Old垃圾收集器
71+
Parallel Scavenge的老年代版本,一般它们搭配使用,追求CPU吞吐量
72+
73+
它们在垃圾收集时都是由多条GC线程并行执行,并暂停一切用户线程,使用"标记-整理"算法.因此,由于在GC过程中没有使垃圾收集和用户线程并行执行,因此它们是追求吞吐量的垃圾收集器.
74+
![](http://upload-images.jianshu.io/upload_images/4685968-7dfdda76a61a31cf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
75+
# **老年代垃圾收集器**
76+
# 1 Serial Old垃圾收集器
77+
Serial的老年代版本,都是单线程收集器,GC时只启动一条GC线程,因此都适合客户端应用.
78+
79+
80+
它们唯一的区别就是
81+
- Serial Old工作在老年代,使用"标记-整理"算法
82+
- Serial工作在新生代,使用"复制"算法.
83+
![](http://upload-images.jianshu.io/upload_images/4685968-b150a60a1bae36a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
84+
85+
# 4 CMS垃圾收集器(Concurrent Mark Sweep Collector) : 低延迟为先!
86+
回收停顿时间比较短、目前比较常用的垃圾回收器。它通过初始标记(InitialMark)、并发标记(Concurrent Mark)、重新标记( Remark)、并发清除( Concurrent Sweep )四个步骤完成垃圾回收工作
87+
由于CMS采用的是“标记-清除算法",因此戸生大量的空间碎片。为了解决这个问题,CMS可以通过配置
88+
```
89+
-XX:+UseCMSCompactAtFullCollection
90+
```
91+
参数,强制JVM在FGC完成后対老年代迸行圧縮,执行一次空间碎片整理,但是空间碎片整理阶段也会引发STW。为了减少STW次数,CMS还可以通过配置
92+
```
93+
-XX:+CMSFullGCsBeforeCompaction=n
94+
```
95+
参数,在执行了n次FGC后, JVM再在老年代执行空间碎片整理
96+
97+
对许多应用来说,快速响应比端到端的吞吐量更为重要
98+
99+
管理新生代的方法与 parallel 和 serial 相同
100+
在老年代则尽可能并发执行,每个 GC 周期只有2次短的停顿
101+
102+
一种追求最短停顿时间的收集器
103+
在GC时使得用户线程和GC线程并发执行,因此在GC过程中用户也不会感受到明显卡顿
104+
但用户线程和GC线程之间不停地切换会有额外的开销,因此垃圾回收总时间就会被延长
105+
**垃圾回收过程**
106+
前两步需要"Stop The World"
107+
- 初始标记 (Initial Mark)
108+
停止一切用户线程,仅使用一条初始标记线程对所有与GC Roots直接相关联的 老年代对象进行标记,速度很快
109+
- 并发标记 (Concurrent Marking Phase)
110+
使用多条并发标记线程并行执行,并与用户线程并发执行.此过程进行可达性分析,标记所有这些对象可达的存货对象,速度很慢
111+
- 重新标记 ( Remark)
112+
因为并发标记时有用户线程在执行,标记结果可能有变化
113+
停止一切用户线程,并使用多条重新标记线程并行执行,重新遍历所有在并发标记期间有变化的对象进行最后的标记.这个过程的运行时间介于初始标记和并发标记之间
114+
- 并发清除 (Concurrent Sweeping)
115+
只使用一条并发清除线程,和用户线程们并发执行,清除刚才标记的对象
116+
这个过程非常耗时
117+
![](http://upload-images.jianshu.io/upload_images/4685968-fb1723e25639cf21.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
118+
## 4.1 **CMS的缺点**
119+
- 吞吐量低
120+
由于CMS在GC过程用户线程和GC线程并行,从而有线程切换的额外开销
121+
因此CPU吞吐量就不如在GC过程中停止一切用户线程的方式来的高
122+
- 无法处理浮动垃圾,导致频繁Full GC
123+
由于垃圾清除过程中,用户线程和GC线程并发执行,也就是用户线程仍在执行,那么在执行过程中会产生垃圾,这些垃圾称为"浮动垃圾"
124+
如果CMS在GC过程中,用户线程需要在老年代中分配内存时发现空间不足,就需再次发起Full GC,而此时CMS正在进行清除工作,因此此时只能由Serial Old临时对老年代进行一次Full GC
125+
- 使用"标记-清除"算法产生碎片空间
126+
由于CMS使用了"标记-清除"算法, 因此清除之后会产生大量的碎片空间,不利于空间利用率.不过CMS提供了应对策略:
127+
- 开启-XX:+UseCMSCompactAtFullCollection
128+
开启该参数后,每次FullGC完成后都会进行一次内存压缩整理,将零散在各处的对象整理到一块儿.但每次都整理效率不高,因此提供了以下参数.
129+
- 设置参数-XX:CMSFullGCsBeforeCompaction
130+
本参数告诉CMS,经过了N次Full GC过后再进行一次内存整理.
131+
132+
# 5 G1收集器(Garbage-First)
133+
Hotspot 在JDK7中推出了新一代 G1 ( Garbage-First Garbage Collector )垃圾回收,通过
134+
```
135+
-XX:+UseG1GC
136+
```
137+
参数启用
138+
和CMS相比,Gl具备压缩功能,能避免碎片向題,G1的暂停时间更加可控。性能总体还是非常不错的,G1是当今最前沿的垃圾收集器成果之一.
139+
140+
## 5.1 G1的特点
141+
- 追求停顿时间
142+
- 多线程GC
143+
- 面向服务端应用
144+
- 整体来看基于标记-整理和局部来看基于复制算法合并
145+
不会产生内存空间碎片
146+
- 可对整个堆进行垃圾回收
147+
- 可预测的停顿时间
148+
## 5.2 G1的内存模型
149+
没有新生代和老年代的概念,而是将Java堆划分为一块块独立的大小相等的Region.
150+
当要进行垃圾收集时,首先估计每个Region中的垃圾数量,每次都从垃圾回收价值最大的Region开始回收,因此可以获得最大的回收效率
151+
![](https://upload-images.jianshu.io/upload_images/4685968-db8d827f816cca34.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
152+
G1将Java堆空间分割成了若干相同大小的区域,即region
153+
包括
154+
- Eden
155+
- Survivor
156+
- Old
157+
- Humongous
158+
159+
其中,`Humongous `是特殊的Old类型,专门放置大型对象.
160+
这样的划分方式意味着不需要一个连续的内存空间管理对象.G1将空间分为多个区域,**优先回收垃圾最多**的区域.
161+
G1采用的是**Mark-Copy** ,有非常好的空间整合能力,不会产生大量的空间碎片
162+
G1的一大优势在于可预测的停顿时间,能够尽可能快地在指定时间内完成垃圾回收任务
163+
在JDK11中,已经将G1设为默认垃圾回收器,通过jstat命令可以查看垃圾回收情况,在YGC时S0/S1并不会交换.
164+
165+
## 5.3 Remembered Set
166+
一个对象和它内部所引用的对象可能不在同一个Region中,那么当垃圾回收时,是否需要扫描整个堆内存才能完整地进行一次可达性分析?
167+
当然不是,每个Region都有一个Remembered Set,用于记录本区域中所有对象引用的对象所在的区域,从而在进行可达性分析时,只要在GC Roots中再加上Remembered Set即可防止对所有堆内存的遍历.
168+
## 5.4 G1垃圾收集过程
169+
170+
- 初始标记
171+
标记与GC Roots直接关联的对象,停止所有用户线程,只启动一条初始标记线程,这个过程很快.
172+
- 并发标记
173+
进行全面的可达性分析,开启一条并发标记线程与用户线程并行执行.这个过程比较长.
174+
- 最终标记
175+
标记出并发标记过程中用户线程新产生的垃圾.停止所有用户线程,并使用多条最终标记线程并行执行.
176+
- 筛选回收
177+
回收废弃的对象.此时也需要停止一切用户线程,并使用多条筛选回收线程并行执行.
178+
![这里写图片描述](http://upload-images.jianshu.io/upload_images/4685968-0ea2b3a18ecc561a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
179+
180+
S0/S1的功能由G1中的Survivor region来承载,通过GC日志可以观察到完整的垃圾回收过程如下,其中就有Survivor regions的区域从0个到1个
181+
![](https://upload-images.jianshu.io/upload_images/4685968-ad5f6b99b3de502e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
182+
红色标识的为G1中的四种region,都处于Heap中.
183+
G1执行时使用4个worker并发执行,在初始标记时,还是会触发STW,如第一步所示的Pause
184+
185+
186+
187+
188+
189+
190+
191+
192+
193+
194+
195+
196+
197+
198+
199+
200+
201+
202+
203+
204+
205+
206+
207+
208+
209+
210+
211+
212+
213+
214+
215+
216+
217+
218+
219+
220+
221+

0 commit comments

Comments
 (0)