JVM部分
一、JVM是由哪几部分组成的
- 类加载器
- 运行时数据区
- 执行引擎
- 本地库接口
说一下运行时数据区的组成:
本地方法栈、虚拟机栈、堆区、程序计数器、方法区。
虚拟机栈帧的组成:
每个栈帧包含五部分,分别包括局部变量表、操作数栈、动态链表(指向运行时常量池的方法引用)、方法返回地址和一些附加信息。
详细参考:JVM——运行时数据区域_jvm运行时数据区-CSDN博客
什么是元空间(方法区的实现):
元空间是一块本地内存,它用于存放类的元数据,例如类名、访问修饰符、字段、方法、注解等信息。这些信息在程序运行期间保持不变,存储在元空间中可以提高程序的运行效率。元空间由Java虚拟机主动管理,可以将其看作是一块特殊的堆内存。
详细参考: JVM--方法区&元空间_元空间和方法区的区别-CSDN博客
二、堆和栈的区别
参考文章: 一文读懂堆与栈的区别_堆栈-CSDN博客
三、对象是如何创建的,创建对象如何解决并发问题
参考文章:关于JVM对象创建与内存分配机制_java jvm生成大对象 会怎么样-CSDN博客
四、如何定位到内存中的对象,对象在内存中是怎么存在的
定位内存的对象
java 程序需要通过栈上的 reference 数据来操作堆上的具体对象。由于reference 类型在JVM 规范中只规定了一人指向对象的引用,并未定义这个引用如何定位和访问具体位置,所以对象访问方式由具体虚拟机实现而定。目前主流的访问方式有句柄和直接指针两种。
- 直接指针,java堆对象的布局中就必须考虑如何放置访问方法区中类型数据的相关信息。
- 句柄访问,java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例和类型数据各自的具体地址信息。
二者各有优势,使用句柄访问这样做的好处是中 reference 存储的句柄地址较为稳定,因为在Java 堆中进行了垃圾回收,对象的地址发生了改变的时候,只需要修改句柄的对象实例数据指针就行。而使用直接指针的最大好处就是速度更快。
对象的内存布局:
对象在堆内存的内存布局主要有三部分,即对象头、实例数据以及对其填充。
对象头
对象头主要包含两部分的内容,一个叫做运行时元数据(MarkWord),一个叫做类型指针 (CIass MetadataAddress)。
类型指针指向元数据区代表当前类的 class 对象,确定该对象所属的类型,而运行时元数据又包含了:
- 哈希值(hashcode)也就是对象在堆空间中都有一个首地址值,栈空间的引用根据这个地址指向堆中的对象这就是哈希值起的作用;
- GC 分代年龄: 对象首先是在Eden中创建的,在经过多次GC后,如果没有被进行回收,就会在 survivor 中来回移动,其对应的年龄计数器会发生变化,达到闽值后会进入养老区,
- 锁状态标志: 在同步中判断该对象是否是锁:
- 线程持有的锁;
- 线程偏向ID;
- 偏向时间戳。
实例数据
它是对象真正存储的有效信息,包括程序代码中定义的各种字段类型,当然也包含从父类继承下来的字段。注意这里有一些规则: 相同宽度的字段总是被分配在一起,父类中定义的变量会出现在子类之前,因为父类的加载是优先于子类加载的。
对齐填充:没有特殊含义,仅仅起到占位符的作用。
五、内存溢出与内存泄漏问题
六、什么是垃圾回收系统?哪些内存区域需要回收?
在Java中,垃圾回收(Garbage Collection,简称GC),是自动管理内存的机制。它负责检测不再使用的对象,并释放它们所占用的内存,以供其他对象使用。
参考文章:关于JVM垃圾回收机制_java垃圾回收可回收区域-CSDN博客
七、如何判断对象是否可以回收,GC Roots对象有哪些,对象的回收过程?
参考文章:
Java中垃圾回收机制中如何判断对象需要回收?常见的 GC 回收算法有哪些?_java 中垃圾回收机制中如何判断对象需要回收?常见的 gc 回收算法有哪些?-CSDN博客
八、方法区能否被回收
方法区中回收的对象包括两部分,不被引用的常量以及不被使用的类型。
已经没有任何字符串对象引用常量池中的“java”常量,且虚拟机中也没有其他地方引用这个字面量。如果在这时发生内存回收,而且垃圾收集器判断确有必要的话,这个“java”常量就将会被系统清理出常量池。
判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:
·该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
·加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的。
·该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
九、四种引用类型
参考文章:
(一)四种引用类型_引用类型有哪四种-CSDN博客
十、请问了解Minor GC和Full GC么,这两种GC有什么不⼀样吗
- Minor GC又称为新生代GC : 指的是发生在新生代的垃圾收集。因为Java对象大多都具备朝生夕灭的特性,因此Minor GC(采用复制算法)非常频繁,一般回收速度也比较快。
- Full GC又称为老年代GC或者Major GC : 指发生在老年代的垃圾收集。出现了Major GC,经常会伴随至少⼀次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC的策略选择过程)。Major GC的速度⼀般会比Minor GC慢10倍以上。
十一、有哪些垃圾回收算法?新生代为划分空间的比例
垃圾回收算法:标记-清除、标记-复制、标记-整理,分代算法。
新生代划分空间比例:
一个 Eden区,两个survivor区,eden区占80%内存空间,每一块survivor区占 10%。平时使用一块eden和一块survivor区所以内存使用率为 90%。
参考文章:新生代的垃圾回收算法_新生代垃圾回收算法-CSDN博客
十二、常见的垃圾回收器,CMS是如何工作的,G1收集器是如何工作的
CMS
CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一 些,整个过程分为4个步骤:• 初始标记(CMS initial mark)初始标记仅仅只是标记⼀下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。• 并发标记(CMS concurrent mark)并发标记阶段就是进行GC Roots Tracing的过程。• 重新标记(CMS remark)重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的 标记记录,这个阶段的停顿时间⼀般会比初始标记阶段稍长⼀些,但远比并发标记的时间短,仍然 需要“Stop The World”。• 并发清除(CMS concurrent sweep)并发清除阶段会清除对象。
详细参考:史上最经典垃圾回收器(CMS,G1)详解、适用场景及特点、使用命令_cms垃圾回收器-CSDN博客
十三、触发GC的条件
一、Minor GC触发条件:
当Eden区满时,触发Minor GC。
二、Full GC触发条件:
- 调用System.gc()时,系统建议执行Full GC,但是不必然执行
- 老年代空间不足
- 方法区空间不足
- 年轻代对象总大小大于老年代可用空间大小,或历史Minor GC后进入老年代的对象平均大小大于老年代可用空间大小(与空间分配担保机制有关)
- 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代。
参考文章: jvm中触发GC的条件_jvm gc触发条件-CSDN博客
十四、什么是三色标记?什么是多标和漏标? 如何解决漏标问题?
详细文章:JVM - 你们垃圾回收器用的什么? G1有哪些特点?G1如何实现可预测的停顿时间?漏标问题如何解决的?介绍下三色标记?说说STAB 算法 ?_stab算法-CSDN博客
十五、什么是类加载机制,讲讲类加载过程
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转化解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
与编译时需要进行连接的语言不同,在Java语言里,类的加载、连接和初始化过程都是程序运行期间完成的,Java天生可以动态扩展的语言特性就是依赖运行期间动态加载和动态连接这个特点来实现的。例如,编写一个面向接口的应用程序,可以等到运行时再指定其 实际的实现类,用户可以通过Java预置的或自定义类加载器,让某个本地的应用程序在运行时从网络 或其他地方上加载一个二进制流作为其程序代码的一部分。
类加载过程
其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。 在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
一、加载:就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
二、连接:包括以下三个步骤
- 验证:是否有正确的内部结构,并和其他类协调一致
- 准备:负责为类的静态成员分配内存 (所使用的内存都将在方法区中进行分配),并设置默认初始化值 (注意这里并没有指定具体的值,只是默认值,比如int是0,boolean是false。指定具体的值那是在类的初始化。但是,如果是static final静态常量,则直接指定具体的值!)
- 解析:将常量池内的符号引用替换为直接引用
三、初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
①声明类变量并指定初始值
②使用静态代码块为类变量指定初始值
详细文章:10. 类加载机制-CSDN博客
值得你收藏的类加载、初始化、实例化、加载时机,初始化时机..._全局变量直接new会在类加载时实例化吗-CSDN博客
十六、 类加载时机、类初始化顺序
类加载时机
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
所以什么情况下虚拟机需要开始加载一个类呢?虚拟机规范中并没有对此进行强制约束,这点可以交给虚拟机的具体实现来自由把握。
类初始化顺序
参考文章:Java类加载机制、初始化顺序_java的jar包中的类和应用中的类初始化顺序-CSDN博客
十七、什么是类加载器,有哪些类加载器
类加载器:类加载器(Class Loader)是Java虚拟机(JVM)的一部分,负责将Java类的二进制代码加载到内存中,并转换为可执行的Java字节码。它是Java语言的重要特性之一,为Java应用程序提供了动态加载和运行时扩展的能力。
类加载器主要有以下几种类型:
- 启动类加载器:加载 JDK 中 lib 目录中 Java 的核心类库,即$JAVA_HOME/lib目录。
- 扩展类加载器。加载 lib/ext 目录下的类。
- 应用程序类加载器:加载我们写的应用程序。
- 自定义类加载器:根据自己的需求定制类加载器。
详细参考文章: 什么是类加载器,类加载器有哪些-CSDN博客
十八、什么是双亲委派模型, 如何破坏双亲委派模型
什么是双亲委派模型?
当一个类需要加载时,类加载器会根据一定的加载规则进行类加载工作。一般情况下,类加载器会先委托父加载器来尝试加载类,只有当父加载器无法加载时,才由子加载器尝试加载。这种机制被称为"双亲委派模型"。这种模型保证了类的加载是从上到下的,避免了重复加载和类的版本冲突问题。
如何破坏双亲委派模型:
双亲委派机制是Java虚拟机(JVM)的一个特性,它确保了类的唯一性和安全性。这个机制的核心思想是,当一个类加载器要加载一个类时,它首先会委托给它的父类加载器去尝试加载这个类,只有当父类加载器无法完成这个任务时,它才会尝试自己去加载这个类。
破坏双亲委派机制通常是不被推荐的行为,因为它可能会导致类的唯一性无法保证,从而引发一些安全问题。然而,出于学习或特定场景的需要,了解如何绕过这个机制也是有价值的。以下是一些可能破坏双亲委派机制的方法:
- 自定义类加载器: 通过继承ClassLoader类并重写loadClass方法,可以创建一个自定义的类加载器。在这个方法中,可以忽略双亲委派机制,直接从文件系统或者网络加载类。
- 使用线程上下文类加载器: 在某些情况下,可以通过设置线程的上下文类加载器来改变类加载的行为。通过设置不同的类加载器,可以加载同名但不同的类。
- 使用URLClassLoader: URLClassLoader允许从指定的URL加载类,它不遵循双亲委派模型,而是直接加载类。
- 使用系统类加载器加载: 通过使用系统类加载器(ClassLoader.getSystemClassLoader())加载类,可以绕过双亲委派机制。
- 代理类加载器: 创建一个代理类加载器,它在加载类时,可以决定是否调用父类加载器。
- 使用反射: 在某些情况下,可以使用反射API来加载类,但这通常不会破坏双亲委派机制。
- 修改JVM实现: 如果你有JVM的源代码,可以修改JVM的实现,从而改变类加载的行为。