我将根据“30道有趣的JVM面试题(小结)”这篇文章,给出一份完整的攻略,包括每道面试题的解析和答案。
1. 什么是JVM?
JVM即Java Virtual Machine,Java虚拟机。它是一种能够在各种平台上运行Java程序的虚拟机。JVM可以将Java代码编译成字节码,然后在不同的平台上通过解释执行这些字节码以实现Java程序的运行。
2. Java程序运行时,JVM内存是如何划分的?
JVM内存主要分为以下几个部分:
- 堆区(heap): 存放对象实例,被所有线程共享。堆区在Java程序启动时被创建,其大小可以通过 JVM 的启动参数 -Xmx 和 -Xms 来设定。
- 虚拟机栈(stack): 每个线程都有独立的虚拟机栈,用于存储局部变量和方法调用的信息。每个方法在执行的时候都会创建一个栈帧(stack frame)来存储方法参数、局部变量等信息。栈的深度由JVM的启动参数-Xss来设定。
- 本地方法栈(native stack): 与虚拟机栈类似,但是用于执行本地方法。
- 方法区(method area): 存储类信息、常量等。在 HotSpot JVM 中,方法区被称为 Permanent Generation(永久代),但是在 JDK 8 中,永久代被移除,方法区和堆区是同一个区域:Metaspace。
- PC寄存器(Program Counter Register):记录当前线程执行的字节码位置。
3. 什么是类加载器?有哪些类加载器?
类加载器即ClassLoader。类加载器用于将类的.class文件加载到JVM中,并将其转换为Class对象。Java类库中的类加载器按照工作方式的不同,可以分为以下几类:
- 启动类加载器(Bootstrap ClassLoader):用于加载Java的核心类库,如java.lang.*等。
- 扩展类加载器(Extension ClassLoader):用于加载扩展目录(ext目录)中的类。
- 系统类加载器(System ClassLoader):用于加载应用程序classpath目录下的类。
- 自定义类加载器(Custom ClassLoader):用户自定义的类加载器,通过继承ClassLoader并实现自定义功能来实现。
4. Java内存模型是什么?
Java内存模型(Java Memory Model, JMM)规定了JVM中各个线程如何与内存进行交互。它定义了一系列规则来保证多线程程序的正确性和可见性。
Java内存模型采用了一种称为“主内存-工作内存”模型。主内存是所有线程共享的内存区域,工作内存是线程独立的内存区域。工作内存中保存了主内存中的部分数据副本,线程在工作内存中读写数据,再将修改后的值写回主内存,确保不同线程之间的数据可见性与协同工作的正确性。
5. 什么是信号量?
信号量(Semaphore)是一种用于控制访问共享资源的计数器。它可以用于限制并发线程的数量,保护共享资源的访问。信号量通常是基于操作系统提供的原语实现的。
Java提供了java.util.concurrent.Semaphore类,可以使用它方便地实现信号量。Semaphore类有两个方法:acquire和release,用于获取和释放信号量的计数器。可以使用Semaphore的构造函数指定计数器的初始值。
示例:
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
for (int i = 0; i < 5; i++) {
new Thread(new Task(semaphore)).start();
}
}
static class Task implements Runnable {
private Semaphore semaphore;
public Task(Semaphore semaphore) {
this.semaphore = semaphore;
}
public void run() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " acquire semaphore");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " release semaphore");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上述代码创建了一个初始计数器为2的Semaphore,然后启动5个线程来尝试获取信号量。由于计数器为2,所以只有两个线程能够成功获取信号量,另外三个线程需要等待。在获取信号量后,线程执行任务,然后释放信号量,让其他线程能够获取信号量。
6. 什么是自旋锁?
自旋锁(Spin Lock)是一种保证同步的机制,它不会使线程进入阻塞状态,而是让线程不断地执行循环检查锁的状态,直到获取到锁为止。自旋锁通常适用于轻量级的同步场景。
Java中,自旋锁通常有两种实现方式:CAS(Compare and Swap)和AtomicInteger。这两种实现方式都可以通过循环不停地尝试CAS操作来保证线程的同步。
示例:
import java.util.concurrent.atomic.AtomicInteger;
public class SpinLockDemo {
private AtomicInteger state = new AtomicInteger(0);
public void lock() {
while (!state.compareAndSet(0, 1)) {
// 空循环,等待获取锁
}
}
public void unlock() {
state.set(0);
}
public static void main(String[] args) {
SpinLockDemo spinLock = new SpinLockDemo();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + " get lock");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock");
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start");
spinLock.lock();
System.out.println(Thread.currentThread().getName() + " get lock");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.unlock();
System.out.println(Thread.currentThread().getName() + " release lock");
}).start();
}
}
上述代码中,实现了一个自旋锁SpinLock,使用AtomicInteger来实现。在lock方法中,不断循环尝试CAS操作以获取锁;在unlock方法中,则将状态设为0,表示释放锁。然后启动两个线程分别尝试获取锁,执行任务,释放锁。由于使用了自旋锁,线程不会进入阻塞状态,从而减少了线程切换的开销。
通过上述示例,可以更好地理解什么是自旋锁并学会实现自旋锁。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:30道有趣的JVM面试题(小结) - Python技术站