您的位置:首页 > 编程语言 > Java开发

JDK8之后-JVM运行时数据区域

2018-03-05 21:12 274 查看

java虚拟机运行时数据区域

首先弄清几个概念:

1.方法区(method area)只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。永久代是HotSpot虚拟机特有的概念,是对方法区的实现,别的JVM没有永久代的概念。(虽然去除了永久代,但是方法区作为概念上的区域仍然存在)

2.在JDK8中,JDK8的HotSpot VM已经是以前的HotSpot VM与JRockit VM的合并版,也就是传说中的“HotRockit”,只是产品里名字还是叫HotSpot VM。所以对于说JDK8去除永久代换成元空间的说法,就是默指的合并后的HotSpot虚拟机。

3.为什么要将永久代去除呢?

一方面是节省空间,避免了常见的永久内存错误:java.lang.OutOfMemoryError: PermGen问题。另一方面是为了整合JRockit,因为JRockit没有永代区这样类似的空间。

其实,从jdk7开始,就开始了永久代的转移工作,将譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;等。但是指导JDK8永久代才被元空间替代。

4.元空间又是什么呢?以前存储在永久代里面的数据现在存在了哪里?

元空间是一块与堆不相连的本地内存。原本存在永久代的数据,一部分移到了java堆里面,一部分移到了本地内存里面(即元空间)(文档中原句:Move part of the contents of the permanent generation in Hotspot to the Java heap and the remainder to native memory.) 。永久代中原来存储的字符串常量(池)、符号引用(这两个在jdk7普遍就已经将其放在堆上了)和类的静态变量现在存储在java堆中,其余的数据作为元数据存储在元空间中。

5.什么是元数据呢?

元数据是数据的数据或者叫做用来描述数据的数据或者叫做信息的信息。(比如原本方法区存储的类信息、即时编译器编译后的代码等),也可以把元数据简单的理解成,最小的数据单位。元数据可以为数据说明其元素或属性(名称、大小、数据类型、等),或其结构(长度、字段、数据列),或其相关数据(位于何处、如何联系、拥有者)。

6.元空间详细:http://blog.csdn.net/lk7688535/article/details/51767460

java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干的不同的数据区域。这些区域各有用途和各自的创建和销毁的时间。下面介绍一下jvm在运行时的数据区域。

首先看一下运行时数据区的总体结构与相关信息(这是jdk7及其以前的组件图)



线程共享区域:方法区、堆、本地库接口

线程私有区域:虚拟机栈(VM Stack)、本地
4000
方法栈(Native Stack)、程序计数器

程序计数器(Program Counter Register)

占有较小的内存空间

可以看作是当前所执行字节码的行号指示器

当线程执行java方法时,记录的是正在执行的vm字节码指令的地址。若执行的为Native方法,计数器值为空即undefined。

唯一一个在jvm中没有规定OutOfMemoryError异常的区域

java虚拟机栈(Java Virtual Machine Stacks)

线程私有,生命周期与线程相同。

描述的是Java方法执行的内存模型:每一个方法执行的同时都会创建一个栈帧(Stack Frame),由于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行就对应着栈帧在虚拟机栈中的入栈,出栈过程。

java内存中的常分为堆内存和栈内存,其中的栈内存就可以认为这个VM Stack,或者说是VM Stack中的局部变量表。

局部变量表:存放编译期可知的各种基本数据类型、对象引用类型和returnAddress类型(指向一条字节码指令的地址:函数返回地址)。long、double占用两个局部变量控件Slot,其余的占一个Slot。局部变量表所需的内存空间在编译期确定,当进入一个方法时,方法在栈帧中所需要分配的局部变量控件是完全确定的,不可动态改变大小。

本地方法栈(Native Method Stack)

为VM提供Native方法服务

本地方法

当某个线程调用一个本地方法时,便不再受虚拟机的限制。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区。

本地方法本质上时依赖于实现的,虚拟机实现的设计者们可以自由地决定使用怎样的机制来让Java程序调用本地方法。

任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。

本地方法接口需要回调Java虚拟机中的Java方法时,该线程会保存本地方法栈的状态并进入到另一个Java栈。

HotSpot虚拟机中把本地方法栈和虚拟机栈合二为一。

java堆

JVM中所管理内存中的最大的一块。在虚拟机启动时被创建。

唯一的目的是存放对象实例,几乎所有的对象实例和数组都是在这里分配内存。(JVM规范中说的是所有的,但是随着JIT便编译器的发展和逃逸技术分析的成熟,一些实例可以不在这个区域分配内存)

堆是垃圾收集管理的主要区域,所以也会被称为”GC堆“

浅堆和深堆

浅堆(Shallow Heap)和深堆(Retained Heap)是两个非常重要的概念,它们分别表示一个对象结构所占用的内存大小和一个对象被GC回收后,可以真实释放的内存大小。

浅堆(Shallow Heap)是指一个对象所消耗的内存。在32位系统中,一个对象引用会占据4个字节,一个int类型会占据4个字节,long型变量会占据8个字节,每个对象需要占用8个字节。

深堆(Retained Heap)的概念略微复杂。要理解深堆,首先需要了解保留集(Retained Set)。对象A的保留集指当对象A被垃圾回收后,可以被释放的所有对象集合(包括对象A本身),即对象A的保留集可以被认为是只能通过对象A被直接或间接访问到的所有对象的集合。通俗地说,就是指仅被对象A所持有的对象的集合。深堆是指对象的保留集中所有的对象的浅堆大小之和。

例如:对象A引用了C和D,对象B引用了C和E。那么对象A的浅堆大小只是A本身,不含C和D,而A的实际大小为A、C、D三者之和。而A的深堆大小为A与D之和,由于对象C还可以通过对象B访问到,因此不在对象A的深堆范围内。

方法区

前面已经介绍方法区在JDk8之后的的变动

JDK7及之前版本的方法区(Method Area)和Java堆一样,是各个线程共享的内存区域,用于存储已经被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。

虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但它有另外一个名字叫Non-Heap(非堆)。

两个子区域:

持久代: 这个区域会存储包括类定义、结构、字段、方法(数据及代码)以及常量在内的类相关数据。它可以通过-XX:PermSize及-XX:MaxPermSize来进行调节。如果它的空间用完了,会导致java.lang.OutOfMemoryError: PermGenspace的异常。而JDK8开始,持久代已经被彻底删除了,取代它的是另一个内存区域也被称为元空间。

存放数据区域:方法区存储的是每个class的信息,类加载器引用(classLoader)、运行时常量池(所有常量、字段引用、方法引用、属性)、每个方法的名字、类型(如类的全路径名、类型或接口) 、修饰符(如public、abstract、final)、属性、每个方法的名字、返回类型、参数类型(按顺序)、修饰符、属性、方法代码。

方法区也可被垃圾收集,例如:在HotSpot中将GC分代收集扩展至方法区。避免了还要再为方法区编写GC算法。

总结:在java中虚拟机自动内存管理机制下,创建对象不需要像c一样自己进行内存的分配,不容易发生内存泄漏等问题,但是一旦发生这些问题便不容易去找到发生问题的根源,了解JVM的内存模型使你可以更容易的查找原因。

友情链接:一位python大佬的技术简书https://www.jianshu.com/u/bdd1b3ad7323
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: