hello 我是宝哥 , 接上一篇文章,咱们聊到了

JVM加载类的流程

有面试官会让你解释一下Java的内存模型,有些人解释对了,后果面试官说不对,应该是堆啊、栈啊、办法区什么的(遇到这种面试官,就是你装逼的时刻了..)

看完本篇文章你将理解:

  • 1.JVM内存构造
  • 2.JVM栈帧分析
  • 3.办法区在JDK1.7和1.8中的区别
  • 4.堆分代构造

倡议珍藏!

JVM内存构造

首先JVM内存构造JAVA内存模型是两个概念.

JVM内存构造:

Class文件通过类加载机制 加载到内存空间,JVM内存构造就是上图中内存空间,Java内存模型,则是另外的一个概念.

依据《Java 虚拟机标准(Java SE 7 版)》规定,Java 虚拟机所治理的内存如下图所示。


从上图可看出,运行时数据辨别为五大区域。这些区域各有各的用处,其中办法区和堆是所有线程共享的,栈,本地办法栈和程序计数器则为线程公有的。

1.程序计数器

程序计数器是一块很小的内存空间,它是线程公有的,能够认作为以后线程的行号指示器。

为什么要程序计数器呢

因为CPU会在多个线程中切换上下文,须要应用程序计数器纪录以后线程运行到哪一行了,期待线程从新获取到运行工夫时,持续从计数的地位往下执行.至于它是线程公有的,是因为每个线程都须要独立计数,各个线程之间不会产生影响.

面试题:程序计数器会产生OutOfMemoryError吗?

答:这块内存区域是虚拟机标准中惟一没有OutOfMemoryError的区域。如果线程执行的是个java办法,那么计数器记录虚拟机字节码指令的地址。如果为native办法,那么计数器为空。

2.Java栈(JVM线程栈)

首先咱们要记住,栈形容的是办法执行的内存模型,它是线程公有的.

每个办法被执行的时候都会创立一个栈帧用于存储局部变量表,操作栈,动静链接,办法进口等信息。每一个办法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程,如下图:

面试题:Java虚拟机栈可能呈现哪两种类型的异样?

  • 1 虚拟机栈是一个栈,当咱们的栈帧超过最大深度时,会抛出StackOverflowError
  • 2 栈无奈申请到足够的空间时,抛出OutOfMemoryError异样

栈帧(Stack Frame)

每一个办法从调用至执行实现的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程,那么一个栈帧蕴含什么?

  • 局部变量表(Local Variable Table)

也叫本地变量表,它所须要的内存空间在编译期实现调配,当进入一个办法时,这个办法在栈中须要调配多大的局部变量空间是齐全确定的,在办法运行期间不会扭转,它的最小单位为Slot,一个Slot能够寄存一个32位以内的数据类型.虚拟机通过索引定位的办法查找相应的局部变量,索引的范畴是从0~局部变量表最大容量。如果Slot是32位的,则遇到一个64位数据类型的变量(如long或double型),则会应用两个间断的Slot来存储

  • 操作数栈(Operand Stack)

也常称为操作栈,它是一个后入先出栈(LIFO)。当一个办法刚刚开始执行时,其操作数栈是空的,随着办法执行和字节码指令的执行,会从局部变量表或对象实例的字段中复制常量或变量写入到操作数栈,再随着计算的进行将栈中元素出栈到局部变量表或者返回给办法调用者,也就是出栈/入栈操作。一个残缺的办法执行期间往往蕴含多个这样出栈/入栈的过程。

  • 动静连贯(Dynamic Linking)

在一个class文件中,一个办法要调用其余办法,须要将这些办法的符号援用转化为其在内存地址中的间接援用,而符号援用存在于办法区中的运行时常量池。
Java虚拟机栈中,每个栈帧都蕴含一个指向运行时常量池中该栈所属办法的符号援用,持有这个援用的目标是为了反对办法调用过程中的动静连贯(Dynamic Linking)。
这些符号援用一部分会在类加载阶段或者第一次应用时就间接转化为间接援用,这类转化称为动态解析。另一部分将在每次运行期间转化为间接援用,这类转化称为动静连贯

  • 办法返回地址(Return address)

个别办法执行时,有2种形式会退出该办法

  • 1.失常退出
    失常退出指办法失常实现并退出,没有抛出任何异样,以后办法失常实现,则依据以后办法返回的字节码指令,这时有可能会有返回值传递给办法调用者(调用它的办法),或者无返回值(void)。具体是否有返回值以及返回值的数据类型将依据该办法返回的字节码指令确定。
  • 2.异样退出
    办法执行过程中遇到异样,并且这个异样在办法体外部没有失去解决,导致办法退出,只有在本办法的异样表中没有搜寻到相应的异样处理器,就会导致办法退出。

办法退出过程实际上就等同于把以后栈帧出栈,因而退出能够执行的操作有:复原下层办法的局部变量表和操作数栈,把返回值(如果有的话)压如调用者的操作数栈中,调整PC计数器的值以指向办法调用指令后的下一条指令。
一般来说,办法失常退出时,调用者的PC计数值能够作为返回地址,栈帧中可能保留此计数值。而办法异样退出时,返回地址是通过异样处理器表确定的,栈帧中个别不会保留此局部信息

  • 附加信息

指的是在虚拟机实现中退出了一些标准里没有形容的信息到栈帧之中,例如与调试相干的信息。

本地办法栈

本地办法栈是与虚拟机栈施展的作用十分相似,区别是虚拟机栈执行的是Java办法(也就是字节码)服务,而本地办法栈则为虚拟机应用到的native办法服务,可能底层调用的c或者c++,咱们关上jdk装置目录能够看到也有很多用c编写的文件,可能就是native办法所调用的c代码。

java是跨平台的语言,既然是跨了平台,所付出的代价就是就义一些对底层的管制,而java要实现对底层的管制,就须要一些其余语言的帮忙,这个就是native的作用了.

它是所有线程共享的,它的目标是寄存对象实例。同时它也是GC所治理的次要区域,因而常被称为GC堆,当初收集器常应用分代算法进行垃圾回收,这里是垃圾回收重灾区.

堆分代构造

堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。

新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。它们的默认比例关系如下图:

  • 新生代 (Young Generation)
    新生成的对象优先寄存在新生代中,新生代对象朝生夕死,存活率很低,在新生代中惯例利用进行一次垃圾收集个别能够回收70%~95%的空间,回收效率很高
  • 幸存者区 (Survivor)
    幸存者区有2块, From Survivor区, To Survivor区,也称S0,S1区.被面试官问了不要懵,GC进行时,Eden区中所有存活的对象都会被复制到 To Survivor区,而在FromSurvivor区中,仍存活的对象会依据它们的年龄值决定去向,年龄值达到年龄阀值(默认为15,新生代中的对象毎熬过一轮垃圾回收,年龄值就加1,GC分代年龄存储在对象的 header中)的对象会被移到老年代中,没有达到阀值的对象会被复制到 To Survivor区。接着清空Eden区和 From Survivor区,新生代中存活的对象都在 To Survivor区。接着,From Survivor区和 To Survivor区会替换它们的角色,GC时当 To Survivor区没有足够的空间寄存上一次新生代收集下来的存活对象时,须要依赖老年代进行调配担保,将这些对象寄存在老年代中.
  • 老年代(Old)

在新生代中经验了屡次(具体看虚拟机配置的值)GC后依然存活下来的对象会进入老年代中。老年代中的对象生命周期较长,存活率比拟高,在老年代中进行GC的频率相对而言较低,而且回收的速度也比较慢


常见面试题:

  • 何时产生Minor GC/Young GC ?

新生成的对象在Eden区调配(大对象除外,大对象间接进入老年代),当Eden区没有足够的空间进行调配时,虚拟机将发动一次 Minor GC也叫Young GC

  • 为什么分代?
    将对象依据存活概率进行分类,对存活工夫长的对象,放到固定区,从而缩小扫描垃圾工夫及GC频率。针对分类进行不同的垃圾回收算法,对算法取长补短

办法区


从这张图能够看到JDK1.8和JDK1.7相比最大的区别是:元空间区取代了永恒代,永恒代本来次要寄存ClassMeta的信息。而元空间的实质和永恒代相似,都是对JVM标准中办法区的实现。不过元空间与永恒代之间最大的区别在于:元空间并不在虚拟机中,而是应用本地内存。因而,默认状况下,元空间的大小仅受本地内存限度。

上图中咱们能够看到,JDK1.7和1.8对于运行时数据区和堆中的办法区都做了调整,jdk8中引入了一个新的内存区域叫metaspace。并不是所有的jvm中都有永恒代,IBM的J9,oracle的JRocket都没有永恒代,永恒代是实现层面的货色,永恒代外面存的货色基本上就是办法区规定的那些货色。


面试题: 办法区、永恒代、元空间的区别?

  • 办法区
    是JVM的标准,所有虚拟机必须恪守的。是JVM 所有线程共享的、用于存储类的信息、常量池、办法数据、办法代码等。
  • 永恒代:
    全称Permanent Generation space ,是指内存的永恒保留区域。是 HotSpot 虚拟机基于JVM标准对办法区的一个落地实现,并且只有 HotSpot 才有 PermGen space,在JDK8被移除了.
  • 元空间:
    元空间与永恒代之间最大的区别在于:元空间并不在虚拟机中,而是应用本地内存。它也是基于JVM标准对办法区的一个落地实现

JDK6、JDK7 时,办法区 就是 PermGen(永恒代)。
JDK8 时,办法区就是 Metaspace(元空间)

不同版本的JDK中 堆溢出的实例:


面试题:为什么去除了永恒代:

1)字符串存在永恒代中,容易呈现性能问题和内存溢出。

2)类及办法的信息等比拟难确定其大小,因而对于永恒代的大小指定比拟艰难,太小容易呈现永恒代溢出,太大则容易导致老年代溢出。

3)永恒代会为 GC 带来不必要的复杂度,并且回收效率偏低。

结语

通过本篇学习,咱们曾经晓得JVM对于内存的治理,以及它的构造,

千万不要搞混 JVM运行时数据区和JMM(Java memory modle)的关系

而JAVA对象布局在我的一篇文章中有讲过他们是不同的概念
JAVA对象布局

下一篇咱们来钻研JMM(java的内存模型)和Java中的逃逸剖析,以及多线程编程延长.
请继续关注公众号:JAVA宝典

关注公众号:java宝典