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