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程序提供了访问了方法区内的数据结构的接口。

需注意:

  1. 类加载器并不需要等到某个类被首次主动使用时再加载它。
  2. JVM规范允许类加载在预料到某个类将要被使用时,就预先加载它。
  3. 如果在预先加载的过程遇到.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异常。

 如果我们有一个类想要通过自定义的类加载器来加载这个类,而不是通过系统默认的类加载器,说白了就是不走双亲委派那一套。即:

  1. 自定义类加载器 ,重写loadclass方法。典型的打破双亲委派模型的框架和中间件有tomcat与osgi
  2. SPI机制绕开loadclass 方法。当前线程设定关联类加载器

 需要打破双亲委派机制的场景:

  • 自定义的类加载器,用于从网络或数据库中加载类定义,而不是从标准的类路径中加载。
  • 同一个应用程序中创建多个类加载器,并在它们之间实现类加载隔离。当涉及到类加载隔离时,通常是指在同一个Java应用程序中创建多个类加载器,以将不同的类加载到各自的加载器命名空间中,从而实现类的隔离。例如TomCat

 

 

参考文章:https://zhuanlan.zhihu.com/p/347881762

     https://blog.csdn.net/qq_39093474/article/details/122808595

 

posted @ 2023-08-14 19:16  _Explosion!  阅读(18)  评论(0编辑  收藏  举报