百度面试题解析:synchronized、volatile、JMM内存模型、JVM运行时区域及堆和方法区(三)

16. 说一下synchronized的修饰范围以及锁的部门?

`synchronized`是Java中实现线程同步的关键字,它可以修饰方法或代码块。通过`synchronized`关键字,Java能够保证在同一时刻只有一个线程可以执行被保护的代码块。

  1. **修饰实例方法**:

当`synchronized`修饰实例方法时,它修饰的是对象的实例锁。每个对象都有一个唯一的实例锁,只有获取该锁的线程才能执行被`synchronized`修饰的方法。

```java

public synchronized void someMethod() {

// 同步代码

}

```

  1. **修饰静态方法**:

当`synchronized`修饰静态方法时,它修饰的是类级别的锁。类级别的锁是针对类的所有实例共享的,因此静态方法的同步是针对类的类锁。

```java

public static synchronized void someStaticMethod() {

// 同步代码

}

```

  1. **修饰代码块**:

`synchronized`还可以修饰代码块,确保只有一个线程能够执行特定的代码块。通常使用`synchronized(this)`或者`synchronized(ClassName.class)`来加锁。

```java

public void someMethod() {

synchronized (this) {

// 同步代码

}

}

```

  • **锁的部门**:

锁的部门就是加锁的对象。例如:

  • 对于实例方法,锁是当前对象(`this`)。

  • 对于静态方法,锁是当前类的`Class`对象。

  • 对于代码块,锁是提供的对象。

17. 了解volatile吗?都有哪些特性?分别举例子

`volatile`是Java中的一个轻量级同步机制,它确保了变量的可见性,即当一个线程修改了`volatile`变量的值,其他线程能立刻看到修改后的值。`volatile`变量不会被缓存到寄存器中,它直接从主内存读取。

**特性**:

  1. **保证可见性**:

`volatile`修饰的变量在多个线程之间是可见的。修改一个`volatile`变量的线程会立刻将修改结果刷新到主内存,其他线程能立即读取到修改后的值。

```java

private volatile boolean flag = false;

public void changeFlag() {

flag = true; // 设置为true

}

public boolean checkFlag() {

return flag; // 立即反映flag的最新值

}

```

  1. **禁止指令重排**:

`volatile`禁止编译器和JVM对该变量的读写操作进行重排序优化。它会确保对`volatile`变量的操作在执行时的顺序是按照代码的顺序进行的。

```java

private volatile int a = 0;

public void setA() {

a = 1; // 第一行

// 其他操作

}

public void printA() {

System.out.println(a); // 保证printA在setA之后才能看到a的最新值

}

```

  1. **不能保证原子性**:

`volatile`不能保证复合操作的原子性,如`i++`。如果需要复合操作的原子性,应该使用`synchronized`或`AtomicInteger`等机制。

```java

private volatile int count = 0;

public void increment() {

count++; // 不是原子操作

}

```

18. JMM内存模型

JMM(Java Memory Model)是Java虚拟机中用来定义多线程之间如何交互的内存模型。它规定了线程对共享变量的访问规则,保证了程序在多线程环境下的正确性。

  • **工作内存**:每个线程都有自己的工作内存(缓存),即该线程的局部变量和对共享变量的缓存副本。

  • **主内存**:所有线程共享的内存,存放所有的实例字段、静态字段和构成对象的字段。

  • **规则**:

  1. **原子性**:基本的读写操作是原子的,但复合操作(如`i++`)不是原子的。

  2. **可见性**:一个线程对共享变量的修改必须立刻对其他线程可见(通过`volatile`、`synchronized`等机制)。

  3. **有序性**:Java编译器和JVM为了提高性能可能会进行指令重排,但JMM规定了一些发生顺序,以确保程序执行时的顺序符合预期(通过`synchronized`、`volatile`等控制顺序)。

19. JVM运行时区域

JVM(Java Virtual Machine)运行时区域主要包括以下几部分:

  1. **程序计数器**:

每个线程有一个程序计数器,它指示当前线程执行的字节码的行号。

  1. **Java虚拟机栈**:

每个线程有一个私有的虚拟机栈,用于存储方法调用的局部变量、操作栈、方法返回地址等。每个方法调用时都会创建一个栈帧,方法结束时栈帧被销毁。

  1. **本地方法栈**:

本地方法栈与Java虚拟机栈类似,但它用于支持JVM调用的本地方法。

  1. **堆**:

堆是存放Java对象的地方,所有的对象实例和数组都分配在堆中。堆是JVM中最大的一块内存区域。

  1. **方法区**:

方法区存储的是类的元数据、常量、静态变量和即时编译器编译后的代码。它是共享的,多个线程可以访问。

  1. **直接内存**:

直接内存并不属于JVM运行时区域的一部分,但通过`java.nio`包,程序可以直接操作系统的内存。

20. 堆、方法区和虚拟机栈分别是什么?存了什么?

  • **堆**:

堆是JVM中存放对象实例和数组的地方,是Java内存中最大的一块区域。JVM会对堆进行垃圾回收。

  • **方法区**:

方法区存储JVM加载的类信息、常量池、静态变量、即时编译器编译后的代码等。

  • **虚拟机栈**:

虚拟机栈用于存储方法调用的栈帧,每个方法调用都会在栈中分配一个栈帧,栈帧中包含局部变量、操作数栈等信息。

21. 方法区的版本变化以及内部常量池的变化?

  • **方法区的版本变化**:

在早期的JVM实现中,方法区是堆的一部分,但在Java 8以后,方法区被移除并被称为**元空间(Metaspace)**。元空间不再在JVM堆中分配内存,而是使用本地内存。

  • **常量池的变化**:

  • **JDK 7及以前**:常量池存储在方法区内。

  • **JDK 8及以后**:常量池被移到了堆中,变成了运行时常量池。原本存储在方法区的类元数据也被移到了元空间。

22. 堆为什么要分成新生代和老年代?

JVM的堆被分为新生代(Young Generation)和老年代(Old Generation),主要是为了优化垃圾回收(GC)过程,减少GC的开销。

  1. **新生代**:

新生代主要存放新创建的对象。新生代中的对象大部分生命周期短暂,因此GC频繁发生,但每次回收的代价较小。

  1. **老年代**:

老年代用于存放存活较久的对象。因为老年代对象的生命周期较长,GC较少发生,但回收代价较大。

通过分代回收,JVM能够优化GC过程,提高性能。年轻代使用**Minor GC**,老年代使用**Major GC**或**Full GC**。

相关推荐
YSRM3 小时前
Leetcode+Java+图论II
java·leetcode·图论
十铭忘3 小时前
基于SAM2的眼动数据跟踪2
java·服务器·前端
luckyPian4 小时前
前端+AI:HTML5语义标签(一)
前端·ai·面试·html·html5·ai编程
okjohn4 小时前
浅谈需求分析与管理
java·架构·系统架构·软件工程·产品经理·需求分析·规格说明书
芒果量化4 小时前
Optuna - 自动调参利器&python实例
开发语言·python·算法·机器学习
Baihai_IDP4 小时前
LLM 应用评估综合指南(多轮对话系统、RAG、AI Agent)
人工智能·面试·llm
用户0332126663674 小时前
Java添加、设置和删除PDF图层:
java
foundbug9994 小时前
基于CSMA-CA协议的V2X通信MATLAB仿真
开发语言·网络·matlab
荣光波比4 小时前
K8S(十)—— Kubernetes核心组件详解:Pod控制器与配置资源管理
java·容器·kubernetes