既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
1、类加载过程
(1)加载
加载class字节码到Java进程的内存中,在堆中创建类对象。
(2)验证
验证class字节码的规范(是否符合jvm规范)。
(3)准备
正式为类中的变量(静态变量)分配内存并对类变量进行初始化:
- static常量:赋值为初始值;
- static变量:赋值为缺省值;(Integer缺省值为null,int为0)
(4)解析
把常量池中的符号引用替换为直接引用:
- 符号引用:class文件(字节码)private static int x=123;此时进程还没有启动,无法表示变量x指向123(本质是指向内存地址),此时使用符号引用来表示这个指向关系;
- 直接引用:进程运行起来,x直接指向123(内存地址);
- 替换:把class文件常量池中的符号引用,替换为进程运行起来的运行时常量池中的直接引用。
(5)初始化
是类的初始化,不是对象初始化。初始化阶段,Java虚拟机真正开始执行类中编写的Java程序代码。初始化阶段就是执行类构造器方法的过程。
类对象的初始化:执行静态变量和静态代码块。
2、双亲委派模型
(1)什么是双亲委派模型
类加载的机制:双亲委派模型(jdk默认的类加载机制),其它机制(破坏双亲委派模型的其它机制)。
**类加载器:**包含四种,从上而下(上下关系,不是以extends继承来实现的,是逻辑上的上下关系):
- BootStrap ClassLoader 启动类加载器(主要负责加载Java核心类库,即%JRE_HOME%\lib目录)
- ExtClassLoader 扩展类加载器(主要负责加载目录%JRE_HOME%\lib\ext目录下的类)
- AppClassLoader 系统/应用类加载器(加载当前应用的classpath目录下的类)
- 自定义加载器
启动/扩展/应用这三类类加载器,只是加载jar目录下不同的jar包。
什么是双亲委派模型?
基于四种类加载器,按照从上到下的顺序,来加载类。类加载器收到类加载请求时,不会立即去自己加载,而是将这个请求向上传递,每一层都是如此,因此所有的加载请求最终应该在最顶层的启动类加载器中。因为类加载只执行一次,所以,上边找到,下边就不执行加载;上边没有找到,就交给下一级的加载。
(2)双亲委派模型的优点
- 避免重复加载类:如A类和B类都有一个父类C,当A启动起来的时候就会加载C,则B类进行加载时就不需要重复加载C类。
- 安全性:确保优先采用启动/扩展/应用类加载器来加载类。
如果自定义一个类加载,加载自定义的java.long.Object类:
【1】遵循双亲委派模型,就不会加载到自定义的Object,还是jre中的(安全);
【2】不遵循双亲委派模型,就可以加载到自定义的Object类(不安全)。【注】在jdk中,自定义类加载器时,进行了安全校验:加载类,如果类的全限定名以java./javax.开头的包,报错。
(3)破坏双亲委派模型
遵循双亲委派机制的类加载,某些场景下,可能无法实现知道需要加载的类名(jdbc中,jdk是无法知道数据库驱动类的类名)。
解决方案:破坏双亲委派机制。
常用的方案:SPI机制(jdk提供的一个ServiceLoader.load,约定了从jar包下/META-INF/services/文件名 中进行加载)
四、垃圾回收(GC)
- Java语言,不用自己分配内存,也不用自己回收内存(jvm中,实现了垃圾回收机制,自动回收)。
- 垃圾回收区域:堆(回收的主要区域)、方法区(保存类信息,静态变量,很少回收);
1、死亡对象判断算法
(1)引用计数算法
给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器-1;任何时刻计数器为零的对象不能再被使用,即对象已死。**但jvm中没有选用引用计数器来管理内存:引用计数器无法解决对象的循环引用问题。**如下:
//此时instance的引用计数器会陷入死循环
Test test1 = new Test();
Test test2 = new Test();
test1.instance = test2;
test2.instance = test1;
(2)可达性分析算法
通过一系列称为“GC Roots”的对象作为起始点,从这些节点往下搜索,搜索走过的路径称为“引用链”,当一个对象到GC Roots没有引用链相连时,证明是“不可达的”。
引用的分类(强度依次递减):
- 强引用:只要引用还在,就不会被回收;
- 软引用(SoftReference):系统要发生内存溢出之前,会对其及逆行回收;
- 弱引用:每次GC发生时都会进行回收;
- 虚引用:无法使用,只是在对象被回收时发起一个系统通知。
2、垃圾回收算法
(1)标记清除算法(老年代回收算法)
算法分为两个阶段:
- 标记:标记出所有需要回收的对象;
- 清除:标记完成后统一回收所有被标记的对象。
缺陷:
- 效率低:标记和清除过程效率都不高;
- 清除后产生大量不连续的内存碎片,导致在之后的运行中需要分配较大对象时,无法找到足够的连续内存不得不提前进行下一次垃圾回收。
(2)复制算法(新生代算法)
复制算法解决了”标记-清理“的效率问题。将可用的内存按容量分为大小相等的两块,每次只使用一块。当内存需要进行垃圾回收时,会将该区域存活的对象复制到另一块上面,然后将已经使用过的内存区域清理掉。
**使用场景:**大多数对象具有朝生夕死的特性。新生代的对象符合该特性,使用复制算法。
**缺陷:**空间利用率不高,只有50%。
JVM中,新生代的回收算法,是复制算法的优化版本:
- 新生代中98%以上的对象都是“朝生夕死”的,所以不需要按照1:1来划分内存空间,而是将内存划分为一块较大的Eden区和两块较小的Survivor区(8:1:1);
- 每次使用Eden和一块Survivor。回收时,将Eden和Survivor中存活的对象一次性复制到另一块Survivor空间,最后清理掉Eden和使用的Survivor空间。
- 部分对象会在两个Survivor区域来回复制,如此交换15次(默认15)之后如果还存活,会vu你放到老年代。
(3)标记整理算法(老年代算法)
复制收集算法在对象存活率较高时会及逆行大量的赋值操作,效率比较低。因此来年代一般不使用。标记整理算法过程和标记清除算法一致,不同的是,标记完后,不直接进行清除,而是将存活的对象移动到一端,最后清除掉端边界以外的内存。
(4)分代算法
分代算法(没有具体的算法实现)是通过区域划分,实现不同的区域不同的垃圾回收策略,从而更好地实现垃圾回收。JVM垃圾收集都采用的是“分代收集”算法,只是根据对象的存活周期的不同将内存分为几块。一般是分为新生代和老年代。新生代中,每次垃圾回收只有少量的对象存活,使用复制算法;老年代中,对象存活率高,使用标记清除算法或标记整理算法。
哪些对象进入新生代?哪些对象进入老年代?
- 默认创建的对象(非大对象)都进入新生代;
- 老年代:
【1】大对象:对象占据的空间超出jvm规定的阈值;
【2】新生代中年龄超过15的对象。
【3】新生代GC时,分配担保失败的对象:新生代GC时把Eden区域和s0中的存活对象复制到s1区域中。(新生代对象的特点是朝生夕死,大多数情况下,存活的对象不足10%,但是也有可能s1区域中放不下,此时就放入老年代中。
什么时候发生垃圾回收?
对象进入哪个区域,如果该区域空间不足,就会触发该区域的GC。
Minor GC和Major GC有什么区别?
- 新生代GC/Minor GC:采用复制算法,效率比较高;
- 老年代GC/Major GC:采用标记清除算法/标记整理算法,效率比较差,一般比新生代GC慢十倍以上。
3、垃圾收集器
理解以下几个概念:
- **并行:**指多条垃圾收集线程并行工作,用户线程仍处于等待状态(暂停SWT【Stop the World】);
- **并发:**用户线程和垃圾收集线程同时执行(不一定并行,可能会交替执行),用户程序继续运行,二垃圾收集程序在另外一个CPU上;
- **吞吐量:**CPU用于运行用户代码的时间和CPU总消耗时间的比值;(是一个程序的性能指标)
- **用户体验:**stw单次停顿越长,用户体验就越差,此时用户体验就不好;单词stw停顿时间短一点,整个stw的时间可以长一点(用户体验更好,但吞吐量小(性能差一点))
适用场景:和用户打交道的程序,比如我们的一个Java网站后端。
jvm中经典的垃圾收集器:
- Serial收集器:新生代收集器(复制算法);单线程(单个垃圾回收线程)的方案=>目前大多数电脑都是支持多线程,所以这个收集器效率不高;
- ParNew收集器:新生代收集器;搭配CMS(老年代收集器)的方案;
- Parallel Scanvenge收集器:新生代收集器;吞吐量优先=>适应性能优先的程序;搭配Parallel Old(老年代收集器,也是吞吐量优先)
- Serial Old收集器:老年代收集器(标记整理算法);单线程;
- Parallel Old收集器:老年代收集器(标记整理算法);吞吐量优先。
(1)CMS收集器(Concurr Mark Sweep)
- 老年代收集器
- 标记清除算法
- 用户体验优先=>整体看是并发(垃圾回收线程和用户线程同时执行)的过程,有局部的stw(少许时间暂停用户线程)
- CMS搭配新生代的ParNew收集器
四个步骤:
- **初始标记:**标记GC Roots能直接关联到的对象,需要SWT;
- **并发标记:**进行GC Roots引用链追踪的过程(搜索引用路径);
- **重新标记:**修复并发标记阶段用户线程同时执行产生标记变动的记录,需要SWT;(这个阶段停顿时间比初始标记阶段稍长,但远比并发标记时间短)
- **并发清除:**并发清除垃圾。
优缺点:
【1】**优点:**并发收集、低停顿;
【2】缺点:
- **CPU比较敏感:**用户体验优先,意味着吞吐量低(单次停顿时间短,整体停顿时间长)=>CPU利用率下降;
- 无法处理浮动垃圾(浮动垃圾:并发清除时用户线程执行产生的垃圾):
(1)需要预留一部分空间:用来保存并发清除阶段用户线程创建的对象;
(2)并发模式失败(Concurrent MOdel Failure):并发清除阶段用户线程创建的对象超出预留的空间大小=>再次触发另一次老年代GC(老年代GC比较耗时),采用后备方案Serial Old方案进行回收- **内存碎片问题:**标记清除算法会带来这样的问题,大对象时,内存足够,但连续内存不足,提前触发垃圾回收。
(2)G1收集器(唯一一个全区域的垃圾回收器)
其内存划分方式不再时新生代和老年代,而是把堆划分为多个大小相同的region区,动态分配为E区、S区、T区(老年代)以及H区(用于存储大对象,大小超过region大一的一半的对象)。
- 老年代收集器;
- 全堆收集器(most garbage优先回收):整体基于“标记整理算法”,局部基于“复制算法”;
- 用户体验优先。
回收方式:
**(1)新生代垃圾收集:**使用复制算法,把多个E区和S区存活的对象复制到空的region区在动态指定为S区)。
**(2)老年代垃圾收集:**分为四个阶段
- 初始阶段:和CMS类似(标记GC Roots关联的对象,SWT),不同的是,可以和新生代1GC同时执行;
- 并发标记:和CMS类似,同时如果发现那些Tenured(老年代)region中对象的存活率很小或者基本没有对象存活,会在次将其回收,不用等到最后阶段;
- 最终标记:和CMS的重新标记类似;
- 筛选回收:挑选对象存活率低的region进行回收,也是和新生代回收同时进行。
五、Java内存模型
Java内存模型(java Memory Model):用来屏蔽各种硬件和操作系统的内存访问差异,实现Java程序在各种平台下都能达到一致的内存访问效果。
1、主内存和工作内存
Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中取出变量的底层细节。此处变量包括(实例字段、静态字段和构成数组对象的元素),但不包括局部变量和方法参数(是线程私有的,不会被线程共享)。
线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,不能直接对鞋主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的值的传递均需要通过主内存来完成的。
- **主存:**存放线程共享的数据;
- **工作内存:**存放线程私有的,及共享变量的拷贝。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
鞋主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的值的传递均需要通过主内存来完成的。
- **主存:**存放线程共享的数据;
- **工作内存:**存放线程私有的,及共享变量的拷贝。
[外链图片转存中…(img-Z4sq8XBs-1714969669690)]
[外链图片转存中…(img-FsjO9eE5-1714969669690)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!