JVM面试真题总结(十二)
文章收录在网站:http://hardyfish.top/
文章收录在网站:http://hardyfish.top/
文章收录在网站:http://hardyfish.top/
文章收录在网站:http://hardyfish.top/
对比Java内存模型与JVM内存模型的不同点
Java内存模型(
Java Memory Model,简称JMM
)和JVM内存模型是两个不同的概念
- 它们关注的问题和解决的问题是不同的。
Java内存模型:
- Java内存模型主要关注的是多线程环境下,如何以线程安全的方式对共享变量进行操作。
- 它定义了变量的读取、写入等操作的规则,并规定了在并发环境下
- 如何通过
volatile、synchronized
等关键字来保证共享变量的可见性和有序性。- Java内存模型解决的是在多线程编程中,如何保证内存的可见性、原子性和有序性,以防止出现数据不一致的问题。
JVM内存模型:
- JVM内存模型主要关注的是JVM的内存区域划分和内存管理。
- 它将JVM内存划分为堆内存、栈内存、方法区、程序计数器等区域,并定义了每个区域的使用方式和作用。
- 比如,堆内存主要用于存储对象实例,栈内存用于存储局部变量,方法区用于存储已被加载的类信息等。
- JVM内存模型主要解决的是内存的分配和回收问题。
总的来说,Java内存模型主要是为了解决多线程编程中的内存可见性和有序性问题
- 而JVM内存模型则是关注JVM如何管理和分配内存。
Java 8的内存结构有哪些显著变化?
在Java 8中,内存结构相较于之前的版本有一些变化。
主要的变化在于永久代(PermGen)被移除,取而代之的是元空间(Metaspace)。
以下是关于这两者的详细解释:
永久代(PermGen):
- 在Java 7及其之前的版本中,永久代主要用于存储类的元数据、静态变量以及方法区等。
- 永久代的内存大小是有限的,当加载的类过多时
- 可能会导致永久代内存溢出(
java.lang.OutOfMemoryError: PermGen space
)
- 这在实际应用中是一个常见的问题。
元空间(Metaspace):
- 在Java 8中,永久代被移除,取而代之的是元空间。
- 元空间与永久代的主要区别在于它的内存分配。
- 元空间并不位于Java堆内存中,而是使用本地内存(Native Memory)。
- 这意味着元空间的大小不再受到Java堆内存的限制,而是受到本地内存的限制,这有助于减少永久代内存溢出的问题。
- 当然,元空间也并非无限大,当元空间的内存分配超出限制时
- 仍然会抛出内存溢出异常(
java.lang.OutOfMemoryError: Metaspace
)。除了上述变化外,Java 8中的内存结构大致保持不变,包括Java堆、栈、程序计数器、本地方法栈等。
Java堆主要用于存储对象实例,栈用于存储局部变量、方法调用等,程序计数器用于存储当前线程的执行位置
- 本地方法栈用于支持本地方法的调用。
总结一下,Java 8中的内存结构变化主要是将永久代替换为元空间
- 这有助于解决永久代内存溢出的问题,同时使得内存分配更加灵活。
在实际应用中,我们需要关注元空间的内存使用情况,以便在需要时进行调整。
为什么Java 8要移除永久代(PermGen)?
永久代(
PermGen
)在Java 8中被移除,主要是因为以下几个原因:简化垃圾收集:
- 在Java 7及其之前的版本中,永久代存储了大量的类的元数据
- 这使得垃圾收集器需要处理这部分内存,增加了垃圾收集的复杂性。
- 移除永久代后,垃圾收集器只需要关注Java堆内存,从而简化了垃圾收集的过程。
避免内存溢出:
- 永久代的内存大小是有限的,当加载的类过多时,可能会导致永久代内存溢出。
- 而元空间使用的是本地内存,其大小只受限于本地内存的大小,因此更不容易出现内存溢出。
提高性能:
- 永久代的内存管理需要消耗一定的性能。
- 移除永久代后,可以减少内存管理的开销,从而提高系统的性能。
更好的内存控制和监控:
- 永久代的内存分配和回收策略与Java堆不同,这使得对其进行控制和监控比较困难。
- 而元空间使用的是本地内存,可以借助于本地内存管理工具进行更好的控制和监控。
总的来说,永久代被移除是为了简化垃圾收集,避免内存溢出,提高性能,以及实现更好的内存控制和监控。
对比堆内存和栈内存的特点和使用场景
堆和栈是Java内存中的两个重要区域,它们在内存分配、数据存储和生命周期等方面有以下主要区别:
内存分配:
- 堆(
Heap
)是Java内存中用于存储对象实例的区域,它是一个运行时数据区,大小可动态扩展。
- 堆内存由所有线程共享,因此在堆中分配的内存可以被所有线程访问。
- 栈(
Stack
)是Java内存中用于存储局部变量、方法调用等的区域。
- 每个线程都有一个独立的栈,栈内存由线程私有。
- 栈的大小是固定的,当栈内存不足时,会导致栈溢出错误(
java.lang.StackOverflowError
)。数据存储:
- 堆中主要存储对象实例及其相关数据。
- 当我们使用new关键字创建对象时,对象实例被分配到堆内存中。
- 栈中主要存储基本数据类型(如
int、float、boolean
等)
- 对象引用变量以及方法调用相关信息(如方法调用的顺序、局部变量等)。
生命周期:
- 堆内存中的对象实例的生命周期较长。
- 它们会在垃圾收集器运行时被回收,具体回收时机取决于垃圾收集器的策略。
- 栈内存中的数据随着方法的调用和返回而创建和销毁。
- 当一个方法执行结束后,该方法在栈中的局部变量和相关信息会被自动销毁。
访问速度:
- 访问堆内存中的对象实例相对较慢,因为它涉及到查找对象引用以及处理垃圾收集等过程。
- 访问栈内存中的数据相对较快,因为栈内存由线程私有,且其数据结构简单,方便存取。
总之,堆和栈的主要区别在于内存分配、数据存储和生命周期。
堆用于存储对象实例,大小可扩展,生命周期较长,访问相对较慢
而栈用于存储基本数据类型、对象引用变量和方法调用相关信息,大小固定,生命周期较短,访问相对较快。