面试题-JVM-001.介绍Java内存区域

介绍Java内存区域

1. 程序计数器:

  1. 记录下1条需要执行的字节码指令: 分支、循环、跳转、异常处理、线程恢复等功能都需要依赖程序计数器;
  2. 线程私有;
    主要有两个作用:

    1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序、选择、循环、异常处理。
    2. 在多线程下,记录当前线程执行位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪了。
    3. 唯一不会出现 OutOfMemoryError (内存泄漏) 的内存区域!

2. Java虚拟机栈:

  1. 线程私有
  2. 描述的是 Java 方法执行的内存模型,每次⽅法调用都通过栈传递数据
  3. Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上,Java 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)

局部变量表主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。

Java 虚拟机栈会出现两种错误: StackOverFlowError 和 OutOfMemoryError 。

  1. StackOverFlowError:
    若Java 虚拟机栈的大小不允许动态扩展,当线程请求栈的深度超过当前Java 虚拟机栈的最大深度,就抛出 StackOverFlowError。
  2. OutOfMemoryError:
    若Java虚拟机堆中没有空闲内存,并且垃圾回收器也无法提供更多内存的话。就会抛出 OutOfMemoryError。
    Java 方法有两种返回方式,不管哪种返回方式都会导致栈帧被弹出:
  • return 语句。
  • 抛出异常。

3. 本地方法栈

虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。

方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现StackOverFlowError 和 OutOfMemoryError 两种错误

4. 堆

JVM管理的内存中最大的一块,Java堆是所有线程共享的一块内存区域,在虚拟机启动时创建.

  1. 存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
  2. 从jdk1.7开始已经默认开启逃逸分析,如果某些方法中的对象引用没有被返回或者未被外面使用(也就是未逃逸出去),那么对象可以直接在栈上分配内存。
  3. Java堆是GC管理的主要区域,因此也被称作GC 堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以 Java 堆还可以细分为:新生代老年代:再细致一点有:Eden 空间、From Survivor、To Survivor 空间等。

新生代:

Eden 区、两个 Survivor 区都属于新生代->按照顺序被命名为 s1 和 s2

老年代:

大部分情况,对象都会首先在 Eden 区域分配,在一次新生代GC后,如果对象还存活,则会进入 s1 或者 s2,并且年龄加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。

几个错误:
OutOfMemoryError: GC Overhead Limit Exceeded:

当JVM花太多时间执行GC且只能回收很少的堆空间,就会发生此错误!

java.lang.OutOfMemoryError: Java heap space:

在创建新对象时, 堆内存空间不足, 会引发java.lang.OutOfMemoryError: Java heap space 错误。(和本机物理内存无关,和你配置的内存大小有关!)

5. 方法区/元数据区

⽅法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息常量静态变量即时编译器编译后的代码等数据。

虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有⼀个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。

扩展1. 方法区和永久代的关系

  1. 方法区是规范,永久区是HotSpot的实现
    《Java 虚拟机规范》规定了方法区的概念和它的作用,并没有规定如何去实现。
    方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代HotSpot 的概念,方法区Java虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
  2. Metaspace(元空间)和 PermGen(永久代)类似,都是对JVM规范中方法区的一种落地实现。

    不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

    扩展2. 方法区/永久代/Metaspace

  3. 方法区:(逻辑上)
    逻辑上的东西,是JVM 的规范,所有虚拟机必须遵守的。
    是JVM 所有线程共享的、用于存储类的信息、常量池、方法数据、方法代码等。
  4. 永久代:(方法区的实现、JDK7及之前、主要是和元空间对比)
    PermGen , 就是 PermGen space ,全称是 Permanent Generation space ,是指内存的永久保存区域。PermGen space 则是 HotSpot 虚拟机基于JVM规范对方法区的一个落地实现,并且只有 HotSpot 才有 PermGen space。而如 JRockit(Oracle)、J9(IBM) 虚拟机有方法区 ,但是就没有 PermGen space。PermGen space 是JDK7及之前, HotSpot虚拟机对方法区的一个落地实现。在JDK8被移除。‘
  5. Metaspace(元空间、JDK8及之后):
    元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存

使用本地内存有什么好处呢?最直接的表现就是OOM问题将不复存在,因为默认的类的元数据分配只受本地内存大小的限制,也就是说本地内存剩余多少,理论上Metaspace就可以有多大,这解决了空间不足的问题。不过,让Metaspace变得无限大显然是不现实的,因此我们也要限制Metaspace的大小:使用-XX:MaxMetaspaceSize参数来指定Metaspace区域的大小。JVM默认在运行时根据需要动态地设置MaxMetaspaceSize的大小。

如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器。根据JDK 8的特性,G1和CMS都会很好地收集Metaspace区(一般都伴随着Full GC)

-XX:MetaspaceSize是分配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark ),此值为估计值。MetaspaceSize的值设置的过大会延长垃圾回收时间。垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大。
-XX:MaxMetaspaceSize是分配给类元数据空间的最大值,超过此值就会触发Full GC,此值默认没有限制,但应取决于系统内存的大小。JVM会动态地改变此值。
-XX:MinMetaspaceFreeRatio表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最小比例,不够就会导致垃圾回收。
-XX:MaxMetaspaceFreeRatio表示一次GC以后,为了避免增加元数据空间的大小,空闲的类元数据的容量的最大比例,不够就会导致垃圾回收。

移除PermGen(永久代)从从JDK7 就开始。例如,字符串内部池,已经在JDK7 中从永久代中移除。直到JDK8 的发布将宣告 PermGen(永久代)的终结。
其实,移除 PermGen 的工作从 JDK7 就开始,永久代的部分数据就已经转移到了 Java Heap 或者是 Native Heap。
但永久代仍存在于JDK7 中,并没完全移除,比如:
字面量 (interned strings)转移到 Java heap;
类的静态变量(class statics)转移到Java heap ;
符号引用(Symbols) 转移到 Native heap ;
JDK版本 方法区的实现 运行时常量池所在的位置
JDK6 PermGen space(永久代) PermGen space(永久代)
JDK7 PermGen space(永久代) Heap(堆)
JDK8 Metaspace(元空间) Heap(堆)

字符串内部池,已经在JDK7 中从永久代中移除(在堆中)-jdk7中已经是了.
JDK6、JDK7 时,方法区 就是 PermGen(永久代)。
JDK8 时,方法区就是 Metaspace(元空间)。

扩展3. 字符串常量池在哪

  1. jdk6->PermGen永久代
  2. jdk7/jdk8->Heap堆内存

你可能感兴趣的:(面试问题jvm)