Java程序员面试复习手册

Java虚拟机知识点快速复习手册(下)

2019-02-08  本文已影响0人  蛮三刀酱

前言

本文快速回顾了常考的的知识点,用作面试复习,事半功倍。

上篇主要内容为:虚拟机数据区域,垃圾回收

下篇主要内容为:类加载机制

参考

类加载机制

类是在运行期间动态加载的。

类的生命周期

包括以下 7 个阶段:

其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。

类加载过程

包含了加载、验证、准备、解析和初始化这 5 个阶段。

1. 加载

加载是类加载的一个阶段,注意不要混淆。

加载过程完成以下三件事:

其中二进制字节流可以从以下方式中获取:

2. 验证

确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

3. 准备

准备阶段为static修饰的变量分配内存并设置初始值,使用的是方法区的内存。

实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在Java堆中。

初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。

public static int value = 123;

如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。

public static final int value = 123;

4. 解析

将常量池的符号引用替换为直接引用的过程。

其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定

5. 初始化

初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 <clinit>() 方法的过程。

在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。

<clinit>() 方法具有以下特点:

public class Test {
    static {
        i = 0;                // 给变量赋值可以正常编译通过
        System.out.print(i);  // 这句编译器会提示“非法向前引用”
    }
    static int i = 1;
}
static class Parent {
    public static int A = 1;
    static {
        A = 2;
    }
}

static class Sub extends Parent {
    public static int B = A;
}

public static void main(String[] args) {
     System.out.println(Sub.B);  // 2
}

类初始化时机

主动引用

虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化加载、验证、准备都会发生):

被动引用

以上 5 种场景中的行为称为对一个类进行主动引用。

除此之外,所有引用类的方式都不会触发初始化,称为被动引用。

被动引用的常见例子包括:

System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
SuperClass[] sca = new SuperClass[10];
System.out.println(ConstClass.HELLOWORLD);

类加载器

实现类的加载动作。在 Java 虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。

1. 类与类加载器

两个类相等:类本身相等,并且还要使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。

这里的相等,包括类的

2. 类加载器分类

从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:

从 Java 开发人员的角度看,类加载器可以划分得更细致一些:

3. 双亲委派模型

应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。

下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。

该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器

这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承的关系实现。

[图片上传失败...(image-78b66d-1549615529620)]

(一)工作过程

一个类加载器首先将类加载请求传送到父类加载器,只有当父类加载器无法完成类加载请求时才尝试加载。

(二)好处

Java类伴随其类加载器具备了带有优先级的层次关系,确保了在各种加载环境的加载顺序。
保证了运行的安全性,防止不可信类扮演可信任的类。

例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 的类并放到 ClassPath 中,程序可以编译通过。因为双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。正因为 rt.jar 中的 Object 优先级更高,因为程序中所有的 Object 都是这个 Object。

(三)实现

以下是抽象类java.lang.ClassLoader的代码片段,其中的 loadClass() 方法运行过程如下:

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    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) {
                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.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

4. 自定义类加载器实现

FileSystemClassLoader 是自定义类加载器,继承自 java.lang.ClassLoader,用于加载文件系统上的类。它首先根据类的全名在文件系统上查找类的字节代码文件(.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。

loadClass() 实现了双亲委派模型的逻辑,因此自定义类加载器一般不去重写它,但是需要重写 findClass() 方法。

public class FileSystemClassLoader extends ClassLoader {

    private String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassData(String className) {
        String path = classNameToPath(className);
        try {
            InputStream ins = new FileInputStream(path);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String classNameToPath(String className) {
        return rootDir + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
    }
}

双亲委派的理解

https://blog.csdn.net/inspiredbh/article/details/74889654

双亲委派模型的实现过程:

实现双亲委派模型的代码都集中在java.lang.ClassLoader的loadClass()方法中:

1.当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求委派给父类加载器Extension ClassLoader去完成。

2.当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派给父类加载器Bootstrap ClassLoader去完成。

3.如果Bootstrap ClassLoader加载失败(在<JAVA_HOME>\lib中未找到所需类),就会让Extension ClassLoader尝试加载。

4.如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载。

5.如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载

6.如果均加载失败,就会抛出ClassNotFoundException异常。

双亲委派模型的优点:

保证了运行的安全性,防止不可信类扮演可信任的类。

Class.forName()的理解

https://blog.csdn.net/fengyuzhengfan/article/details/38086743

Class.forName(className)可以简单的理解为:获得字符串参数中指定的类,并进行初始化操作。

Class.forName的一个很常见的用法是在加载数据库驱动的时候。

首先你要明白在java里面任何class都要装载在虚拟机上才能运行。

  1. forName这句话就是装载类用的(new是根据加载到内存中的类创建一个实例,要分清楚)。

  2. 至于什么时候用,可以考虑一下这个问题,给你一个字符串变量,它代表一个类的包名和类名,你怎么实例化它?

A a = (A)Class.forName("pacage.A").newInstance();
这和 A a =new A();是一样的效果。
  1. jvm在装载类时会执行类的静态代码段,要记住静态代码是和class绑定的,class装载成功就表示执行了你的静态代码了,而且以后不会再执行这段静态代码了。

  2. 动态加载和创建Class 对象,比如想根据用户输入的字符串来创建对象

String str = 用户输入的字符串  
Class t = Class.forName(str);  
t.newInstance();

newInstance()方法和new关键字最主要的区别

JVM 参数

GC 优化配置

配置 描述
-Xms 初始化堆内存大小
-Xmx 堆内存最大值
-Xmn 新生代大小
-XX:PermSize 初始化永久代大小
-XX:MaxPermSize 永久代最大容量

GC 类型设置

配置 描述
-XX:+UseSerialGC 串行垃圾回收器
-XX:+UseParallelGC 并行垃圾回收器
-XX:+UseConcMarkSweepGC 并发标记扫描垃圾回收器
-XX:ParallelCMSThreads= 并发标记扫描垃圾回收器 = 为使用的线程数量
-XX:+UseG1GC G1 垃圾回收器
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar

关注我

我是蛮三刀把刀,目前为后台开发工程师。主要关注后台开发,网络安全,Python爬虫等技术。

来微信和我聊聊:yangzd1102

Github:https://github.com/qqxx6661

原创博客主要内容

同步更新以下博客

1. Csdn

http://blog.csdn.net/qqxx6661

拥有专栏:Leetcode题解(Java/Python)、Python爬虫开发

2. 知乎

https://www.zhihu.com/people/yang-zhen-dong-1/

拥有专栏:码农面试助攻手册

3. 掘金

https://juejin.im/user/5b48015ce51d45191462ba55

4. 简书

https://www.jianshu.com/u/b5f225ca2376

个人公众号:Rude3Knife

个人公众号:Rude3Knife

如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~

上一篇下一篇

猜你喜欢

热点阅读