类加载过程
https://cloud.tencent.com/developer/article/1749366
加载——链接(验证、准备、解析)——初始化——卸载(程序结束或异常终止)
加载(3件事):
- 通过全类名获取该class文件,以二进制流的形式读入内存
- 将字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中国生产一个类的class对象,最为方法区这个类的各种数据入口
验证:验证文件格式、语义、语法、符号引用是否有错误
准备:静态变量在方法区分配内存并赋初值
解析:将符号引用转化为直接引用
初始化:对类变量赋值和初始化操作,如果有父类则优先初始化父类
java对象创建过程
- 类加载检查
- 先判断这个类是否被加载过,如果没有先执行类的加载过程
- 分配内存
- 对象的内存大小在类加载完成的时候就可以确定下来
- 内存分配方式有指针碰撞和空闲列表
- 内存分配并发问题
- 创建对象是频繁的,需要保证线程安全问题
- CAS失败重试
- TLAB,为每个线程分配一个tlab
- 创建对象是频繁的,需要保证线程安全问题
- 初始化零值
- 为属性、代码块、构造器等所有的属性赋予默认值,保证对象实例字段在不赋值的情况下可以使用
- 设置对象头
- 将对象的类信息、hashcode和对象的gc信息、锁信息存储到对象头中
- 执行init方法
- 对属性赋值
- 完成
为什么要打破双亲委派机制
https://www.cnblogs.com/lyc88/articles/11431383.html
因为类加载器收到了加载范围的限制,在某些情况下父类加载器无法加载到所需要的文件,这时候就需要委托子类加载器去加载class文件。
JDBC的Driver接口定义在JDK中,其实现由各个数据库的服务商来提供,比如MySQL驱动包。DriverManager 类中要加载各个实现了Driver接口的类,然后进行管理,但是DriverManager位于 $JAVA_HOME中jre/lib/rt.jar 包,由BootStrap类加载器加载,而其Driver接口的实现类是位于服务商提供的 Jar 包,**根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载第一个类的类装载器装载被引用的类。**也就是说BootStrap类加载器还要去加载jar包中的Driver接口的实现类。我们知道,BootStrap类加载器默认只负责加载 $JAVA_HOME中jre/lib/rt.jar 里所有的class,所以需要由子类加载器去加载Driver实现,这就破坏了双亲委派模型。
jvm内存模型
线程私有:程序计数器、虚拟机栈、本地方法栈
线程共享:
-
堆
新生代、
-
方法区
-
直接内存(非运行时数据区)
jdk1.8前:有方法区,和堆放在一起(内存中的数据)
jdk1.8后:方法区改为元空间,数据放到直接内存中
常见的垃圾回收器有哪些
-
Serial收集器
-
单线程串行
-
serial:复制算法,serial old:标记整理
-
一条垃圾收集线程,会产生大量的stw
-
-
ParNew收集器
-
多线程并行,Serial的升级版
-
ParNew复制算法,老年代(一般使用cms)标记整理
-
多条垃圾收集线程,在gc的时候会stw
-
除了Serial外,只有ParnewGC能与CMS收集器配合工作
-
-
Parallel Scavenge
- 多线程并行,Serial的升级版
- 新生代复制算法,老年代标记整理
- 多条垃圾收集线程,在gc的时候会stw
- 和Parnew的主要区别为使用自适应调节策略,高吞吐量,主要适用于运算而不需要太多交互的地方,常在服务器中使用
- jdk1.8的默认
-
CMS收集器
- 多线程并发
- jdk1.5的时候出现
- 主要为低延迟,适合页面交互
- 老年代标记清除算法
主要分为四个阶段
- 初始标记
- 快速标记GCRoots能直接关联的对象
- 并发标记
- 耗时较长,但是是并发执行的,没有stw
- 从GCRoots对象开始遍历整个对象图的过程
- 重新标记
- 在并发标记的时候,工作线程和垃圾线程同时或交叉运行,为了修正并发标记出现的变动
- 耗时较长,但是远比并发标记的时间短
- 会产生stw
- 并发清除
- 此阶段清理删除,标记阶段死亡的对象,由于不需要移动存活对象,这个阶段也是并发的(这个也是为什么不使用标记整理的原因)
缺点:
- 产生内存碎片,如果空间不足,在无法分配大对象的情况下,不得不提前触发FULLgc
- cms虽然不会导致用户停顿,但是因为占用了一部分线程导致程序变慢,总吞吐量降低
- 无法处理浮动垃圾,gc线程和用户线程交叉运行,可能在运行中出现垃圾,但是这个时候gc无法对垃圾记性回收,只能等到下一次
-
G1收集器
- 在延迟可控的情况下获得尽可能高的吞吐量
- 全新的分区算法
空间整合:
cms:”标记-清除“算法、产生内存碎片,若干次gc后进行一次碎片整理
g1:划分为一个个的region,内存的回收是以Region为单位(一个大对象可以跨region存放)。region之间是复制算法,但是可以看做标记整理算法,可以避免内存碎片的产生
可预测的停顿时间模型:
这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
- 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。
- G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
- 相比于CMSGC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。
相较于CMS,G1还不具备全方位、压倒性优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用(Footprint)还是程序运行时的额外执行负载(overload)都要比CMS要高。
从经验上来说,在小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上则发挥其优势。平衡点在6-8GB之间。