Java虚拟机——内存区域及内存溢出异常

一、Java内存区域

1、概述

      对于java程序员来说,在虚拟机的自动内存管理机制的帮助下,不需要为每一个new操作去写delete/free代码,而且不容易出现内存泄漏和内存溢出问题。但是把内存控制的权利交给虚拟机管理,一旦出现内存泄漏和溢出的问题,不了解虚拟机是怎样使用内存的,那排查错误将会成为一项异常艰难的工作。

2、运行时数据区域

      

2.1、程序计数器

    程序计数器(Program Counter Register)是一块较小的内存空间,他的作用可以看做事当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基本功能都需要依赖这个计数器来完成。

    由于java虚拟机的多线程是通过线程轮流切换并分配处理执行时间的方式实现的,所以每条线程都需要有一个独立的程序计数器,因此程序计数器是线程私有的内存区域。

    如果线程正在执行的是一个java方法,则计数器记录的是正在执行的虚拟机字节码执行的地址,如果正在执行的是Native方法,则计数器为空(Undefined)。

2.2、Java虚拟机栈

    与程序计数器一样,java虚拟机栈也是线程私有的,他的生命周期于线程相同。虚拟机栈主要描述的java方法执行的内存模型,每个方法被执行的时候会同时创建一个栈帧用于存储局部表量表、操作栈、动态链接、方法出口等信息。方法被调用时就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

     1、局部表量表:是存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型)和returnAddress类型。

注意:其中64位长度的long和double数据类型会占用2个局部变量空间,其余的数据类型只占用1个。

            a、reference类型:他不等于对象本身,根据不同的虚拟机实现,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置。

            b、returnAddress类型:指向了一条字节码指令的地址。

2.3、本地方法栈

    本地方法栈和虚拟机栈的作用是非常相似的,区别不过是虚拟机执行java方法服务,也就是字节码服务,而本地方法栈是为虚拟机使用的Native方法服务。

2.4、java堆

    java堆是java虚拟机所管理的内存中最大的一块。是被所有线程共享的内存区域,在虚拟机启动时创建。存放的是对象实例,几乎所有的对象实例都在这里分配内存。

    java堆是垃圾收集器管理的主要区域,因此很多时候也称为“GC堆”。

    java堆细分为:新生代和老年代,再细致一点有Eden空间、From Survivor空间、To Survivor空间。

2.5、方法区

    方法区与java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

2.6、运行时常量池

     是方法去的一部分。Class文件中除了有类的版本、字段、方法、接口等信息外,还有一项信息就是常量池,用于存放编译期生成的各种字面量和符号引用,这些内容都会放在类加载后存放到方法区的运行时常量池中。

注意:运行时常量池相对于Class文件常量池的另一个重要特征就是具备动态性,java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中的常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。

2.7、直接内存

     直接内存并不是虚拟机运行时数据区的一部分,也不是java虚拟机规则中定义的内存区域,但是这部分内存也是被频繁使用的。

3、对象访问  

       在java语言中,对象访问是如何进行的?对象访问也会涉及java栈、java堆、方法区这三个重要的内存区域。列如:

Object object = new Object();

      假如这行代码出现在方法体中,“Object object”这部分的语义会反映到java栈的本地变量表中,作为一个reference类型数据出现。而“new Object()”的语义会反映到java堆中,形成一块存储了object类型所有实例数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定的。

       对象访问的两种方式:句柄和直接指针。

       使用句柄方式:Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。

        使用直接指针方式:java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。

二、内存溢出异常

 1、Java堆溢出

       java堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常。

 2、虚拟机栈和本地方法栈溢出

      在HotSpot虚拟机中并不是区分虚拟机栈和本地方法栈,因此对于HotSpot来说,-Xoss参数虽然存在,但实际上是无效的,栈容量只有-Xss参数设定。在java虚拟机规范中描述了两种异常:

      a、如果线程请求的长深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;

      b、如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。 

注:在单线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。     

3、运行时常量池溢出

    要向运行时常量池中添加内容,最简单的做法是使用String.intern()这个Native方法。作用:如果池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加但常量池中,并且返回此String对象的应用。由于常量池分配在方法区内,可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而限制其中常量池的容量。

4、方法区溢出

     方法区用于存放Class的相关信息,方法区溢出一般是运行时产生大量的类去填满方法区,就会出现溢出。

     方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾回收器回收掉,判断条件是非常苛刻的。即使是同一个类文件,被不同的加载器加载时也会视为不同的类。

5、本机直接内存溢出

      DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与java堆的最大值(-Xmx)一样。列如:使用DirectByteBuffer分配内存也会抛出内存溢出异常,但他抛出异常时并没有真正的向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory()。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java虚拟机主要分为以下几个内存区域: 1. 程序计数器(Program Counter Register) 2. Java虚拟机栈(Java Virtual Machine Stacks) 3. 本地方法栈(Native Method Stacks) 4. Java堆(Java Heap) 5. 方法区(Method Area) 6. 运行时常量池(Runtime Constant Pool) 7. 直接内存(Direct Memory) 其中,程序计数器、Java虚拟机栈、本地方法栈都是线程私有的内存区域Java堆、方法区、运行时常量池、直接内存则是线程共享的内存区域。 ### 回答2: Java虚拟机(JVM)中有几个重要的内存区域,它们分别是堆、栈、方法区、程序计数器和本地方法栈。 堆是Java程序最主要的内存区域之一,用于存储对象实例和数组。所有通过关键字new创建的对象都会在堆上分配内存。堆是被所有线程共享的内存区域,每个对象的实例变量在堆中占用一定的空间。 栈是一个线程私有的内存区域,用于存储线程的方法调用。每个线程在执行方法时都会在栈中创建一个栈帧,栈帧包含方法的参数、局部变量和返回值等信息。栈的操作是后进先出的,所以栈也被称为LIFO(Last In First Out)数据结构。 方法区(JDK 8及以前版本称为“永久代”)用于存储类的元数据信息,如类名、方法名、字段名和运行时常量池等。方法区也是被所有线程共享的内存区域。 程序计数器是一块较小的内存区域,它用来指示线程当前执行的字节码指令地址。每个线程都有一个独立的程序计数器,因此它是线程私有的。 本地方法栈与栈类似,但用于执行本地方法调用(Native Method)。本地方法是用C或C++等本地语言编写的方法,它们可以直接在系统级别访问硬件设备或其他资源。本地方法栈也是线程私有的。 总之,Java虚拟机内存区域包括堆、栈、方法区、程序计数器和本地方法栈。这些内存区域各有不同的作用,用于支持Java程序的执行和对象的管理。 ### 回答3: 在Java虚拟机中,主要有以下几个内存区域: 1. 程序计数器(Program Counter Register): 程序计数器是一块较小的内存区域,它保存着当前线程正在执行的字节码指令的地址。每个线程都有自己独立的程序计数器。 2. Java栈(Java Stack): Java栈用于存储Java方法的局部变量、参数、方法返回值以及一些操作数。每个线程的方法在执行时都会创建一个对应的栈帧,栈帧中存放了方法的信息。 3. 本地方法栈(Native Method Stack): 本地方法栈与Java栈类似,但它是为Native方法服务的。Native方法是使用其他语言(如C、C++)编写的方法,本地方法栈用于保存这些方法的本地变量。 4. Java堆(Java Heap): Java堆是Java虚拟机所管理的内存中最大的一块区域,被所有线程共享。Java堆用于存储对象实例和数组,是垃圾回收的主要区域Java堆可以分为新生代和老年代两个区域。 5. 方法区(Method Area): 方法区用于存储已被加载的类信息、常量、静态变量等数据。在方法区中还有一个叫做运行时常量池的区域,它存放着每个类的运行时常量信息。 6. 运行时常量池(Runtime Constant Pool): 运行时常量池是方法区中的一部分,用于保存编译器生成的各种字面量和符号引用。 除了以上几个内存区域Java虚拟机还包括了直接内存(Direct Memory)。直接内存并不是Java内存区域的一部分,它使用的是操作系统的内存,但可以被Java虚拟机直接访问,通常用于NIO(New IO)操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值