Skip to main content

java面经-JVM篇

·551 words·3 mins
WFUing
Author
WFUing
A graduate who loves coding.
Table of Contents

1. 说一说JVM的内存模型
#

2. JAVA类加载的过程是怎么样的?什么是双亲委派机制?有什么作用?一个对象从加载到JVM,再到GC清除,都经历了什么?
#

public class ClassLoaderDemo {

    public static final String aaa = "aaaa";

    public static void main(String[] args) throws Exception {
        // 父子关系 AppClassLoader <- ExtClassLoader <- Bootstrap ClassLoader
        ClassLoader cl1 = ClassLoaderDemo.class.getClassLoader();
        System.out.println("cl1 > " + cl1);
        System.out.println("parent of cl1 > " + cl1.getParent());
        // Bootstrap ClassLoader由C++开发,是JVM虚拟机的一部分,本身不是JAVA类。
        System.out.println("grand parent of cl1 > " + cl1.getParent().getParent());
        // String, Int等基础类由Bootstrap ClassLoader加载。
        ClassLoader cl2 = String.class.getClassLoader();
        System.out.println("cl2 > " + cl2);
        System.out.println(ClassLoaderDemo.class.getClassLoader().loadClass("java.util.List").getClassLoader());
        // java指令可以通过增加-verbose:class -verbose:gc 参数在启动时打印出类加载情况
        // Bootstrap ClassLoader,加载java基础类。这个属性不能在java指令中指定,推断不是由java语言处理。
        System.out.println("Bootstrap ClassLoader: " + System.getProperty("sun.boot.class.path"));
        // Extension ClassLoader 加载JAVA_HOME/ext 下的jar包。可通过-D java.ext.dirs另行指定目录
        System.out.println("Extension ClassLoader: " + System.getProperty("java.ext.dirs"));
        // AppClassLoader 加载CLASSPATH,应用下的Jar包。可通过-D java.class.path另行指定目录
        System.out.println("AppClassLoader: " + System.getProperty("java.class.path"));
    }
}
cl1 > jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
parent of cl1 > jdk.internal.loader.ClassLoaders$PlatformClassLoader@76ed5528
grand parent of cl1 > null
cl2 > null
jdk.internal.loader.BuiltinClassLoader@6d06d69c
Bootstrap ClassLoader: /opt/jdk-16.0.1/lib
Extension ClassLoader: /opt/jdk-16.0.1/lib/ext:/usr/java/packages/lib/ext
AppClassLoader: /home/user/example:/opt/jdk-16.0.1/lib

JAVA 的类加载器:AppClassloader -> ExtClassloader -> Bootstrap ClassLoader

在 Java 中,类加载器的层次结构是由 JVM 在启动时创建的,每个类加载器都有一个父类加载器(除了 Bootstrap ClassLoader)。当一个类加载器需要加载某个类时,它会先委托给其父类加载器进行加载,如果父类加载器无法加载,则会尝试自己加载。这种委托机制被称为双亲委派模型

在双亲委派模型中,AppClassLoader 是 Java 应用程序的类加载器,它的父类加载器是 ExtClassLoader,ExtClassLoader 的父类加载器是 Bootstrap ClassLoader。因此,当 AppClassLoader 需要加载某个类时,它会先委托给 ExtClassLoader,如果 ExtClassLoader 无法加载,则会继续委托给 Bootstrap ClassLoader,直到找到为止。这样可以保证类的加载顺序和加载的安全性,避免了类的重复加载和恶意代码的加载

每种类加载器都有自己的加载目录。

  • AppClassLoader(或称为 System ClassLoader):负责加载应用程序的类,其加载路径通常包括 CLASSPATH 和用户自定义的类路径。
  • ExtClassLoader(或称为 Extension ClassLoader):负责加载 Java 扩展目录(JAVA_HOME/ext)下的类库。
  • Bootstrap ClassLoader:是虚拟机的内置类加载器,负责加载 Java 核心类库(例如 java.lang 包中的类),其加载路径是 JVM 的启动路径。

JAVA 中的类加载器的继承关系:

/**
 * Loads the class with the specified <a href="#binary-name">binary name</a>.  The
 * default implementation of this method searches for classes in the
 * following order:
 *
 * <ol>
 *
 *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
 *   has already been loaded.  </p></li>
 *
 *   <li><p> Invoke the {@link #loadClass(String) loadClass} method
 *   on the parent class loader.  If the parent is {@code null} the class
 *   loader built into the virtual machine is used, instead.  </p></li>
 *
 *   <li><p> Invoke the {@link #findClass(String)} method to find the
 *   class.  </p></li>
 *
 * </ol>
 *
 * <p> If the class was found using the above steps, and the
 * {@code resolve} flag is true, this method will then invoke the {@link
 * #resolveClass(Class)} method on the resulting {@code Class} object.
 *
 * <p> Subclasses of {@code ClassLoader} are encouraged to override {@link
 * #findClass(String)}, rather than this method.  </p>
 *
 * <p> Unless overridden, this method synchronizes on the result of
 * {@link #getClassLoadingLock getClassLoadingLock} method
 * during the entire class loading process.
 *
 * @param   name
 *          The <a href="#binary-name">binary name</a> of the class
 *
 * @param   resolve
 *          If {@code true} then resolve the class
 *
 * @return  The resulting {@code Class} object
 *
 * @throws  ClassNotFoundException
 *          If the class could not be found
 */
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
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
  • 每个类加载器对他加载过的类,都是有一个缓存的。
  • 双亲委派:向上委托查找,向下委托加载。
    • 作用:保护JAVA核心的类不回被应用程序覆盖。
  • 类加载过程:加载->连接->初始化
    • 加载:把Java的字节码数据加载到JVM内存当中,并映射成JVM认可的数据结构
    • 连接:分为三个小阶段。
      • 验证:检查加载到的字节信息是否符合JVM规范。
      • 准备:创建类或接口的静态变量,并赋初始值,属于半初始化状态。
      • 解析:把符号引用转为直接引用。
    • 初始化:

一个对象从加载到JVM,再到GC清除,都经历了什么?

  1. 用户创建一个对象,JVM首先需要到方法区区找对象的类型信息。然后再创建对象。
  2. JVM要实例化一个对象,首先要在堆当中先创建一个对象。-> 半初始化状态
  3. 对象首先会分配在堆内存中新生代的Eden。然后经过一次Minor GCGC,如果对象存活,就会进入S区。在后续的每次GC中,如果对象一直存活,就会在S区来回拷贝,每移动一次,年龄加1。->多大年龄才会移入老年代?年龄最大15,超过一定年龄后,对象转入老年代。
  4. 当方法执行结束后,栈中的指针会先移除掉。
  5. 堆中的对象,经过Full GC,就会被标记为垃圾,然后被GC线程清理掉。