JAVA虚拟机之内存管理

来源:互联网 发布:开票软件下载步骤 编辑:程序博客网 时间:2024/06/05 09:56

JAVA虚拟机之内存管理

最为一名程序猿,不断的去复习java虚拟及内存,你会发现,自己对程序性能、并发、多线程越来越深刻。简单的说,在当今高并发、大数据的互联网时代,Java底层的东西,你必须要懂。毕竟从springboot的到来来看,架构越来越简单,编写越来越简单,而底层的东西越来越少许人关注,但价值越来越高。好了,言归正传,进入正题了。


运行时数据区域

运行时区域的划分若干部分,但我个人认为主要分为:

  1. 所有线程共享的数据区

    • 方法区

  2. 线程隔离的数据区

    • 虚拟机栈
    • 本地方法栈
    • 程序计数器
      这里写图片描述

1. 虚拟机栈

属于线程私有,它的生命周期和线程相同。主要描述的是java方法执行的内存模型. 每个方法的执行,都会创建一个针栈,英文名:Stack Frame,其主要是用来存储局部变量表、操作数栈、动态链接、方法出口等信息。现在好多人一说java内存,就区分为堆和栈,其实这种区分,很粗糙,也很流行,应为大部分人关注的确实是这两点。所指的栈就是虚拟机栈。

这个区域规定两种异常:StackOverFlowError(栈的深度超过了虚拟机的深度)、OutOfMemoryError(虚拟机的内存扩展无法申请到足够的内存)

以下详细介绍几点:

局部变量表

以字节为长度的数组,通过索引来访问的。

  • 基本类型 :如type bolean char short long int float long double,其中double long是64为长度,会占用2个局部变量空间(Slot),其余的数据类型只占一个。
  • 引用对象 :reference类型:
    1. 指向对象起始位置的引用指针
    2. 指向一个对象的句柄 或其他与此对象相关的关系
  • returnAddress类型 :指向一个字节码指令的地址

注: 这里强调下句柄,句柄其实就是一个遥控器,拥有句柄,并不代表又电视机和DVD(好吧,我老了 ^^),而电视机 DVD就是对象。 如:String a; 这不是创建一个对象,而是一个句柄,这个句柄a并没有执行任何地址。按照官方的解释:句柄术语一般用来指获另外一个对象的方法,一个广义上的假指针

操作数栈

也叫操作栈,和局部变量表一样,以字节为长度的素组,但是通过标准的栈操作,即入(压)栈出栈。虚拟机把它最为一个工作区,大多数数据都要从这里弹出、执行运算,并把返回结果压入操作栈里。

动态链接

编译java类或接口的时候,看似没有什么关系,但他们之间是通过接口(Harbor)符号关联的,或者通过与java API的class文件相关联。在运行程序的时候,java虚拟机装载程序类和接口时,并通过动态链接将他们关联起来。

方法出口

返回信息


2. 本地方法栈

本地方法栈其实就是为虚拟机使用到的Native服务的方法。目前来说,对native的使用的方式、语言、数据结构都没有严格的规定,不同的虚拟机实现都很自由。目前Sun HotSpot虚拟机已经把本地方法栈和虚拟机合二为一了。异常抛出也和虚拟机栈一样。


3. 程序计数器

程序计数器占用的内存空间很小,可以看作的当前线程所执行程序的字节码的行号指示器。字节码解释器工作主要通过这个计数器来选择下一条需要执行的字节码指令。分支、判断、跳转、循环、异常处理、线程恢复等功能等都需要他来完成。

任何时刻,一个处理器或一个内核(毕竟有多核处理器)只会执行一跳线程中的指令。因此,每条线程都需要一个独立的程序计数器,且个线程之间它是独立的,互不影响。这类内存区域称为“线程私有”。

执行java代码,这个计数器是在执行虚拟机字节码指令的地址;Native方法,这个计数器是空,即undefined


4. 方法区

方法区被称为用永久代,本质上两者不等。只是虚拟机使用永久代来实现方法区而已。

由于永久带有 -XX:MaxPermSize的上限,就可能会遇到内存溢出的问题。当然J9和JRockit只要没有触碰到进程可用的内存上限,例如32位系统中的4G,就不会出现问题。目前,官方已经在JDK1.7的HotSpot中,移除了原本放到永久代的字符串常量池。方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常。
这里写图片描述
这里解释下运行时常量池和类文件常量池:

  • 类文件常量池:是类的一部分信息,每个类都有且都独立的,用于存放编译器生成的各种字面量和符号引用。而某种类型的字面量和符号引用,都放到常量池项。常量池的组织很简单,前两个字节占用的位置叫做常量池计数器(constant_pool_count),它记录着常量池的组成元素常量池项(cp_info)的个数。紧接着会排列着constant_pool_count-1常量池项(cp_info)。后面自己弄明白了,再后续类的加载。

  • 运行时常量池:而则部分内容将在类加载后进入方法区的运行时常量池,刚开始这些class文件常量池的链接都是符号链接,跟在class文件一样,当运行的时候就会转化为直接链接。比如说class String要调用自己的valueOf方法,它就会有一个Methoderef常量,这是个符号链接,等真的要调用的时候,该常量就会被resolve为一个直接链接(直接指向调用方法的实体)


5. JAVA堆

JAVA堆是Java虚拟机内存最大的一块,也称为GC堆,是所有线程共享的一块内存区域。唯一的目的是存放对象实例,几乎所有的对象实例都分配到这里。但随着JIT编译器的发展与逃逸分析基数、栈上分配、标量替换优化基数将会导致一些变化,所有的对方都分配到堆上越来越不“绝对”了。

堆的内存扩展,可以通过-Xmx 和-Xms控制,如果对内存没有完成实例分配,并且堆也无法在扩展就会抛出OutOfMemoryError异常


直接内存

这块内存并不是虚拟机运行时数据区的内存,也不是Java虚拟机贵方中定义的内存区域。这部分内存使用频繁,野可能导致OutOfMemoryError异常。

JDK1.4有了NIO即New Input/OutPut类,引入了一种基于通道与缓冲区Buffer的I/O方式,他可以使用Native函数库直接分配内存,然后通过一个存储在Java队中的DirectByterBuffer对象最为这块内存的引用进行操作。这样能很大程度上提高性能,因为避免了在Java堆和Native堆中来复制数据。

本机直接内存不会收到Java堆的大小限制,但肯定会受到本地总内存(随机存储器RAM+交换分区SWAP+分页文件)大小及处理器寻址空间的限制。所以配置-Xmx等参数时,如果湖绿直接内存,就会导致内存区域之和大于物理内存,从而导致动态扩展时出现OutOfMemoryError异常。

原创粉丝点击