JVM相关
对象头
在JVM中,对象在内存中的布局分为3块:对象头、实例数据和对齐填充。
- 实例数据: 程序代码中定义的各种类型的字段内容。
- 对齐填充: JVM要求对象的大小必须是8个字节的整数倍,对象头已经是8的整数倍了,如果实例数据没有8的整数倍就需要对齐填充来补全。
对象头 = Mark Word + 类型指针(Class pointer)。
类型指针(Klass pointer) : 用于标识JVM通过这个指针来确定这个对象是哪个类的实例。一般占8字节,开启指针压缩(一般默认开启)后占4字节
Mark Word : 用于储存对象自身的运行时数据,例如对象的hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程的ID、偏向时间戳等。在运行期间,Mark Word里存储的数据会随着内部锁标志位的变化而变化。一般占8字节
垃圾回收机制GC
详见超链接
内存分布
java中对象均是创建在堆区,只有引用和基本数据类型储存在栈,内存分布中比较重要的两个点即堆区和方法区。
堆区即我们对象创建使用的区域,具体又分为老年代和新生代,详细见分代收集算法
方法区是一个抽象的名称,是java虚拟机的一个规范概念,jdk8以前具体为永久代,jdk8及以后为元空间。存储 类信息、方法信息、常量池信息等静态数据
永久代
永久代放在堆中并且独立与堆,用于存储 类信息、方法信息、常量池信息等静态数据。
元空间
元空间完全剥离虚拟机,存在于直接内存之中,因此只受本机可用内存的限制,它的作用依旧没变,
类加载器
概念
java类加载器负责将类的字节码加载到内存中,并将其转换为可执行的Java对象。类加载器在Java应用程序中起着重要的作用,它实现了动态加载类的机制,使得Java具备了灵活性和可扩展性。
类加载器是Java虚拟机用于加载类文件的一种机制。在Java中,每个类都由类加载器加载,并在运行时被创建为一个Class对象。类加载器负责从文件系统、网络或其他来源中加载类的字节码,并将其转换为可执行的Java对象。类加载器还负责解析类的依赖关系,即加载所需的其他类。
类的生命周期可以分为:加载---连接---初始化---使用---卸载
Java虚拟机定义了三个主要的类加载器(所有类加载器都继承自ClassLoader这个抽象类 除了启动类加载器):
- 启动类加载器(Bootstrap Class Loader):也称为根类加载器,它负责加载Java虚拟机的核心类库,如java.lang.Object等。启动类加载器是虚拟机实现的一部分,它通常是由本地代码实现的,不是Java类。
- 扩展类加载器(Extension Class Loader):它是用来加载Java扩展类库的类加载器。扩展类库包括javax和java.util等包,它们位于jre/lib/ext目录下。
- 应用程序类加载器(Application Class Loader):也称为系统类加载器,它负责加载应用程序的类。它会搜索应用程序的类路径(包括用户定义的类路径和系统类路径),并加载类文件。
除了这三个主要的类加载器,Java还支持自定义类加载器,开发人员可以根据需要实现自己的类加载器。自定义类加载器必须继承java.lang.ClassLoader
类,并重写findClass
方法。在findClass
方法中,开发人员可以根据自己的规则和逻辑来加载类的字节码。
加载
编写一个新的Java类时,JVM会帮我们编译成class对象,存放在同名的.class
文件中。在运行时,当需要生成这个类的对象时,JVM会帮我们检查该类是否已经加载到内存中,若是没有加载,则把.class文件加载到内存中,若是已经加载,则根据class文件生成实例对象。
加载阶段指定是将类的.class
文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class
对象(JVM规范并未说明Class对象位于哪里,Hotspot虚拟机将其放在方法区中),用来封装类在方法区内的数据结构,类的加载的最终产品是位于堆区中Class
对象。Class对象封装了类在方法区的数据结构,并且向Java程序提供了访问了方法区内的数据结构的接口。
需注意:
- 类加载器并不需要等到某个类被
首次主动使用
时再加载它。 - JVM规范允许类加载在预料到某个类将要被使用时,就预先加载它。
- 如果在预先加载的过程遇到
.class
文件缺失或存在错误,类加载器必须在程序首次主动使用
该类时才报告错误(linkagerror),如果该类一直没有被程序主动使用
,那么类加载就不会报告错误。
当我们使用Class.forName("xxx")主动去加载一个类时,该方法会使用调用该方法的类的类加载器去加载参数类。
类初始化
Java程序对类的使用方式分为两种,主动使用
和被动使用
。一般来说,只有当对类的首次主动使用
才会导致类的初始化。所以主动使用
又叫做类加载过程中初始化
开始的时间。
主动使用包括
- 创建类的实例对象,即直接new一个对象
- 调用类的静态方法。
- 访问类的静态成员(被final和staic修饰的变量除外)。
- 反射(例如Class.forName()和对象.class)。
- 初始化某个类的子类,则其父类也会被初始化。
- jvm启动时被标明为启动类的类(JavaTest),还有就是Main方法的类会首先被初始化。
卸载
当用户程序代码执行完毕后,JVM便开始销毁创建的Class对象,最后负责运行的JVM也退出内存。
双亲委派模型
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜素范围中没有找到所需要的类时,即无法完成该加载,子加载器才会尝试去加载该类。
例如:
1.当AppClassLoader去加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委托给父加载器ExtClassLoader去完成。
2.当ExtClassLoader去加载一个class时,它首先也不会去尝试加载这个类,而是把类加载请求委托给父加载器BootstrapClassLoader去完成。
3.如果BootstrapClass加载失败(例如在JAVA_HOME/jre/lib里未找到该class),就会使用ExtClassLoader来尝试加载。
4.如果使用ExtClassLoader加载失败(例如在JAVA_HOME/jre/lib/ext里未找到该class),就会使用AppClassLoader来尝试加载。如果AppClassLoader也加载失败,则会抛出 ClassNotFoundException异常。
如果我们有一个类想要通过自定义的类加载器来加载这个类,而不是通过系统默认的类加载器,说白了就是不走双亲委派那一套。即:
- 自定义类加载器 ,重写loadclass方法。典型的打破双亲委派模型的框架和中间件有tomcat与osgi
- SPI机制绕开loadclass 方法。当前线程设定关联类加载器
需要打破双亲委派机制的场景:
- 自定义的类加载器,用于从网络或数据库中加载类定义,而不是从标准的类路径中加载。
- 同一个应用程序中创建多个类加载器,并在它们之间实现类加载隔离。当涉及到类加载隔离时,通常是指在同一个Java应用程序中创建多个类加载器,以将不同的类加载到各自的加载器命名空间中,从而实现类的隔离。例如TomCat
参考文章:https://zhuanlan.zhihu.com/p/347881762
https://blog.csdn.net/qq_39093474/article/details/122808595