程序员

阿里面试真题讲解:详解类的生命周期,一文带你掌握

2020-08-11  本文已影响0人  程序员匡胤

详解类的生命周期

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


class(编译成.class文件)

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

Loading(加载)

有哪些类加载器?

  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文件

什么是双亲委派机制?

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()方法

LazyLoading

  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(执行)

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相关技术文章或行业资讯,欢迎大家关注和转发文章!

上一篇下一篇

猜你喜欢

热点阅读