Java 对象的生命周期

一、类的加载过程

参考:https://blog.****.net/xuemengrui12/article/details/82707473

1、一个Java文件从编码完成到最终执行,一般主要包括两个过程

编译,即把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件。

运行,则是把编译声称的.class文件交给Java虚拟机(JVM)执行。

而我们所说的类加载过程即是指JVM虚拟机把.class文件中类信息加载进内存,并进行解析生成对应的class对象的过程。

 

2、类加载器就是寻找类或接口字节码文件进行解析并构造JVM内部对象表示的组件,在java中类装载器把一个类装入JVM,经过以下步骤:

2.1)、加载:查找和导入Class文件(Class对象还不完整,所以此时的类还不可用)

         
2.2)、链接:(其中解析步骤是可以选择的)

(a)检查:检查载入的class文件数据的正确性

(b)准备:给类的静态变量分配存储空间

(c)解析:将符号引用转成直接引用(举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。)

2.3)、初始化:对静态变量,静态代码块执行初始化工作

( 如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类; 如果类中存在初始化语句,就依次执行这些初始化语句。)

例子:https://blog.****.net/adavinajh/article/details/90957625

 

3、加载、 验证、准备和初始化这四个阶段的顺序是确定的,类的加载过程必须按照这种顺序接部就班地开始,而解析则不一定: 它在某些情况下可以在初始化阶段之后再开始, 这是为了支持 Java语言的运行时绑定 (也称为动态绑定https://www.jianshu.com/p/25c84710a052或晩期绑定)。

4、类加载的时机:

4.1)遇到 new、 getstatic、 putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化, 则需要先触发其初始化, 生成这4条指令的最常见的 Java代码场景是:

使用 new关键字实例化对象的时候
读取或设置一个类的静态字段的时候(即在字节码中,执行getstalic或putstatic指令时),被final修饰、已在编译期把结果放入常量池的静态字段除外
调用一个类的静态方法的时候(即在字节码中执行invokestatic指令时)。
4.2 ) 当调用Java API中的某些反射方法时, 比如类Class中的方法或者java.lang.reflect包的方法对类进行反射调用的时候, 如果类没有进行过初始化 , 则需要先触发其初始化。

4.3 ) 当初始化一个类的时候, 如果发现其父类还没有进行过初始化, 则需要先触发其父类的初始化。

4.4) 当虚拟机启动时, 用户需要指定一个要执行的主类(包合 main()方法的那个类) . 虚拟机会先初始化这个主类。

4.5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

 

二、类加载的双亲委托机制

1.类与类加载器

对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。

2.双亲委派模型

从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,属于虚拟机自身的一部分。另外一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。
 

Java 对象的生命周期

 

双亲委派机制的工作流程:

2.1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。

每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。

2.2.  当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.

2.3.  当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
 

 

3. 双亲委派模式破坏

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派,这里仅仅是举了破坏双亲委派的其中一个情况。这里用到了SPI(https://blog.****.net/sinat_34976604/article/details/86743260

 

应用到破坏的例子

jdbc(https://www.cnblogs.com/joemsu/p/9310226.html#_caption_2

tomcat(https://blog.****.net/sinat_34976604/article/details/86709870)

 

 

三、垃圾回收机制

https://blog.****.net/weixin_39067991/article/details/81045201

提到java的垃圾回收机制就不得不提一个方法:

 System.gc()用于调用垃圾收集器,在调用时,垃圾收集器将运行以回收未使用的内存空间。它将尝试释放被丢弃对象占用的内存。
 然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用。
 所以System.gc()并不能说是完美主动进行了垃圾回收。

三个角度理解下
1、jvm怎么确定哪些对象会进行回收
1.1  引用计数法:无法解决循环依赖
1.2 可达性分析算法:通过判断对象的引用链是否可达来决定对象是否可以被回收 (GC ROOT :非用户自定义类加载器加载的对象,运行的线程,同步的锁,native,常量,栈中的对象)

2、jvm会在什么时候进行垃圾回收的动作
2.1 会在cpu空闲的时候自动进行回收
2.2在堆内存存储满了之后
2.3主动调用System.gc()后尝试进行回收


3、jvm到底是怎么清楚垃圾对象的

Java 对象的生命周期