八股打卡六
Java四种引用类型(回顾复习)
- 强引用:Java中最常见的引用类型。当一个对象是强引用指向它时,那么它不会被垃圾回收器回收。
- 软引用:用于有用但是非必需的对象。当一个对象只有软引用指向它时,那么系统内存不足时,垃圾收集器会试图回收它。软引用适用于实现内存敏感的缓存,内存不走时释放缓存中的对象。
- 弱引用:生命周期比软引用短。当一个对象只有软引用指向它时,那么下一次垃圾回收的时候,不管系统内存是否充足,这些对象都会被回收。弱引用适用于实现对象缓存,但不希望缓存的对象影响到垃圾回收的情况。
- 虚引用:Java中最弱的引用类型。当一个对象只有虚引用指向它时,那么任何时候,它都有可能被垃圾收集器回收。在对象回收之前,会被放到一个队列,供程序员处理。虚引用适用于跟踪对象被垃圾回收的时机,以便提供必要的清理或记录。
垃圾回收算法
标记-清除算法
该方法分为两个阶段:标记阶段和清除阶段。标记阶段通过根节点,标记所有从根节点开始可达的对象,未被标记的对象就是未被引用的垃圾对象。清除阶段要对这些未被引用的对象进行清除。
适用于:存活对象较多/老年代。
复制算法
从根集合节点开始扫描,标记所有存活对象,将这些存活对象复制到一块新的内存,将原来的旧内存进行回收。
适用于:存活对象较少/新生代。
缺点:需要一块新的内存空间;复制移动对象;复制算法的高效性建立在存活对象少、垃圾对象多的基础上,这种情况常出现于新生代,而在老年代中存活对象多,使用复制算法会增加复制的成本。
标记-整理算法
该算法是标记-清除算法的优化版。它也是从根节点开始标记所有可达对象,与标记-清除算法直接清除未被标记的对象的做法不同,该算法将所有存活对象压缩到内存的一侧,清除边界外的所有空间,这种算法不需要两块相同的内存,又能避免产生碎片,性价比较高。常用于老年代。
分代收集算法
现在的虚拟机采用的是分代收集算法。堆分为老年代和新生代,堆外还有一个永久代,对不同的代采取不同的算法。老年代存活对象多,没有额外空间分配,使用标记-整理算法或标记-清除算法,新生代存活对象少,使用复制算法。
垃圾回收器
新生代垃圾回收器
- Serial收集器,采用复制算法,是新生代单线程收集器,当收集开始其他工作线程必须停止直至收集完毕。适用于Client模式下的虚拟机。
- ParNew收集器,采用复制算法,是新生代并行收集器,Serial收集器的多线程版本。
- Parallel Scavenge收集器,新生代并行收集器,追求高吞吐量和高效使用CPU。
老年代垃圾回收器
- Serial Old收集器,采用标记-整理算法,是单线程收集器,Serial收集器的老年代版本。
- Parallel Old收集器,采用标记-整理算法,是并行收集器,Parallel Scavenge收集器的老年代版本。Java1.6后出现。
- CMS收集器,采用标记-清除算法,以获取最短回收停顿时间为目标的垃圾收集器。适用于对服务器响应速度有要求的情况。分为四个步骤:初始标记,并发标记,重新标记,并发删除。
新生代和老年代垃圾回收器
G1收集器是分代垃圾收集器,主要采用标记-整理算法。
优点:
- 独特的分代垃圾收集器,兼顾新生代和老年代。
- 使用分区算法,不需要eden,新生代和老年代空间都连续。
- 并行度:垃圾回收时多个线程可以并行工作,利用多核cpu资源。
- 空间整理:回收期间,会进行适当对象移动,减少空间碎片。
- 可预见性:G1垃圾回收器可以选择部分区域进行回收,减少回收范围,降低全局停顿。
步骤:
- 初始标记:标记从GC root直接可达的对象;
- 并发标记:从GC root开始对堆中对象进行可达性分析,找出存活对象;
- 最终标记:标记在并发标记阶段发生变化的对象,将被回收;
- 筛选回收:对每个region的回收价值和成本排序,按照用户预期的GC停顿时间,指定回收计划,回收一部分region。
类加载机制
类加载机制是Java虚拟机在运行Java线程时将类加载到内存的过程。分为三个阶段:
加载
在该阶段,类加载器会查找类的字节码文件,放入内存当中。这个字节码可能在文件系统、网络等位置。该阶段不会执行类中的静态初始化代码。
连接
- 验证:确保类文件的格式正确,没有不安全的构造。
- 准备:为类的静态变量分配内存,赋予默认的初始值。
- 解析:将类、接口、字段、方法中的符号引用解析为直接引用,即内存地址。
初始化
该阶段执行类的静态初始化代码,包括静态字段的赋值和静态代码块的执行。静态初始化会在类第一次使用时进行,可以是创建实例、访问静态字段、调用静态方法。
双亲委派机制
双清委派机制是Java类加载器的一种设计模式,规定了类加载的方法和顺序。它保证了Java核心库的安全性和一致性。核心思想是:如果一个类加载器收到了类加载请求,它会默认委托给父类加载器处理,只有父类加载器无法加载时才会自行尝试加载。这样能够提高安全性,保证了核心库的类不被篡改。因为所有类最终都会通过顶层的启动类加载器来加载。另外,由于类加载器直接从父类加载器加载类,避免了类的重复加载。
Spring AOP
面向切面编程,是一种横切的技术,剖解开封装对象的内部,将影响多个类的公共行为封装到一个可重用的模块,称之为切面。这样可以减少系统的重复代码,降低模块之间的耦合度。
Spring AOP代理是由Spring IOC容器生成、管理的,依赖关系也由IOC容易管理。因此AOP代理可以使用容器中的其他bean实例作为目标。Spring创建代理的规则为:默认jdk动态代理,这样对于任何接口实例都可以创建代理;如果代理的是类不是接口,那么切换到cglib代理,也可以强制cglib代理。
AOP编程分为三部分:
- 定义普通业务组件;
- 定义一个切点,这个切点可以横切多个业务组件;
- 定义增强处理,这是AOP框架中为普通业务组件织入的处理操作。
Spring IOC
Spring IOC,就是控制反转,它的核心思想是让对象的创建和依赖关系由容器控制,不需要自己new出来,保持组件之间松散的耦合。
容器实际上就是一个Map,里面存放各种对象。通过依赖注入,Spring容器能够在运行时动态地将依赖注入到需要它们的容器当中,不需要对象去创建或寻找依赖。这样大大简化了应用的开发,将应用从复杂的依赖关系中解放出来。举个例子,实际项目中,一个service可能有成百上千个底层类,对这个service实例化,每次都要理清底层类的构造函数,而采用IOC,则只需要配置好,在需要的位置引用即可。
Spring通过XML文件配置,SpringBoot通过注解来配置。通过配置告诉Spring容器如何创建对象,如何管理对象的生命周期。
总而言之,Spring IOC是一个中心化的、能够管理应用所有对象生命周期的一个强大工具。
Bean的作用域
Bean是组成应用程序的主体和Spring IOC容器所管理的对象。Bean的作用域分为以下几种:
- 单例:默认的作用域。当一个Bean 的作用域为Singleton,那么容器中只会有一个共享的Bean实例。所有对该Bean的请求,只要id与Bean的定义相同,那么旧返回同一个Bean实例。
- 原型:当一个Bean的作用域为prototype,那么一个Bean定义可以对应多个对象实例。对于该Bean的每一次请求,都会创建一个新的Bean实例。
- 请求:一个HTTP请求是一个Bean实例,每个请求都有自己的Bean实例,只在请求期内有效。
- 会话:一个HTTP会话是一个Bean实例,与用户会话周期相同。
- 应用程序:在ServletContext中定义的Bean,是为整个应用程序所共享的一个Bean实例。
- WebSocket:在WbSocket生命周期内,一个WebSocket会话拥有一个Bean实例。
Bean的生命周期
Bean的生命周期指的是创建Bean到销毁Bean的过程。
- 创建实例:容器通过构造器或工厂方法来创建Bean的实例/
- 设置属性:容器为Bean设置属性,这些属性可能是其他Bean的引用,也可能是简单的配置值。
- 判断Aware接口设置依赖关系:如果Bean实现了BeanNameAware或BeanFactoryAware接口,那么调用setBeanName()或setBeanFactory()方法,如果Bean实现了ApplicationContextAware接口,那么调用setApplicationContext()方法将Bean的上下文引用进来。
- 前置处理:Spring调用所有注册的BeanPostProcessor接口的postProcessBeforeInitialization()方法。
- 初始化:如果Bean实现了InitializingBean接口,调用afterPropertiesSet()方法,如果设置了init-method,容器也会调用这个方法。
- 后置处理:Spring调用所有注册的BeanPostProcessor接口的postProcessAfterInitialization()方法。
- Bean使用:Bean准备好,可以用了。
- Bean的销毁:容器关闭时,如果Bean实现了DisposableBean接口,那么调用destroy()方法,如果定义了destroy-method,那么也调用这个方法。
- Bean结束:容器销毁Bean,Bean生命结束。