深入理解JVM内存区域

简介: 随着互联网的发展,高并发高可用、快速响应成为软件的必须,而JVM与这些有着密切关联。

1.JVM整体组成

(1)jvm整体组成:

1.类加载器
2.运行时数据区
3.执行索引
4.本地库接口

(2)各个组成部分的用途:

程序在执行之前先要把java代码转换成字节码(class文件),jvm首先需要把字节码通过一定的方式 类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area),而字节码文件是jvm的一套指令集规范,并不能直接交个底层操作系统去执行,因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令再交由CPU去执行,而这个过程中需要调用其他语言的接口 **本地库接口(Native Interface)**来实现整个程序的功能,这就是这4个主要组成部分的职责与功能。

2.运行时数据区域

(1)基本组成:

jvm的运行时数据区,不同虚拟机实现可能略微有所不同,但都会遵从Java虚拟机规范,Java 8 虚拟机规范规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:

  1. 程序计算器
  2. java虚拟机栈
  3. 本地方法栈
  4. java堆
  5. 方法区

(2)线程私有-程序计数器

较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响
如果线程正在执行的是一个Java方法,则指明当前线程执行的代字节码行数
如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)
此内存区域是唯一一个不会出现OutOfMemoryError情况的区域。

(3)线程私有-虚拟机栈

1.生命周期和线程一致。描述的是 Java 方法执行的内存模型:每个方法在执行时都会床创建一个栈帧(Stack Frame)用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。
**2.**栈的大小缺省1M,可用参数-Xss调整大小。
**3.**在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性之中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

***局部变量表:***顾名思义就是存放局部变量的表。主要存放java的八大数据类型,如果局部的一些对象,比如object对象,我们只存它的一个引用地址(基本数据类型,对象引用,return 类型)

***操作数栈:***与局部变量表一样,均以字长为单位的数组。不过局部变量表用的是索引,操作数栈是弹栈/压栈来访问。操作数栈可理解为java虚拟机栈中的一个用于计算的临时数据存储区。

动态链接: 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。在后面的方法调用中会详细介绍。

返回地址: 当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且这个异常没有在方法体内得到处理。 方法退出的过程实际上就等同于把当前栈帧出栈,因此退出时可能执行的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有的话)压入调用者栈帧的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令等。

线程私有-本地方法栈

各虚拟机自由实现,本地方法栈native方法调用 ,本地方法栈的功能和特点类似于虚拟机栈,均具有线程隔离的特点以及都能抛出StackOverflowError和OutOfMemoryError异常。

(4)方法区(永久代(JDK1.7和以前),元空间)

主要存放:类信息,常量,静态变量,即时编译器编译后代码。 更加详细一点的说法是方法区里存放着类的版本,字段,方法,接口和常量池。常量池里存储着字面量和符号引用。
符号引用包括:1.类的全限定名,2.字段名和属性,3.方法名和属性。

在JDK1.7之前运行时常量池包括字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
在JDK1.7 运行时常量池进入到堆
在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之。

(5)java堆

堆是需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等)几乎所有的对象都是在堆中分配.

(6)直接内存

使用Native函数库直接分配堆外内存(NIO)
并不是JVM运行时数据区域的一部分,但是会被频繁使用(可以通过-XX:MaxDirectMemorySize来设置(不设置的话默认与堆内存最大值一样,也会出现OOM异常)
使用直接内存避免了在Java 堆和Native 堆中来回复制数据,能够提高效率

(7)溢出泄露问题

**内存溢出:**程序在申请内存时,没有足够的内存空间
**栈溢出:**方法死循环递归调用(StackOverflowError),不断建立线程(OutOfMemoryError)
**堆溢出:**不断的创建对象,分配对象大于堆的大小
**直接内存:**JVM分配的本地直接内存大小大于JVM的限制(可以通过-XX:MaxDirectMemorySize来设置(不设置的话默认与堆内存最大值一样,也会出现OOM异常)
**方法区溢出:**在经常动态生产大量Class的应用中,CGLIb字节码增强,动态语言,大量JSP(JSP第一次运行需要编译成Java类),基于OSGi的应用(同一个类,被不同的加载器加载也会设为不同的类)
**内存泄露:**程序在申请内存后,无法释放已申请的内存空间。

(8)内存泄露和内存溢出辨析

内存溢出:实实在在的内存空间不足导致;
内存泄漏:该释放的对象没有释放,常见于使用容器保存元素的情况下。
如何避免:
内存溢出:检查代码以及设置足够的空间
内存泄漏:一定是代码有问题
往往很多情况下,内存溢出往往是内存泄漏造成的

# 基础 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×