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

AlgoStriderX
• 阅读 775

介绍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版本方法区的实现运行时常量池所在的位置
JDK6PermGen space(永久代)PermGen space(永久代)
JDK7PermGen space(永久代)Heap(堆)
JDK8Metaspace(元空间)Heap(堆)

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

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

  1. jdk6->PermGen永久代
  2. jdk7/jdk8->Heap堆内存
点赞
收藏
评论区
推荐文章
限时发布!纯手打“RocketMQ笔记”
1JVM的内存区域布局java代码的执行步骤有三点java源码文件编译器字节码文件字节码文件JVM机器码机器码系统CPU执行JVM执行的字节码需要用类加载来载入;字节码文件可以来自本地文件,可以在网络上获取,也可以实时生成。就是说你可以跳过写java代码阶段,直接生成字节码交由JVM执行其中Jav
Wesley13 Wesley13
3年前
java运行时数据区
运行时数据区包括以下几个部分:程序计数器,堆,java栈,本地方法栈,方法区1.程序计数器:当CPU需要执行指令时,需要从程序计数器中获取当前需要执行的指令所在存储单元的地址。用来指示执行哪条指令。其大小不会随程序的执行而发生改变。2.Java栈:java方法执行的内存模型。存放的时一个个栈帧,每个栈帧对应一个被调用的方法。  栈帧中包括:局
good123 good123
3年前
一文看懂JVM内存区域分布与作用
那么我们在开始介绍Java内存区域之前,我们先放一张内存区域的图,方便我们后面介绍的时候可以对照着看。须知,本文是根据JDK8来介绍的。程序计数器首先它是线程私有的,它也称为代码的行号指示器,字节码解释器就是通过改变程序计数器的位置来确定下一行要执行的代码,它不存在OOM。如果线程正在执行一个Java方法,那么它记录的是正在执行虚拟机字节码指令的地址,如果是
Stella981 Stella981
3年前
JVM内存模型和类加载机制
JVM内存模型Java代码是运行在Java虚拟机(JVM)上的,Java虚拟机通过解释执行(解释器)或编译执行(编译器)来完成。Java内存模型分为5个部分:方法区(MethodArea),Java堆(Heap),Java栈(VMStack),本地方法栈(NativeMethodStack),程序计数器(PC寄存器)!(ht
Stella981 Stella981
3年前
JVM内存区域划分
JVM内存区域划分一、JVM运行时数据区划分根据《Java虚拟机规范》JVM会把它管理的内存划分为若干个不同的数据区域,如下图所示:方法区、堆、栈(虚拟机栈、本地方法栈)、程序计数器。线程私有的意思是指,JVM每遇到一个新的线程就会为他们分配栈和程序计数器。!(https
Stella981 Stella981
3年前
JVM技术总结
1.程序计数器:记录正在执行的虚拟机的字节码的指令地址。2.java虚拟机栈:每个Java方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在Java虚拟机栈中入栈和出栈的过程。该区域可能抛出以下异常:1.当线程请求的栈深度超过最大值,会抛出
Stella981 Stella981
3年前
JVM探秘3:内存溢出
在Java虚拟机内存区域中,除了程序计数器外,其他几个内存区域都可能会发生OutOfMemoryError,这次通过一些代码来验证虚拟机各个内存区域存储的内容。在实际工作中遇到内存溢出异常时,需要做到能根据异常信息快速判断是哪个内存区域的溢出,知道什么样的代码会导致这些区域内存溢出,并且知道出现内存溢出后如何处理。Java堆溢出Jav
Stella981 Stella981
3年前
JVM中即时编译器JIT与解释器并存
一.学习目标1.了解解释器与编译器的概念与作用。2.知道jvm中三种执行模式。3.了解热点代码。二.解释器模式与编译器模式以及混合模式  字节码文件通过类装载器装载,被分配被分配到JVM的运行时数据区,然后会被执行引擎执行。执行引擎以指令为单位读取Java字节码。它就像一个CPU一样,一条一条地执行机器指令。每个字节码指令
Wesley13 Wesley13
3年前
Java 进阶(一) JVM运行时内存模型
1.JVM运行时数据区域的划分a.程序计数器(ProgramCounterRegister)一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。每个线程拥有独立的一个计数器,如果当前执行的是Native方法,则计数器值为空。b.JVM栈(JavaVirtualMachineS
Wesley13 Wesley13
3年前
Java 虚拟机垃圾收集机制详解
本文摘自深入理解Java虚拟机第三版垃圾收集发生的区域之前我们介绍过Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈三个区域随线程共存亡。栈中的每一个栈帧分配多少内存基本上在类结构确定下来时就已知,因此这几个区域的内存分配和回收都具有确定性,不需要考虑如何回收的问题,当方法结束或线程结
Stella981 Stella981
3年前
JVM学习第一天
程序计数器当前线程所执行的字节码的行号指示器每个线程都有自己私有的计数器native方法,计数器值为空该内存区域没有规定任何的OutOfMemoryError情况虚拟机栈Java方法执行的内存模型,用于存储局部变量标、操作数栈、动态链接、方法出口等信息虚拟机栈也是线程私有局部变量表所需的内存控件在