Class Cycle java类的生命周期

10 篇文章 0 订阅

Class Cycle

类的周期

再jvm中一个class 通过了 loader --> linking --> initializing -->gc 的过程完成了一次生命周期。

而linking中又分为三步Verification Preparation Resolution 。

loader 类加载

加载过程 “parents delegate”

“parents delegate” 翻译过来 也就是双亲加载。其实就是个名字。实际操作就是parent class loader而已。
摘录了 java API doc 原文关于 loading 文档
原文:

The Java Class Loading Mechanism

The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.
Here are some highlights of the class-loading API:

  • Constructors in java.lang.ClassLoader and its subclasses allow you to specify a parent when you instantiate a new class loader. If you don’t explicitly specify a parent, the virtual machine’s system class loader will be assigned as the default parent.
  • The loadClass method in ClassLoader performs these tasks, in order, when called to load a class:
    1. If a class has already been loaded, it returns it.
    2. Otherwise, it delegates the search for the new class to the parent class loader.
    3. If the parent class loader does not find the class, loadClass calls the method findClass to find and load the class.
  • The findClass method of ClassLoader searches for the class in the current class loader if the class wasn’t found by the parent class loader. You will probably want to override this method when you instantiate a class loader subclass in your application.
  • The class java.net.URLClassLoader serves as the basic class loader for extensions and other JAR files, overriding the findClass method of java.lang.ClassLoader to search one or more specified URLs for classes and resources.
    To see a sample application that uses some of the API as it relates to JAR files, see the Using JAR-related APIs lesson in this tutorial.
类加载器的类别
  1. BootstrapClassLoader 启动类加载器

c++编写,加载java核心库 java.*,构造ExtClassLoaderAppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

System.getProperty("sun.boot.class.path")
  1. ExtClassLoader 标准扩展类加载器

c++编写,加载java核心库 java.*,构造ExtClassLoaderAppClassLoader。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作

System.getProperty("java.ext.dirs")
  1. AppClassLoader 系统类加载器

java编写,加载程序所在的目录,如user.dir所在的位置的class

System.getProperty("java.class.path")
  1. CustomClassLoader 用户自定义类加载器

java编写,用户自定义的类加载器,可加载指定路径的class文件

核心源码
// name 需要加载的类名称
//resolve 是否解析。解析的动作参看Linking 章节的 Resolution
protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查这个classsh是否已经加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // c==null表示没有加载,如果有父类的加载器则让父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果父类的加载器为空 则说明递归到bootStrapClassloader了
                        //bootStrapClassloader比较特殊无法通过get获取
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {}
                if (c == null) {
                    //如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
//findCLass 是需要子类来具体实现的
  protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
  
加载流程图

delegation model for loading

机制与作用
  1. 防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
  2. 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。
需要注意

需要注意的一点是在根据加载器的类别来看 parent是面向对象的概念 ;而不是代码实现继承的意思。很多人都认为他们是继承了相同的UrlClassLoader就认为他们是平级的他们的parent应该是UrlClassLoader。这里不是这个概念,举例来说你和你爸都是人,之所以他是你爸说明你的属性父亲指向了那个男人,而不是说你俩都是人你俩就平级了,这里的parent 是属性而不是 代码实现中 extends 父类的概念。

关于打破双亲委派的办法
  • 如何打破:重写 ClassLoader 中的 loadClass()方法
  • 何时打破: 使用JNDI服务、代码模块热部署

加载方式

LazyLoading java对class的加载方式是lazy的。但准确的说应该是Lazy Initializing
再JVM 的规范中也并没有明确的规定什么时候进行loading 也没有规定loading以后的Class 是放在内存的什么位置【这里的位置指堆栈元等】

但是JVM规定了什么时候必须进行初始化操作。【以下五种情况】

  • new getstatic putstatic invokestatic指令,访问final变量除外
  • java.lang.reflect对类进行反射调用时
  • 初始化子类的时候,父类首先初始化
  • 虚拟机启动时,被执行的主类必须初始化
  • 动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化

自定义类加载器

  1. 继承ClassLoader 类

  2. 实现 findClass 方法。若要类被加载需要调用父类的defineClass 方法

    public class MyLoader extends ClassLoader {
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            File f = new File( name.replace(".", "/").concat(".class"));
            try {
                FileInputStream fis = new FileInputStream(f);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int b = 0;
                while ((b=fis.read()) !=0) {
                    baos.write(b);
                }
                byte[] bytes = baos.toByteArray();
                baos.close();
                fis.close();
                return defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return super.findClass(name);
        }
    
        public static void main(String[] args) throws Exception {
            ClassLoader l = new MyLoader();
            Class c1 = l.loadClass("com.Hello");
            Class c2 = l.loadClass("com.Hello");
            //此时比可以认为 c1 被加载过以后 Class的对象只会有一份 所以c1 必然== c2
            System.out.println(c1 == c2);
            Hello h = (Hello)c1.newInstance();
            h.m();
            System.out.println(l.getClass().getClassLoader());
            System.out.println(l.getParent());
            System.out.println(getSystemClassLoader());
        }
    }
    

Linking & Initializing

  • Verification

验证文件是否符合JVM规定 比如看你二进制文件内容的首部分是不是 CAFEBABE

  • Preparation

静态成员变量赋默认值

  • Resolution

解析 在ClassLoader中 源码中 的参数resolve 指定是否对类进行解析操作true时进行解析操作。
将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用;同理有很多的动态引用,也就是用到了再去设置地址,用不上就不设置。

Initializing

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

关于 Preparation 与 Initializing

通过一段代码来了解

public class C {
    public static void main(String[] args) {
        System.out.println(T.count); 
    }
}
class T {
    public static T t = new T(); // line A
    public static int count = 2; // line B
    private T() {
        count ++;
    }
}

如上代码 当前代码输出结果为什么? 当lineA与lineB调换顺序后输出结果为什么?
分析如下:
当执行main后调用classloader加载 class T 在 Preparation 设置初始值。t对象为null count为0;再到Initializing阶段先对t进行赋值为new T() 而T() 中执行了count++,这时count的值由0进行++变成了1,但下一步操作对count进行赋值2的操作,count原先值1变为了 2。由此可见。针对于count的变化为初始值0 ,++后的1 ,=号赋值的2.即输出结果为2
当line A与 lineB进行调换后。不同的是在Initializing阶段会先执行line B的操作对count进行赋值2的操作再进行new T()的操作,再T()中又对count进行了++ 操作,这时count变成了3,这次针对count的变化变成了。初始值的0,=号赋值的2,++后的3.即输出结果为3

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值