JVM之详解Java类的装载过程及类加载过程

标题1 问题

为什么要学习JVM?学习JVM是为了什么?

JVM屏蔽了不同操作系统之间的差异,这是Java语言能够Write Once,Run Anywhere的根本。

 

JDK:JRE:JVM三者之间的区别?

JDK=JRE+开发工集(例如:Javac编译工具等)

JRE=JVM+基础标准类库

 

Java程序运行的过程:.java->.class->加载到JVM

字节码文件的结构:参考字节码文件格式和虚拟机规范。

从.java转换到.class文件只不过是转换了一种形式而已。

 

另一种理解字节码文件的方式:javap反编译字节码文件。

 

JVM是跨语言的平台

JVM之详解Java类的装载过程及类加载过程

 

 

字节码文件“装到”JVM的机制——类加载(的过程)

三个步骤:loading->linking->initializing

1.加载:通过类的全限定名来获得文件名,并查找导入磁盘的class文件

2.链接:

1)验证:保证字节码文件的格式正确(版本号、字节码、符号引用等)。

2)准备:为类的静态变量分配内存,并将其初始化为默认初始值。

3)解析:把类中的符号引用转化为直接引用。符号引用指的是由一组符号描述的目标,它可以是任何的字面量;直接引用是指直接指向目标的指针,或者说是真实的指针地址。

JVM之详解Java类的装载过程及类加载过程

3.初始化:为类的静态变量赋值,然后执行类的初始化(static)语句。初始化的详细过程:

1)如果类还没有被加载和链接(即没有执行前两个过程),那就先进行加载和初始化;

2)如果类存在父类,并且父类还没有初始化,那就先初始化直接父类;

3)如果类中存在初始化语句,顺序执行初始化语句。

 

class初始化时机:

1.创建类的实例(四种方式);(哪四种方式:new一个对象;反射;序列化、反序列化;克隆)(说得再直白点,如果类的实例已经存在,那么相应的字节码文件一定也存在)

2.访问类中的某个静态变量,或者对静态变量进行赋值;

3.主动调用类的静态方法;

4.Class.forname("包类名");(完成了类的初始化)

5.完成子类的初始化,也完成对本类的初始化(接口例外);

6.该类是程序引导入口(main方法入口或者test入口)。

Demo实例:

//Demo.java

//F.java
public class F {
    public static int FCount = 200;
    static {
        System.out.println(FCount);
    }
    
}

//S.java
public class S extends F {
    public static int SCount = 100;
    static {
        System.out.println(Scount);
    }
    
}

//Test.java
public class Test {
    public static void main(String[] args)
        S.SCount = 1000; //问题:会先执行哪条打印语句
    }
    
}

 

问题大难:先执行FCount的打印语句。原因:见初始化时机第5条。

 

 

类的加载的实现者:类加载器

JDK自带的Class Loader一共有三个,可以分成两大类:

1.Bootrap ClassLoader;

2.Extension ClassLoader;

3.Application ClassLoader

 

Bootrap ClassLoader介绍:

1.JVM自带的引导类加载器,由C/C++的语言实现,在Java中打印null;

2.加载Java的核心类库,$JAVA_HOME中jre/lib/rt.jar、resource.jar或Java程序运行指定的Xbootclasspath选项jar包;

3.指定加载java,javax,sun等开头的包类名。

 

如果自定义了一个类,这个类的包名为java.lang,那么new一个这个自定义类的对象就会报错,因为java开头的包类名不能自定义类!

Extension ClassLoader介绍:

1.Java语言编写的类加载器sun.misc.Launcher$ExtClassLoader(静态内部类)

2.指定Bootrap ClassLoader为Parent加载器-->getParent()可以获取Bootrap ClassLoader

3.负责架子啊java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext或-Djava.ext.dirs指定目录下的jar包(如果我们自定义的class需要交给Ext来加载可以放置到ext的目录下)

 

Application ClassLoader介绍:

1.Java语言编写的类加载器sun.misc.Launcher$APPClassLoader(静态内部类)

2.该加载器是Java程序默认的类加载器,Java应用的类都是该类加载器加载的

3.指定Extension ClassLoader为parent加载器-->getParent()可以获取Extension ClassLoader

4.负责加载环境变量classpath指定的目录,或者java.class.path指定的目录类库

 

 

双亲委派加载机制/模型(面试常考)

JVM之详解Java类的装载过程及类加载过程

从源码分析可知,Extension ClassLoader和Application ClassLoader都是继承于ClassLoader这个抽象类。ClassLoader中的loadClass方法中实现了双亲委派:

loadClass源码:

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 {
                        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();
                    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;
        }
    }

这段代码的逻辑是:

1.先调用方法findLoadedClass()来判断是否已经存在这个类;

2.若存在,则跳过不执行;否则,执行try语句块中的代码;

3.递归判断parent是否为空并调用parent的loadClass方法。由于刚开始调用时类加载器为Application ClassLoader,所以parent为Extension ClassLoader不是null,继续调用parent的loadClass方法,Extension ClassLoader的parent为Bootrap ClassLoader,这时parent为null(表示不可访问),就会执行try语句块中else部分语句findBootstrapClassOrNull方法,这个方法调用C/C++编写的native方法private native Class<?> findBootstrapClass(String name)来尝试加载这个类;接着也是一个递归过程。

4.如果Bootrap ClassLoader无法加载并且没有找到这个类就会交给子类Extension ClassLoader来尝试加载,如果它也无法加载并且没有找到对应的类,那么最终就会交给Application ClassLoader去加载,由Application ClassLoader实现类的加载过程。

 

双亲委派加载机制/模型的作用:

1.避免类的重复加载;

2.保护程序安全,放置核心的Java语言环境遭受破坏。

如何理解?例如:java.lang包下的类都是非常宝贵的,例如String类,如果我自己定义了一个也叫做String的类,那么如何保证核心类库下的String类能够被正常加载呢?当new一个String对象时,实际上是由Bootrap ClassLoader调用native的方法findBootstrapClass得到的一个final类,而不是自定义的类所对应的对象。

进一步,如果想要破坏这样一个逻辑,或者说想要new的对象是自定义的String类的对象,那么可以通过重写loadClass方法来实现;换句话说,这也是为什么Application ClassLoader、Extension ClassLoader以及Bootrap ClassLoader没有重写这个方法的原因,所以只要调用loadClass方法就一定会按照这个逻辑执行。

 

为什么要自定义类加载器?

1.适配数据源

2.环境隔离;

3.jar包共享;

4.防止源码泄露。

 

 

初始运行时数据区

JVM之详解Java类的装载过程及类加载过程

在JVM中划分了几大区域来分别存储不同时期或者不同class文件产生的不同的数据,有些区域随着JVM的启动就创建了并且随着JVM的关闭而销毁;其他的数据区域则是跟线程相关,即随着线程的创建而创建,随着线程的死亡而销毁。主要包括(参考JVM结构第2.5小节-Runi-Time Data Areas):

1. The pc Register(程序计数器)

通俗的讲,就是指下一个命令的地址。它与线程同创建同销毁。

由于JVM支持多线程,每个线程都会有自己的程序计数器,线程执行方法的过程中都会记录这个程序计数器。例如,线程A在调用某个方法,执行到某个过程时由于某种原因失去CPU执行权,而线程B获得了CPU执行权,当线程B完成某个任务之后,线程A又获得了CPU执行权,那么JVM需要知道从哪里开始继续执行。简单而言,就是用来保存当前当前线程当中执行的位置。

 

2. JVM stacks(Java虚拟机栈)

指当前执行线程的独占空间。与线程同创建同销毁。

初学时,我们简单粗暴的将JVM分为栈和堆,其中的栈就是这个虚拟机栈。

它以栈的形式存在,即先进后出。

保存的是栈帧(frame),每个栈帧中又保存了一些数据结构,例如局部变量、动态链接、返回地址等;或线程的运行状态,或理解为调用的方法。

 

3. Heap(堆)

程序运行中最需要关注的内容。

线程共享的。与虚拟机同创建同消亡。

存储类的实例即对象和数组。

分为老年代、新生代,新生代又分为存活区、伊甸区,思考这样分的原因?

 

4. Method Area(方法区)

是所有线程共享的区域。随着JVM启动而创建。

保存的是类已经加载的信息,例如常量池、属性和方法数据以及方法体和构造器中编译后的代码。这些数据基本不变,而经常发生变化的保存在堆中

如果方法区的内存无法满足申请的需求时,会报出OutOfMemoryError的错误。

JDK8以前叫做永久代(Perm Space),1.8叫做metaspace。

 

5. Run-Time Constant Pool(运行时常量池)

每个常量池由JVM中的方法区进行分配。

 

6. Native Method Stacks(本地方法栈)

native方法(C/C++编写的方法)的保存位置,不能用JVM栈保存

 

 

JVM执行引擎的组成

JVM本质上也是一个程序、一个进独立的进程,最终还是要交给操作系统去运行,这就涉及到执行引擎。

JVM之详解Java类的装载过程及类加载过程

思考问题:Java是编译型语言还是解释型语言?   Ans:解释+编译

解释器:把class字节码文件中的指令翻译成机器语言、机器能够马上执行的指令;

即时编译器:

垃圾回收器:对内存共享区域(例如堆、方法区)的自动处理。

 

总结

JVM之详解Java类的装载过程及类加载过程

注意,图中把常量池合并到了方法区。

 

 

 

 

 

 

 

 

上一篇:统计某一字段等于不同值的个数的sql语句(分享)


下一篇:C#码农的大数据之路 - 使用Ambari自动化安装HDP2.6(基于Ubuntu16.04)并运行.NET Core编写的MR作业