Java基础面试题

JVM 的执行流程?

Java虚拟机(JVM)的执行流程通常包括以下步骤:

  1. 加载(Loading):在加载阶段,JVM负责将Java类的字节码加载到内存中。这个过程通常包括类加载、链接和初始化三个步骤。
    • 类加载(Class Loading):加载字节码文件,可以来自本地文件系统、远程服务器或者其他来源。类加载器负责查找并加载这些类。
    • 链接(Linking):链接分为三个步骤:验证、准备和解析。验证确保字节码符合规范,准备为静态字段分配内存并初始化默认值,解析将符号引用解析为直接引用。
    • 初始化(Initialization):执行类构造器(也称为类初始化器)<clinit>() 的内容,包括静态字段的赋值。
  1. 验证(Verification):在验证阶段,JVM会对加载的类进行各种验证,以确保字节码是合法且安全的。这包括验证字节码的格式、类的继承关系、访问权限等。
  2. 准备(Preparation):在准备阶段,JVM为类的静态字段分配内存,并初始化默认值。这些字段在类初始化时会被赋予正确的值。
  3. 解析(Resolution):在解析阶段,符号引用将被解析为直接引用,以便在运行时能够正确访问其他类、字段和方法。
  4. 初始化(Initialization):在初始化阶段,类构造器<clinit>()会被执行。这个阶段会执行静态字段的初始化和静态块的代码。
  5. 执行(Execution):在执行阶段,JVM开始执行程序的主要逻辑,包括调用类的方法、访问字段等。程序会从main方法开始执行。
  6. 卸载(Unloading):当一个类不再被引用,JVM会卸载这个类,并释放相关的内存资源。

这个流程是通常的执行顺序,但需要注意的是,JVM可能会对这些步骤进行优化或重排序以提高性能。此外,垃圾收集也是JVM的一部分,用于回收不再使用的内存,以确保程序的稳定性和性能。

JVM 是怎么找到一个类的?

JVM在找到一个类时会经过以下步骤:

  1. 加载类文件:首先,JVM需要找到并加载类的字节码文件。这个文件可以来自本地文件系统、远程服务器或其他来源。
  2. 类加载器:JVM通过类加载器(Class Loader)来加载类文件。类加载器负责查找并加载类,它可以是内建的类加载器(如引导类加载器)、扩展类加载器、应用程序类加载器,或者自定义的类加载器。
  3. 类加载顺序:JVM遵循双亲委派模型(Parent-Delegation Model)来查找和加载类。这意味着当需要加载一个类时,首先询问父类加载器是否已加载了这个类,如果没有则交给当前类加载器加载。这一层层的查询最终达到引导类加载器,如果引导类加载器仍然无法找到类,将抛出ClassNotFoundException异常。
  4. 类路径:JVM会在类加载器的类路径上查找字节码文件。类路径是一组目录和JAR文件,JVM会依次检查这些位置以查找所需的类。
  5. 字节码文件验证:在加载类文件之后,JVM会进行验证以确保字节码文件符合规范,是合法的,并且不包含危险代码。
  6. 链接处理:链接阶段包括验证、准备和解析。在验证步骤中,字节码文件会被验证以确保它是合法的。在准备步骤中,静态字段会被分配内存并初始化为默认值。在解析步骤中,符号引用会被解析为直接引用。
  7. 类初始化:在类加载的初始化阶段,类构造器 <clinit>() 会被执行,静态字段会被初始化。

总之,JVM通过类加载器和类路径查找类文件,然后进行验证、链接和初始化,最终将类加载到内存中供程序使用。这个过程确保了类的正确加载和执行。

Java 虚拟机是怎么进行垃圾回收的?

Java虚拟机(JVM)进行垃圾回收(Garbage Collection)的过程是为了回收不再使用的内存,以便程序能够继续运行。JVM使用垃圾收集器来管理内存,垃圾收集器负责查找和回收不再被程序引用的对象。下面是JVM进行垃圾回收的一般过程:

  1. 标记(Marking):垃圾回收从根对象开始,递归地遍历对象图,标记所有被根对象引用到的对象。这些被标记的对象被认为是存活的对象。
  2. 清除(Sweeping):在清除阶段,垃圾收集器会扫描堆内存,识别并清除那些未被标记为存活的对象。清除的对象的内存被释放,可以用于后续的对象分配。
  3. 压缩(Compacting,可选):一些垃圾回收器会执行内存压缩,将存活对象紧凑排列,以便减少内存碎片。这个步骤通常不是所有垃圾回收器都执行的,但对于堆内存碎片较多的情况下非常有用。
  4. 终结处理(Finalization,可选):在清除对象之前,一些垃圾回收器会调用对象的终结方法(finalize()),允许对象执行一些清理工作。但是,终结方法的使用在现代的JVM中已经不鼓励,因为它们会影响性能。
  5. 内存分配(Allocation):垃圾回收之后,可以将内存重新分配给新的对象,程序可以继续运行。

垃圾回收的具体实现取决于使用的垃圾收集器。Java的垃圾收集器有多种不同的算法,包括标记-清除、标记-整理、分代垃圾回收等。这些算法的选择取决于应用程序的性能需求和内存使用情况。

需要注意的是,垃圾回收是一种自动的过程,开发人员不需要手动管理内存分配和释放。Java的自动内存管理机制通过垃圾回收器来确保内存的有效使用,减少内存泄漏和程序中的其他内存问题。

Java 有可能产生内存泄漏吗,举例? 内存泄露会导致什么问题?

是的,Java也可能发生内存泄漏。内存泄漏是指程序中已经不再需要的对象继续占用内存,从而导致可用内存减少,最终可能导致应用程序崩溃或变得极其缓慢。内存泄漏通常是由于程序员的错误或不当的内存管理导致的。

以下是一些Java中可能导致内存泄漏的情况的示例:

  1. 未关闭资源:在使用文件、网络连接、数据库连接等资源时,如果忘记在不再需要这些资源时关闭它们,就会导致内存泄漏。例如,不关闭数据库连接或文件输入流会占用系统资源,导致内存泄漏。
  2. 不合理的缓存:如果程序中使用了缓存,但没有合理地设置缓存的大小或没有实现缓存对象的过期策略,就会导致缓存中的对象永不释放,从而导致内存泄漏。
  3. 监听器未移除:在Java中,使用监听器模式时,如果对象注册了监听器但未及时取消注册,这些对象会一直保持对事件的监听,导致内存泄漏。
  4. 强引用集合:使用强引用集合(如ArrayList)来保存对象,如果这些对象不再需要但仍然存在于集合中,就会导致内存泄漏。

内存泄漏可能会导致以下问题:

  1. 内存占用过高:内存泄漏会导致应用程序占用的内存逐渐增加,最终耗尽可用内存,导致应用程序崩溃。
  2. 性能下降:随着内存泄漏的增加,应用程序的性能会逐渐下降,因为垃圾回收器需要更多的时间来清理不必要的对象。
  3. 不稳定性:内存泄漏可能导致不稳定性,包括程序崩溃或无响应。

为了防止内存泄漏,开发人员应该定期审查代码,确保及时释放不再需要的资源,关闭连接,移除监听器等。此外,使用内存分析工具可以帮助检测和解决内存泄漏问题。

GC 的过程, 内存是怎么分配的, 是一片一片的使用呢, 还是一大块的使用? 还是想 C/C++ 那样零散的内存呢?怎么解决内存碎片问题?

Garbage Collection(GC)过程中,内存分配方式与C/C++有所不同。在Java中,内存是以堆(Heap)和栈(Stack)两种方式进行分配的,而堆内存通常是一大块一大块的,但堆内存中的对象分配是零散的。

  1. 堆内存分配:Java中的对象通常存储在堆内存中。堆内存是一大块连续的内存,但Java虚拟机会动态分配这块内存,以存储对象的数据。因此,对象在堆内存中的分配是零散的,不像C/C++中的连续内存分配。
  2. 栈内存分配:栈内存主要用于存储局部变量和方法调用的信息。栈内存是一块连续的内存区域,但它主要用于跟踪方法的调用和局部变量的存储,而不是存储对象的实际数据。

解决内存碎片问题的方式通常有两种:

  1. 内存管理策略:垃圾回收器可以使用不同的内存管理策略来减少内存碎片。例如,分代垃圾回收器将堆内存分为新生代和老年代,新生代使用复制算法进行垃圾回收,可以大大减少碎片。老年代使用标记-整理算法来整理内存,进一步减少内存碎片。
  2. 对象池:在一些情况下,可以使用对象池来管理对象的分配和回收。对象池会预分配一批对象,并在需要时从池中获取对象,而不是每次都进行新的对象分配。这可以减少对象分配和回收带来的内存碎片问题。

需要注意的是,Java的自动内存管理机制(垃圾回收)通常能够有效地处理内存碎片问题,而开发人员不需要过多地担心这个问题。但在一些特殊情况下,可以采取一些额外的措施来优化内存使用,比如使用对象池。

HashMap 和 HashTable 的区别

  • HashTable是早期的Java集合类,是线程安全的,但性能相对较差。HashMap是非线程安全的,但性能更好。在多线程环境中,通常推荐使用ConcurrentHashMap来替代HashTable。
  • HashMap允许有null的键和值,而HashTable不允许。
  • HashTable的方法是同步的,而HashMap的方法不是。因此,在多线程环境中,HashMap需要外部同步。

ConcurrentHashMap的线程安全

ConcurrentHashMap通过分段锁的机制来保证线程安全。它将整个Map分成多个段,每个段都拥有自己的锁,不同段的操作可以并发执行,从而提高了性能。

ReentrantLock 和 Synchronized 的区别?

  • ReentrantLock和Synchronized都可以用于多线程锁定,但ReentrantLock提供了更多的灵活性,可以实现公平锁和定时锁等。
  • Synchronized是隐式锁,由Java虚拟机自动管理,而ReentrantLock是显式锁,需要手动控制锁的获取和释放。

你了解什么是悲观锁?什么是乐观锁吗?

  • 悲观锁是一种假定在多线程环境下会发生冲突的锁,例如使用Synchronized或ReentrantLock。
  • 乐观锁是一种假定在多线程环境下不会发生冲突的锁,例如使用CAS(比较并交换)机制。如果发生冲突,它会进行回滚或重试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1b( ̄▽ ̄)d 

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值