目录标题
- 1.类加载机制
- 1.1 Java 运行时一个类是什么时候被加载的?
- 1.2 JVM 一个类的加载过程?
- 1.3 一个类被初始化的过程?
- 1.4 继承时父子类的初始化顺序是怎样的?
- 1.5 究竟什么是类加载器?
- 1.6 JVM 有哪些类加载器?
- 1.7 JVM 中不同的类加载器加载哪些文件?
- 1.8 JVM 三层类加载器之间的关系是继承吗?
- 1.9 你了解 JVM 类加载的双亲委派模型吗?
- 1.10 JDK 为什么要设计双亲委派模型,有什么好处?
- 1.11 可以打破JVM 双亲委派模型吗?如何打破JVM 双亲委派模型?
- 1.12 如何自定义自己的类加载器?
- 1.13 ClassLoader 中的 loadClass()、findClass()、defineClass()区别?
- 1.14 Tomcat 的类加载机制吗?
- 1.15 为什么 Tomcat 要破坏双亲委派模型?
- 1.16 有没有听说过热加载和热部署,如何自己实现一个热加载?
- 1.17 加载一个类采用 Class.forName()和 ClassLoader 有什么区别?
- 2.深入剖析 JVM 内存管理
- 1.画一下 JVM 整个运行原理图?
- 2. 请介绍一下 JVM 的内存结构划分?
- 3.JVM 哪些区域是线程私有的,哪些区域是线程共享的?
- 4.JVM 运行时数据区 程序计数器 的特点及作用?
- 5.JVM 运行时数据区 虚拟机栈的特点及作用?
- 6.JVM 运行时数据区 本地方法栈的特点及作用?
- 7 JVM 运行时数据区 Java 堆的特点及作用?
- 8.JVM 中对象如何在堆内存分配?
- 9.JVM 堆内存中的对象布局?
- 10.JVM 什么情况下会发生堆内存溢出?
- 11.JVM 如何判断对象可以被回收?
- 12.谈谈 Java 中不同的引用类型?
- 13.JVM 堆内存分代模型?
- 14.请介绍一下 JVM 堆中新生代的垃圾回收过程?
- 15.JVM 对象动态年龄判断是怎么回事?
- 16.什么是老年代空间分配担保机制
- 16.什么情况下对象会进入老年代?
- 17.JVM 运行时数据区元空间的特点及作用?
- 18.JVM 本机直接内存的特点及作用?
- 19.JVM 本机直接内存溢出问题?
- 20.说几个与 JVM 内存相关的核心参数?
- 3.垃圾回收器
- 6.JVM 故障诊断性能调优实战
1.类加载机制
1.1 Java 运行时一个类是什么时候被加载的?
一个类在什么时候开始被加载,《Java 虚拟机规范》中并没有进行强制约束,交给了虚拟机自己去自由实现,HotSpot 虚拟机是按需加载,在需要用到该类的时候加载这个类;
1、Sun 公司最早的 Classic 虚拟机;
2、Sun/Oracle 公司的 HotSpot 虚拟机; 3、BEA 公司的 JRockit 虚拟机;
4、IBM 公司的 IBM J9 虚拟机;
官方:https://docs.oracle.com/javase/8/
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
-XX:+TraceClassLoading
Java虚拟机(JVM)在以下几个关键时刻会加载一个类:
- 使用类的静态成员:当首次访问类的静态变量或调用静态方法时。
- 实例化类:当使用new关键字创建类的实例时。
- 反射:当使用反射机制,如调用Class.forName或ClassLoader.loadClass时。
- 初始化子类:在初始化子类时,如果父类还没有被加载,会首先加载父类。
- 启动类:JVM启动时,会加载启动类,即包含main方法的类。
1.2 JVM 一个类的加载过程?
一个类从加载到 jvm 内存,到从 jvm 内存卸载,它的整个生命周期会经历 7 个阶段:
1、加载(Loading)
2、验证(Verification) 3、准备(Preparation) 4、解析(Resolution)
5、初始化(Initialization) 6、使用(Using)
7、卸载(Unloading)
其中验证、准备、解析三个阶段统称为连接(Linking);
加载:classpath、jar 包、网络、某个磁盘位置下的类的 class 二进制字节流读进来,在内存中生成一个代表这个类的 java.lang.Class 对象放入元空间,此阶段我们程序员可以干预,我们可以自定义类加载器来实现类的加载;
验证:验证 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证虚拟机的安全;
准备:类变量赋默认初始值,int 为 0,long 为 0L,boolean 为 false,引用类型为 null;常量赋正式值;
解析:把符号引用翻译为直接引用;
初始化:当我们 new 一个类的对象,访问一个类的静态属性,修改一个类的静态属性,调用一个类的静态方法,用反射 API 对一个类进行调用,初始化当前类,其父类也会被初始化 那么这些都会触发类的初始化;
使用:使用这个类;
卸载:
- 该类所有的实例都已经被 GC,也就是 JVM 中不存在该 Class 的任何实例;
- 加载该类的 ClassLoader 已经被 GC;
- 该类的 java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法;
1.3 一个类被初始化的过程?
类的初始化阶段,Java 虚拟机才真正开始执行类中编写的 Java 程序代码;
进行准备阶段时,变量已经赋过一次系统要求的初始零值,而在初始化阶段,才真正初始化类变量和其他资源;
1.4 继承时父子类的初始化顺序是怎样的?
父类–静态变量
父类–静态初始化块子类–静态变量
子类–静态初始化块父类–变量
父类–初始化块父类–构造器 子类–变量
子类–初始化块子类–构造器
(静态代码块或静态变量其实按照代码顺序加载)
1.5 究竟什么是类加载器?
在类"加载"阶段,通过一个类的全限定名来获取描述该类的二进制字节流的这个动作的“代码”被称为“类加载器(Class Loader)”,这个动作是可以自定义实现的;
1.6 JVM 有哪些类加载器?
站在 Java 虚拟机的角度来看,只存在两种不同的类加载器:
1、启动类加载器(Bootstrap ClassLoader),使用 C++语言实现,是虚拟机自身的一部分;
2、其他所有的类加载器,由 Java 语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader;站在 Java 开发者的角度来看,自 JDK 1.2 开始,Java 一直保持着三层类加载器架构;
1.7 JVM 中不同的类加载器加载哪些文件?
1、启动类加载器(Bootstrap ClassLoader):(根的类加载器)C++语言实现的
<JAVA_HOME>\jre\lib\rt.jar,resources.jar、charsets.jar被-Xbootclasspath 参数所指定的路径中存放的类库;
2、扩展类加载器(Extension ClassLoader):
sun.misc.Launcher$ExtClassLoader,<JAVA_HOME>\jre\lib\ext,
被 java.ext.dirs 系统变量所指定的路径中所有的类库;
3、应用程序类加载器(Application ClassLoader):系统的类加载器
sun.misc.Launcher$AppClassLoader
加载用户类路径(ClassPath)上所有的类库;
1.8 JVM 三层类加载器之间的关系是继承吗?
JVM的三层类加载器之间的关系不是继承关系,而是通过委托模型实现的。这种设计确保了类加载的顺序和安全性,避免类重复加载和冲突。通过委托模型,类加载请求从下到上逐层委托,直到找到合适的类加载器来加载指定的类。
1.9 你了解 JVM 类加载的双亲委派模型吗?
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当上一层类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到这个类)时,下一层类加载器才会尝试自己去加载;
1.10 JDK 为什么要设计双亲委派模型,有什么好处?
1. 确保安全,避免 Java 核心类库被修改;
2、避免重复加载;
3、保证类的唯一性;
如果你写一个 java.lang.String 的类去运行,发现会抛出如下异常;
1.11 可以打破JVM 双亲委派模型吗?如何打破JVM 双亲委派模型?
可以;
想要打破这种模型,那么就自定义一个类加载器,重写其中的 loadClass 方法,使其不进行双亲委派即可;
1.12 如何自定义自己的类加载器?
1、继承 ClassLoader
2、覆盖 findClass(String name)方法 或者 loadClass() 方法; findClass(String name)方法 不会打破双亲委派;
loadClass() 方法 可以打破双亲委派;
1.13 ClassLoader 中的 loadClass()、findClass()、defineClass()区别?
loadClass() 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中;
findClass() 根据名称或位置加载.class 字节码;
definclass() 把字节码转化为 java.lang.Class;
1、当我们想要自定义一个类加载器的时候,并且想破坏双亲委派模型时,我们会重写 loadClass()方法;
2、如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢?可以可以重写 findClass 方法(),findClass()方法是 JDK1.2 之后的 ClassLoader 新添加的一个方法,这个方法只抛出了一个异常,没有默认实现;
JDK1.2 之后已不再提倡用户直接覆盖 loadClass()方法,而是建议把自己的类加载逻辑实现到 findClass()方法中;
所以, 如果你想定义一个自己的类加载器, 并且要遵守双亲委派模型, 那么可以继承 ClassLoader,并且在 findClass()中实现你自己的加载逻辑即可;
1.14 Tomcat 的类加载机制吗?
可以看到,在原来的 Java 的类加载机制基础上,Tomcat 新增了 3 个基础类加载器和每个 Web 应用的类加载器+JSP 类加载器;
3 个基础类加载器在 conf/catalina.properties 中进行配置:
common.loader=“ c a t a l i n a . b a s e / l i b " , " {catalina.base}/lib"," catalina.base/lib","{catalina.base}/lib/.jar"," c a t a l i n a . h o m e / l i b " , " {catalina.hom e}/lib"," catalina.home/lib","{catalina.home}/lib/.jar”
server.loader= shared.loader=
Tomcat 自定义了 WebAppClassLoader 类加载器,打破了双亲委派的机制,即如果收到类加载的请求,首先会尝试自己去加载,如果找不到再交给父加载器去加载,目的就是为了优先加载 Web 应用自己定义的类,我们知道 ClassLoader 默认的 loadClass 方法是以双亲委派的模型进行加载类的,那么 Tomcat 打破了这个规则,重写了 loadClass 方法,我们可以看到
WebAppClassLoader 类中重写了 loadClass 方法;
1.15 为什么 Tomcat 要破坏双亲委派模型?
Tomcat 是 web 容器,那么一个 web 容器可能需要部署多个应用程序;
1、部署在同一个 Tomcat 上的两个 Web 应用所使用的 Java 类库要相互隔离;
2、部署在同一个 Tomcat 上的两个 Web 应用所使用的 Java 类库要互相共享;
3、保证 Tomcat 服务器自身的安全不受部署的 Web 应用程序影响;
4、需要支持 JSP 页面的热部署和热加载;
1.16 有没有听说过热加载和热部署,如何自己实现一个热加载?
热加载 是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境;
热部署 是指可以在不重启服务的情况下重新部署整个项目,比如 Tomcat 热部署就是在程序运行时,如果我们修改了 War 包中的内容,那么 Tomcat 就会删除之前的 War 包解压的文件夹,重新解压新的 War 包生成新的文件夹;
1、热加载是在运行时重新加载 class,后台会启动一个线程不断检测你的 class 是否发生改变;
2、热部署是在运行时重新部署整个项目ÿ