Java对象的生命周期
概述
Java对象的生命周期包括以下6个阶段:
创建阶段
即对象的声明和构造阶段,主要过程如下:
- 在内存中分配空间
- 开始构造对象
- 从超类到子类进行static成员初始化(加载静态变量)
- 从超类开始调用构造方法(双亲委派机制)
- 子类成员变量初始化,子类构造方法调用
一旦对象被创建,并被分派给某些变量赋值,这个对象的状态就切换到了应用阶段。
应用阶段
对象至少被一个强引用持有
不可见阶段
即对象超出了所在的作用域(对象在某方法中创建并引用,某方法已调用完毕,对象进入不可见阶段,无法再被引用)
不可达阶段
对象处于不可达阶段是指该对象不再被任何强引用所持有。
与“不可见阶段”相比,“不可见阶段”是指程序不再持有该对象的任何强引用,这种情况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会导致对象的内存泄露情况,无法被回收。
收集阶段
当垃圾回收器发现该对象已经处于“不可达阶段”并且垃圾回收器已经对该对象的内存空间重新分配做好准备时,则对象进入了“收集阶段”。如果该对象已经重写了finalize()方法,则会去执行该方法的终端操作。
终结阶段
当对象执行完finalize方法后进入不可达状态,等待GC回收
线程的生命周期
概述
线程的生命周期包含6种状态:
- 新建(NEW)
- 可运行(RUNNABLE)
- 阻塞(BLOCKED)
- 等待(WAITING)
- 计时等待(TIMED_WAITING)
- 死亡(TERMINATED)
当线程进入运行状态后,一般的操作系统是采用抢占式(两种线程调度模式之一,另外一种为分时调度)的方式来让线程获得CPU。所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞、就绪之间切换。
新建(NEW)
使用new方法,new出来线程,此时仅仅由JAVA虚拟机为其分配内存,并初始化成员变量的值。此时未调用start方法,程序未运行,程序进入RUNNABLE状态。
可运行(RUNNABLE)
Java 中的 Runable 状态对应操作系统线程状态中的两种状态,分别是 Running(运行中) 和 Ready(等待CPU分配资源),也就是说,Java 中处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
所以,如果一个正在运行的线程是 Runnable 状态,当它运行到任务的一半时,执行该线程的 CPU 被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是 Runnable,因为它有可能随时被调度回来继续执行任务。
阻塞状态(BLOCKING)
区别于可运行的RUNNABLE状态,另外有三种情况导致线程无法运行,分别为:BLOCKED(被阻塞)、WAITING(等待)、TIMED_WAITING(计时等待),可统称阻塞状态(BLOCKING)
被阻塞(BLOCKED)
当处于RUNNABLE状态的线程,因为某些原因(没有拿到同步锁或者IO请求被阻塞),放弃了CPU的使用,此时进入BLOCKED状态,暂停运行,JVM也不会再分配CPU给线程,直到线程重新回到RUNNABLE状态。
等待(WAITING)
运行中的程序由于某些条件未满足,主动调用Object.wait()/Thread.join()/LockSupport.park()等方法,程序进入WAITING状态(放入对象等待池中),等待其他线程的唤醒。
计时等待(TIMED_WAITING)
调用wait方法时加入了等待时长参数,或者调用了sleep方法,进入计时等待,超时后自动被系统唤醒,也可以被notify提前唤醒
死亡状态(TERMINATED)
- run方法执行完毕,线程正常退出
- 出现未捕获的异常,线程意外停止
以上两种情况将导致线程终止
线程间切换
- BLOCKED进入RUNNABLE
- 线程获取锁后即可从BLOCKED进入RUNNABLE状态,BLOCKED无超时机制,如没拿到锁将一直阻塞
- WAITING/TIMED_WAITING进入RUNNABLE
- 等待中的线程首先被notify/notifyALl唤醒,进入BLOCKED阻塞状态,获取锁后重新进入RUNNABLE状态
Spring Bean的生命周期
概述
Spring Bean的主要生命周期为以下四个阶段:
2. 前置/后置处理器
5. 前置后置处理器
Servlet生命周期
Servlet生命周期主要为以下四个阶段:
- 加载阶段(Loading)
- 初始化阶段(Initialization)
- 处理请求阶段(Request Handling)
- 销毁阶段(Destruction)
加载阶段(Loading)
服务器启动时(第一次访问时,如果在注解中设置loadOnStartup为一个非-1的值,则设置为服务器启动时加载并创建实例),会加载 Servlet 类并创建对应的 Servlet 实例。这个过程只会执行一次(懒加载机制)。
- 优点:节约内存开销
- 缺点:第一个用户的体验感不好
- 把注解加上启动参数loadOnStartup 非负数的时候容器加载的时候就启动了
@WebServlet(value = "/myServlet",loadOnStartup = 0)
初始化阶段(Initialization)
在 Servlet 实例被创建后,容器会调用其 init() 方法来完成初始化操作。在初始化期间,Servlet 可以读取配置参数和其他上下文信息,并准备好处理客户端请求。
处理请求阶段(Request Handling)
在 Servlet 初始化完成后,容器会将客户端的请求转发给 Servlet 实例,并调用其 service() 方法来处理请求。service() 方法会根据请求的类型(GET、POST 等)来调用对应的 doGet()、doPost() 等方法进行处理。可以访问多次
销毁阶段(Destruction)
当 Servlet 实例不再需要时,容器会调用其 destroy() 方法来释放资源。这个过程只会执行一次。(也可以手动调用destroy方法)
注意
Servlet 生命周期中的各个阶段并非一成不变的。在某些情况下,例如 Servlet 初始化失败或容器需要重新加载 Servlet 类时,Servlet 生命周期可能会被打断或重新开始。