Java虚拟机管理的内存区域包括:堆(Heap),栈(Stack)(本地方法栈和虚拟机栈),方法区,程序计数器
程序计数器
程序计数器:当前线程执行的字节码行号指示器,字节码解释器通过改变计数器的值来选取下一条需要执行的字节码指令。每个线程都需要一个程序计数器,各条线程之间的程序计数器互不影响。程序计数器是线程私有的。
如果线程正在执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行Native方法,这个计数器值为空(undefined)。此内存区域是唯一一个在Java虚拟机中没有规定任何OutOfMemoryError情况的区域。
栈(本地方法栈和虚拟机栈)
本地方法栈:执行Java虚拟机的本地方法(Native Method).
本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常。
虚拟机栈:它是线程私有的,生命周期和线程相同。它描述了Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个帧栈用于方法调用局部变量表,操作栈,动态链接,方法出口等信息。每一个方法的调用过程就是一个栈帧在虚拟机栈中从入栈到出栈的过程。
保存Java方法运行过程中的帧栈信息,包括方法调用的局部变量表,操作栈,动态链接以及方法入口等信息。
局部变量表存放了编译器可知的各种基本数据类型(boolean, byte, char, short, int, float, long, double)、对象引用。局部变量表所需内存空间在编译期完成分配,在方法运行期间不会改变局部变量表的大小。
Java虚拟机规范中对Java虚拟机栈规定了两中异常:
(1)如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;
(2)如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时将抛出OutOfMemoryError异常。
Java堆(Heap)
Java Heap是Java虚拟机所管理的内存中最大的一块,被所有线程共享,在虚拟机启动时创建,存放对象的实例。Java堆是垃圾收集器管理的主要区域。(GC Garbage Collected Heap)
Java堆分为新生代和老生代,再细分可以分为Eden空间, From Survivor空间, To Survivor空间。
Java堆可以处于物理上不连续的内存空间,只要逻辑上是连续的即可。
主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法在扩展,将会派出OutOfMemoryError异常。
方法区(Method Area, 永久带)
方法区是多个线程共享内存的区域,用于存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。垃圾收集行为很少在这个区域出现,该区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
当方法区无法满足内存分配需求时,讲抛出OutOfMemoryError异常。
方法区在HotSpot中也称为永久代(Permanent Generation)
运行时常量池(Runtime Constant Pool):运行时常量是方法区的一部分,常量池用来存放编译期生成的各种字面量和符号引用,它们将会在类加载后存放到方法去的运行时常量池中。
运行期间也可以将新常量放入池中。这种特性被开发人员用的比较多的就是String类的intern()方法。
当常量池无法再申请到内存时抛出OutOfMemoryError异常。
直接内存(Direct Memory):本地直接内存不会受到Java堆的限制,但是会出现OutOfMemoryError的错误。
=====================================================================
对象访问
Object obj = new Object();
Object obj将会反映到Java栈的本地变量中,作为一个reference类型数据出现。
而new Object()将会反映到Java堆中,形成一块存储了Object类型所有势力数据值的结构化内存,根据具体类型以及虚拟机实现的对象内存布局的不同,这块内存的长度是不固定的。
主流的对方访问方式有两种:使用句柄和直接指针
(1)如果使用句柄方式访问,Java堆中将会划分出一块内存来作为句柄池,reference中存储的对象就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
(2)直接指针方式访问,reference中直接存储的就是对象地址。
两者的区别:
1、使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,而对象被移动时值会改变句柄中的实例数据指针,而reference本身不需要被改变。
2、使用直接指针最大的好处是速度更快,它节省了一次指针定位的时间开销。
Sun Hotspot使用第二种方式进行对象访问。
=========================================================================
实战:OutOfMemoryError异常
在JVM规范中,除了程序计数器外,虚拟机内存的其他几个运行时区域都会发生OutofMemoryError异常(OOM)。
Java堆溢出
Java堆用于存储对象实例,我们只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆的容量限制后产生内存溢出异常。
不可扩展:将堆的最小值-Xms参数和最大值-Xmx参数设置为一样即可避免对自动扩展;
通过参数-XX:+HeapDumpOnOutOfMemoryError可让虚拟机在出现内存溢出时候Dump出当前的内存堆转储快照以便分析。
Java堆内存的异常是最常见的异常情况。
java.lang.OutOfMemoryError,提示Java heap space
解决方法:
(1)确认内存中的对象是否有存在的必要,分清楚是内存泄露(Memory Leak)还是内存溢出(Memory Overflow);
(2)如果是内存泄露,通过工具查看泄露对象到GC Roots的引用链。找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收他们。
(3)如果内存溢出,就应当检查虚拟机的堆参数(-Xms和-Xmx),与机器物理内存对比看是否还可以调大。
从代码上检查是否存在某些对象生命周期过长,持有状态时间过长的情况,尝试减少程序运行期间的内存消耗。
内存泄露主要是由于垃圾对象没有回收,而内存溢出主要是由于对象声明周期过长,消耗内存过大,虚拟机需要的堆内存大于可用内存。
虚拟机栈和本地方法栈溢出
在HotSpot虚拟机中不区分虚拟机栈和本地方法栈,所以对于HotSpot来说,-Xoss参数(设置本地方法栈大小)虽然存在,但是实际上是无效的。栈容量只由-Xss参数设定。
关于虚拟机栈和本地方法栈,JVM规范中描述了2种异常:
(1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常;
(2)如果虚拟机在扩展栈时无法申请足够的内存空间,则抛出OutOfMemoryError异常。
方法区溢出
方法区用于存放Class的相关信息,如类名,修饰符,常量池,字段描述和方法描述等。
对这个区域的测试,基本思路是运行时产生大量的类去填满方法区,直到溢出。
借助CGLib直接操作字节码运行时,生成了大量的动态类。SSH框架中对类进行增强时,都会使用到CGLib等字节码增强技术,增强的类越多,就需要越大的方法去来保证动态生成的Class可以加载入内存。
运行时常量溢出
向运行时常量池中添加内容,最简单的方法使用 String.intern()这个本地方法。
该方法的作用:如果池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;
否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
由于常量池分配在方法区内,通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。
运行时候常量池溢出,在OutOfMemoryError后面跟随的提示信息是“PermGen space”,说明运行时常量池属于方法去的一部分。
程序计数器,虚拟机栈,本地方法栈这三个区域和线程的生命周期相同。
每一个栈帧中分配多少内存基本上在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具有确定性,不需要过滤考虑回收问题,因为方法结束或线程结束,内存自然就被回收了。
而Java堆和方法区的内存则不一样,一个接口中的多个实现可能需要的内存不一样,这部分的内存分配和回收是动态的。
==========================================================================
小结:
1、JVM的内存分配结构图;
2、堆,栈(本地方法栈和虚拟机栈),方法区,程序计数器分别存储的内容信息
3、内存溢出和内存泄露的区别?如何处理内存溢出与内存泄露
4、堆,栈,方法区,程序计数器可能出现的异常处理
相关推荐
详细的介绍了JVM内存结构和JVM的6大区域
详细介绍了JVM 内存管理相关知识 内存空间( VM运行时数据区域) ◦ 内存结构 ◦ 内存空间 内存分配 内存回收(GC) 内存分析工具
2019最新深入理解JVM内存结构及运行原理(JVM调优)高级核心课程视频教程下载。JVM是Java知识体系中的重要部分,对JVM底层的了解是每一位Java程序员深入Java技术领域的重要因素。本课程试图通过简单易懂的方式,系统...
下面我们从每个区域的用途,涉及的问题等方面来简单的说一说JVM的内存结构。 方法区 作用:用于存放已被加载的类信息、常量、静态变量、即时编译器(JIT)编译后的代码等数据。 所有线程共享方法区。 方法区内存可以...
类加载 、 类文件结构 、 垃圾回收 、 执行 引擎 、 性能调优 、 监控 等等这些知识,但所有的功能都是围绕着 内存结构 展开的,因为我们编译后的代码信息在运行过程中都是存在于JVM自身的内存区域中的...
JVM内存结构Java 代码是要运行在虚拟机上的,而虚拟机在执行 Java 程序的过程中会把所管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途。如果
其实对于我们一般理解的计算机内存,它算是CPU与计算机打交道频繁的区域,所有数据都是先经过硬盘至... JVM内存的分配结构示意图 下面将逐一介绍下各个区域所做的工作及其充当的功能。 PC Register(PC寄存器)
JVM内存结构:JVM的内存结构主要包括堆内存、方法区、栈(包括Java虚拟机栈和本地方法栈)、程序计数器等。堆内存是所有线程共享的内存区域,用于存放对象实例;方法区保存已加载的类信息、常量、静态变量和编译后的...
1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范 2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能 3、不同
1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范 2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能 3、不同
Java 内存区域, 垃圾收集, 内存分配与回收策略, JVM 调优, 文件结构, 类加载机制, Java 程序 Java是一种面向对象的编程语言,由Sun Microsystems于1995年推出。它是一种跨平台的语言,意味着可以在不同的操作...
第28节Java内存区域-直接内存和运行时常量池00:15:53分钟 | 第29节对象在内存中的布局-对象的创建00:21:19分钟 | 第30节探究对象的结构00:13:47分钟 | 第31节深入理解对象的访问定位00:08:01分钟 | 第32节垃圾...
java内存结构分析java内存结构java栈结构分析:栈帧局部变量表操作数栈动态连接返回地址运行时常量池对象的创建过程类加载的执行流程图对象创建的过程:对象内存分配方式指针碰撞空闲列表栈上分配:内存逃逸:对象...
常量池静态常量池即*.class文件中的常量池,用于存放字面量和符号引用运行时常量池是jvm运行期间,存储常量的数据结构运行时常量池概念运行时常量池(Runti
用于学习JVM基础,仅参考,结构图来源其他地方(忘记) (xmind中的备注内容为查找资料,直接保存的,可以参考来源链接)
JVM 内存区域 线程私有 程序计数器 当前线程所执行的字节码的行号指示器 对于 Java 方法,记录正在执行的虚拟机字节码指令的地址;对于 native 方法,记录值为空 (Undefined) 唯一一个Java 虚拟机规范中没有规定...
JVM,虚拟机结构,java,内存结构
SQL优化 3 数据库优化 6 DB&SQL优化 7 索引 8 分库分表分区 8 数据库引擎 9 ...JVM内存结构 19 内存结构 19 Java堆 20 JVM GC过程 20 GC执行机制(回收器) 21 JVM判断对象是否可以被回收算法等等。
JVM堆:所有线程共享的运行时内存区域,是GC回收的主场所,Java堆保存Java的实例对象,从内存回收对象的存活来分析,堆又可以分为新生代、老年代。 方法区:方法区是线程共享的运行时内存区域,存储虚拟机加载的类的...
第2章 Java内存区域与内存溢出异常 / 24 2.1 概述 / 24 2.2 运行时数据区域 / 25 2.2.1 程序计数器 / 25 2.2.2 Java虚拟机栈 / 26 2.2.3 本地方法栈 / 27 2.2.4 Java堆 / 27 2.2.5 方法区 / 28 2.2.6 运行...