JVM之GC

接 JVM 那条
7.分代回收(垃圾分类)
划分依据:根据对象的年龄(经过GC扫描的轮次)
把堆划分成
新生代:伊甸区+生存区*2
老年代
一个对象的一生:
a)诞生于伊甸区
b)经过- -轮GC,如果存活,就通过复制算法,进入生存区
c)在生存区中经过GC后存活,还是通过复制算法,进入生存区2(每次经过GC扫描后,生存的对象都要被复制到另外-个生存
d)在生存区中活过一定的轮次之后,对象将被放到老年代(认为该对象生存时间会较长,就不再继续频繁扫描了)
e)老年代的对象也是需要进行GC的,扫描轮次没有新生代那么频繁了.

8.垃圾回收器
具体看下垃圾回收算法的集体实现/落地方式
JVM之GC
评价垃圾回收器好坏的标准:
1).回收的空间效率.扫- -遍地,能扫出多少垃圾.
2).回收的速度.扫-遍地要花多长时间.
3).垃圾回收和应用线程之间能否并发执行.扫地的时候会不会影响到别人干活. (STW)
4).垃圾回收器是否是多线程的.
5).回收的时间是否是可预测的.承诺10分钟之内-定把地扫完.即使扫的不算非常干净,但是也最大程度上的把-些重要的垃圾清理掉了.

垃圾回收器里面都是要做两件事:标记(可达性分析) +回收(标记清除,标记复制,标记整理)

(1)1. Serial收集器.给新生代使用,串行回收.
复制算法.单线程进行标记+回收
JVM之GC
单线程进行可达性分析+复制
当咱们进行回收的时候,应用程序线程,就都停止工作了. STW
STW的时间一定是要比应用线程运行时间短很多的.

(2)2. ParNew收集器新生代收集器 多线程并行GC)
JVM之GC
此处的标记-回收过程使用多线程来完成.除此之外,和Serial收集器是非常相似的.
这个收集器回收效率就要比Serial版本更高- -些.

(3)Parallel Scavenge收集器(新生代收集器,并行GC)
设计初衷是为了尽量缩短STW时间.
GC停顿时间的缩短是以牺牲吞吐量和新生代空间作为代价的
工作流程同ParNew收集器,
这个收集器提供了一-些参数, 可以控制STW时间以及吞吐率.
相当于,承诺用户,在一定时间内就会完成一-次 GC,哪怕清理的不是特别干净,但是也能够很大程度上的清理到大部分垃圾了.

(4)Serial Old收集器老年代收集器,串行GC)
相当于Serial收集器的老年代版本.基本特点差不多,也是单线程的方式进行标记回收的.

JVM之GC
单线程,串行的收集老年代内存.

(5)5. Parallel Old收集器(老年代收集器, 并行GC)
ParNew的老年代版本

(6)6. CMS老年代收集器并行GC [重要]
特点尽可能缩短STW时间.
采用的是多线程标记-清除
a)初始标记
[STW]
只是把和GCRoot直接相关的对象先标记出来.
这个标记过程时间比较短. (GCRoot直接相关的对象不是很多)
b)并发标记此较耗时, 但是不涉及STW]
执行整个标记遍历的过程. (从GCRoot开始,把所有能访问到的对象都遍历)
不需要暂停用户线程.
消耗的时间相对比较久,但是可以和用户线程并发.
**注意:**当进行并发标记的时候,由于用户线程也在执行,可能导致某个对象,刚刚标记成不是垃
圾之后,被应用代码- -改,就成了垃圾了~~
并发标记完成之后,标记得到的垃圾结果可能就和真实结果之间存在一定的误差了
c)重新标记(CMS remark) [STW]
修正刚才的误差.由于刚才出现误差的毕竟是少数.重新标记代价不是很大,虽然STW了,用不了多少时间就结束了.
d)并发清除(CMS concurrent sweep) [此较耗时, 也不涉及STW]
多线程的方式把刚才的垃圾对象都释放掉(直接清除)
也可以和应用线程并发进行
优点:
能够让STW时间尽量短
缺点:
a).内存碎片
b). GC操作和应用线程是并发执行的,就更吃CPU资源. (对CPU比较敏感)

7). G1比较新的回收器. Java11 开始才默认使用的回收器.
是以上7种中最优秀的回收器.
JVM之GC

“老少通吃"既可以回收新生代,也能回收老年代.
每个矩形区域称为一个"region”
E表示伊甸区
S表示生存区
T表示老年代
H表示存放大对象的区域
G1在回收的时候不一定需要- -次性的把整个内存都会收完.而是以region为单位进行回收. (回收的粒度更加精细)
针对新生代的region同样是使用复制算法进行回收.
针对老年代的回收,类似于CMS,但是有一定的差别.
a)初始标记和CMS类似,只去找GCRoot直接关联的对象.时间比较短,会涉及STW
b)并发标记(Concurrent Mark),和应用线程并发执行,进行可达性分析,遍历所有的对象. (不涉及STW)
和CMS不-样的是,如果发现某个老年代region中已经没存活对象了,就直接回收掉.不去等最后一个环节回收了. Garbage First
c)最终标记.为了修正b)产生的误差. SATB(snapshot-at- the-begining)
d)筛选回收
参考深入理解JVM这本书.

大对象直接进入老年代
新生代主要使用复制算法.
对象比较大,来回复制开销很大.

深拷贝:针对对象A拷贝之后得到对象B,对A的修改不会对B造成任何影响.这种就叫深拷贝.
浅拷贝:针对对象A拷贝之后得到对象B,对A的修改会影响到B.

要想实现深拷贝:
1).如果对象的属性是内置类型,不过多操心.
2).如果对象的属性是引用类型,需要针对这个弓|用类型也递归的进行深拷贝.

类加载:
1.类加载基本过程
JVM之GC
1).加载:根据类名,找到文件,读取文件,解析架构,把内容放到内存中,并构造出对应的类对象
2).链接:如果该类依赖了一些其他类,链接过程就是把依赖内容进行引入.
3).初始化:初始化静态成员,并且执行静态代码块.

2.关于类加载中的常见问题
1)什么时机会触发某个类的加载?
a)构造该类的实例
b)调用该类的静态属性或方法
c)使用子类时会触发父类的加载.

2)类加载器有哪些~
内置了三个类加载器

a). BootstrapClassLoader :加载标准库中的类. String, Arraylis…
null ( 没法直接通过getClassLoader 获取到BootstrapClassLoader)
b). ExternsionClassLoader 加载- 个特殊目录中的类
sun . misc. LauncherExtClassLoader@7f31245ac).ApplicationClassLoadersun.misc.LauncherExtC [email protected] c). ApplicationClassLoader 加载应用程序自己写的类 sun . misc . Launcher[email protected]
这三个类加载器各自负责自己的-片区域(目录).
BootstrapClassLoader: D:\jdk1.8\jre\lib\t.jar 标准库中的类的实现,就在这个rt.jar中
ExtClassLoader: D:\jdk1.8jre\ib\ext 的所有jar包中找
AppClassLoader: 1) CLASS_ PATH环境变量(很少配置) 2) java -cp 3). 当前目录

3)啥是双亲委派模型(比较重点)
描述的是,类加载中,根据类名找类的.class文件的查找过程.
刚才说的类加载的大的流程
a).读取文件解析
b).连接
c).初始化

以加载String类为例
JVM之GC
三个类加载器之间存在"父子关系" (和继承无关)
类加载器中有一个parent属性,指向了自己的父亲.
给一个类名: java.lang.String
让类加载器通过类名找到对应的class 文件.

a).先从 AppClassLoader开始
b). AppClassLoader 不会立刻查找而是先把类名交给它的父亲,先让父亲查找.
c). ExtClassLoader也不会立刻进行查找,而是把类名又交给它的父亲
d). BootstrapClassLoader拿到类名之后,只能自己亲自查找. rt.jar
如果找到了,就直接加载类即可.如果未找到,再把这个类名交还给ExtClassLoader来查找.
e). ExtClassLoader再根据类名在ext目录中查找.如果找到就加载
如果没找到,就把这个类名再还给AppClassLoader进行加载.
f). AppClassLoader再去CLASS_ PATH环境变量, -cp指定的目录, .当前目录中再去找.
如果找到了,就进行加载 . 如果没找得到,就抛出一一个ClassNotFoundException

目的就是:尽量让标准库的类优先被加载
例如你在代码中创建一个java.lang.String这样的类,名字和标准库的String 类相同了,此时优先加载标准库的String

4)能否违背双亲委派模型?
如果你自己写了一个类加载器是否要遵守双亲委派?
可以违背.
只是标准库中的三个类加载器要遵守.其他的类加载器不太需要遵守.
例如Tomcat中的类加载器就没遵守. (要从webapps目录中加载指定的webapp的类)