1、简述java类加载机制?
- 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的 java 类型。
2、类加载的机制及过程 阿里
程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、链接、初始化3个步骤来对该类进行初始化。JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。
- JVM 类加载机制分为五个部分: 加载, 验证, 准备, 解析, 初始化。如下图所示
- 1、加载
- 加载指的是将类的 class 文件读入到内存,并将这些静态数据转换成方法区中的运行时数据结构,并在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区类数据的访问入口,这个过程需要类加载器参与。
- Java类加载器由JVM提供,是所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承 ClassLoader 基类来创建自己的类加载器。
- 类加载器,可以从不同来源加载类的二进制数据,比如:本地Class文件、Jar包Class文件、网络 Class 文件等等等。
- 类加载的最终产物就是位于堆中的 Class 对象(注意不是目标类对象),该对象封装了类在方法区中的数据结构,并且向用户提供了访问方法区数据结构的接口,即 Java 反射的接口
- 2、链接
- 当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中(意思就是将java类的二进制代码合并到JVM的运行状态之中)。类连接又可分为如下3个阶段。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。主要验证是否符合 Class文件格式规范,并且是否能被当前的虚拟机加载处理;
- 准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配;
- 解析:虚拟机常量池的符号引用替换为字节引用过程。
- 3、初始化(初始化是为类的静态变量赋予正确的初始值)
- 初始化阶段是执行类构造器
< clinit>()
方法的过程。类构造器< clinit>()
方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块( static块 )中的语句合并产生,代码从上往下执行; - 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化;
- 虚拟机会保证一个
< clinit>()
方法在多线程环境中被正确加锁和同步 - 类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化
- 类的初始化何时会被触发呢?
- 1、当虚拟机启动时,初始化用户指定的主类;
- 2、当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类;
- 3、当遇到调用静态方法的指令时,初始化该静态方法所在的类;
- 4、当遇到访问静态字段的指令时,初始化该静态字段所在的类;
- 5、子类的初始化会触发父类的初始化;
- 6、如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
- 7、使用反射 API 对某个类进行反射调用时,初始化这个类;
- 8、当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类
Demo,饿汉式单例,只有当调用 Singleton.getInstance 时,程序才会访问 LazyHolder.INSTANCE,才会触发对LazyHolder 的初始化(对应第四种情况)
public class Singleton { private Singleton() {} private static class LazyHolder { static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return LazyHolder.INSTANCE; } }
3、描述一下JVM加载Class文件的原理机制
Java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显式的加载所需要的类。
类装载方式,有两种 :
- 1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中,
- 2.显式装载, 通过class.forName()等方法,显式加载需要的类
Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到 jvm 中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销。
4、什么是 tomcat 类加载机制?
当 tomcat启动时,会创建几种类加载器: Bootstrap 引导类加载器 加载 JVM启动所需的类,以及标准扩展类(位于 jre/lib/ext 下) System 系统类加载器 加载 tomcat 启动的类,比如
bootstrap.jar,通常在 catalina.bat 或者 catalina.sh 中指定。位于 CATALINA_HOME/bin 下
Common 通用类加载器
5、什么是类加载器,类加载器有哪些?
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
- 启动类加载器(Bootstrap ClassLoader) 用来加载java核心类库,无法被 java 程序直接引用。
- 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader. getSystemClassLoader()
来获取它。 - 用户自定义类加载器,通过继承
java.lang.ClassLoader
类的方式实现。
6、 自己写过 String 类能加载吗, 之前的 String 是什么时候加载进去的? 阿里
- 不能加载, 因为双亲委派机制, JVM 出于安全性的考虑, 全限定类名相同的 String 是不能被加载的。
- java.lang.String 会被顶级类加载器 Bootstrap Classloader 加载。 当 class 文件被加载到内存中时, 类文件常量池中的其他常量会加载到运行时常量池, 但是字符串常量不会。 它会首先在堆区中创建一个字符串对象, 然后再把这个对象的引用保存到全局字符串常量池中。
7、什么是双亲委派模型?
- 如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。