欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java开发中Class类的生命周期

程序员文章站 2022-07-10 18:32:47
详解类加载过程(双亲委派机制)我们知道java文件编译称.class文件之后,要加载(Loading)到内存,然后初始化(Initializing)之后才能被执行,本文主要讲下类加载的过程,下图是类的生命周期图。有哪些类加载器?为什么要使用双亲委派机制加载类?什么是双亲委派机制?怎么写自定义加载类?怎么打破双亲委派机制?......



详解类的生命周期

  1. 我们知道java文件编译称.class字节码文件之后,要加载(Loading)到内存,接着对字节码进行链接(Linking),然后初始化(Initializing)之后才能被使用执行,使用完成之后,被卸载回收(GC),下图是类的生命周期(Class Cycle)图。
    Java开发中Class类的生命周期

class(编译成.class文件)

  1. java c 命令把java文件编译成,class字节码文件

Loading(加载)

有哪些类加载器?

  • java类加载器总分为以下四种:
  1. BootstrapClassLoader 启动类加载器,加载java核心库lib/rt.jar、charsets.jar等,C++编写,如果调用getClassLoader()是个null,说明当前类由该加载器加载,ExtClassLoaderAppClassLoader加载器也在rt.jar包下,所以者两个加载器类的加载器也是BootstrapClassLoader,使用下面代码打印加载类的范围
 String bootPath = System.getProperty("sun.boot.class.path"); System.out.println(bootPath.replaceAll(";", System.lineSeparator())); 
  1. ExtClassLoader 扩展类加载器,java编写,加载扩展jar包jre/lib/ext/*.jar,或者由-Djava.ext.dirs指定,使用下面代码打印加载类的范围
 String extPath = System.getProperty("java.ext.dirs"); System.out.println(extPath.replaceAll(";", System.lineSeparator())); 
  1. AppClassLoader 系统类加载器,java编写,加载程序所在的目录clssspath或者java.class.path系统属性类,如user.dir所在的位置的class,使用下面代码打印加载类的范围
 String appPath = System.getProperty("java.class.path"); System.out.println(appPath.replaceAll(";", System.lineSeparator())); 
  1. CustomClassLoader 用户自定义类加载器,java编写,用户自定义的类加载器,可加载指定路径的class文件

什么是双亲委派机制?

  • 双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会看自己有没有加载过该类,加载过直接返回,没加载过,把请求委派给父加载器,每个类加载器都是如此,直到顶层的类加载器BootstrapClassLoader,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载。
  • 注意: 这里说父类加载器,并不是说明这些加载器是继承的关系,这里说的父类加载器,是指加载器里面维护了一个parent 变量,parent就是指当前加载器的父类加载器
  • 类加载过程如下图:
    Java开发中Class类的生命周期
  • 类加载器主要源码,核心:findLoadedClass()-> parent.loadClass() -> findClass()
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //委托父加载器加载 c = parent.loadClass(name, false); } else { //如果父加载器为空,那么调用BootStrapClassLoader来加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //调用自己的findClass(name)方法来加载类。 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } //是否需要链接该类 if (resolve) { resolveClass(c); } return c; } //由子类加载器实现 protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } 

为什么要使用双亲委派机制加载类?

  1. 任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。

  2. 类没必要加载到内存多次,带来内存消耗的同时,也带来了安全性问题

  3. Java中基础的类,比如说String类,如果用户自己编写了一个java.lang.String类,并把它放到了ClassPath中,会出现很多个String类,这样Java类型体系中最基础的行为都无法保证,应用程序也将一片混乱。

怎么写自定义类加载器?

  1. 创建一个自己的加载器,继承 ClassLoader
  2. 重写ClassLoader类的findClass()方法,调用ClassLoader定义类的方法defineClass(),返回Class对象
  3. 可以在findClass里面加一些自己的一些逻辑,比如说将编译后的class文件进行加密,只有自己定义的ClassLoader可以加载成功等
  4. 下面是自定义加载器代码:
public class MyClassLoader extends ClassLoader { @Override protected Class<?> findClass(String name) throws ClassNotFoundException { File file = new File("/Users/", name.replace(".", "/").concat(".class")); FileInputStream fis = null; ByteArrayOutputStream baos = null; try { fis = new FileInputStream(file); baos = new ByteArrayOutputStream(); int b = 0; while ((b = fis.read()) != 0) { baos.write(b ^ seed); } byte[] bytes = baos.toByteArray(); return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { e.printStackTrace(); } finally { if (baos != null) { try { baos.close(); } catch (IOException e) { e.printStackTrace(); } } if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } return super.findClass(name);//throws ClassNotFoundException() } public static void main(String[] args) throws Exception { ClassLoader l = new MyClassLoader(); String className = "com.cl.Hello"; Class clazz = l.loadClass(className); Class clazz1 = l.loadClass(className); System.out.println(clazz == clazz1); Hello h = (Hello) clazz.newInstance(); h.m(); System.out.println(l.getClass().getClassLoader()); System.out.println(l.getParent()); System.out.println(getSystemClassLoader()); } } 

如何打破双亲委派机制?

  1. 重写 ClassLoader类中的 loadClass()方法
  • 何时打破过?
    1. JDK1.2之前,自定义ClassLoader都必须重写loadClass()
    2. ThreadContextClassLoader可以实现基础类调用实现类代码,通过thread.setContextClassLoader指定
    3. 热启动,热部署,osgi tomcat 都有自己的模块指定classloader(可以加载同一类库的不同版本)

LazyLoading

  • 严格讲应该叫lazy initialzing,因为java虚拟机规范并没有严格规定什么时候必须loading,但严格规定了什么时候initialzing,以下5种情况把必须初始化类:
  1. new getstatic putstatic invokestatic指令,访问final变量除外
  2. java.lang.reflect对类进行反射调用时
  3. 初始化子类的时候,父类首先初始化
  4. 虚拟机启动时,被执行的主类必须初始化
  5. 动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化

Linking(链接)

  1. verification 验证,验证文件是否符合JVM规定
  2. preparation 准备,静态成员变量赋默认值
  3. resolution 解析,将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用

Initializing(初始化)

  • 调用类初始化代码 ,给静态成员变量赋初始值

Execute(执行)

  • 类的执行有三种方式,混合执行 编译执行 解释执行
    1. 解释执行(-Xint):解释器解释执行所有字节码,启动快执行稍慢
    2. 编译执行(-Xcomp):使用纯编译模式,执行快,启动很慢
    3. 混合执行(-Xmixed) :开始解释执行,启动速度较快,对热点代码(-XX:CompileThreshold=10000)实行检测和编译
  • 新版JVM默认类执行方式是混合执行 mixed mode
java version "1.8.0_201" Java(TM) SE Runtime Environment (build 1.8.0_201-b09) Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode) 

GC(回收)

  • 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
  • 如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了

其他

  • 类加载过程: 加载到内存 -> 静态变量赋默认值 -> 静态变量赋初始值
  • 对象创建过程:申请内存 -> 成员变量赋默认值 -> 执行构造方法成员变量赋初始值

本文地址:https://blog.csdn.net/lost_deer/article/details/107882858

相关标签: JVM java class