精华内容
参与话题
问答
  • Java内存区域

    2017-12-10 12:37:36
    Java内存区域

    JVM根据执行Java程序的过程中把它所管理的内存划分为若干个不同的数据区域。


    1、程序计数器(Program Counter Register,PCR)

    这是一块较小的内存空间,在JVM概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程回复等基础功能都需要依赖这个计数器来完成。

    JVM的多线程是通过线程轮流切换并且分配处理器执行时间来实现的(并发,concurrent),因此为了线程切换之后能够恢复到正确的执行位置,每条线程都需要一个独立的PCR,各条线程之间计数器互不影响,独立存储。

    如果线程正在执行一个Java方法,则这个PCR记录的是正在执行的JVM字节码指令的地址;如果是native方法,这个计数器值为空(Undefined)。

    这个内存区域是JVM规范中唯一一个没有规定任何OutOfMemoryError的区域。


    2、Java虚拟机栈(Java Virtual Machine Stacks)

    Java虚拟机栈也是线程私有的,生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时会产生一个栈帧(Stack Frame,方法运行时的基础数据结构)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

    经常有人把Java内存区分为堆空间(Heap)和栈空间(Stack),这种分法太过粗糙,JMM的划分远比这复杂。

    局部变量表存放了编译期可知的各种基本数据类型和对象引用(reference类型,不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能时指向一个代表对象句柄或者其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。

    long和double类型的数据占用两个局部变量空间(Slot),其余的数据类型只占用一个。局部变量表所需要的内存空间在编译期间完成分配。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变这个局部变量表的大小。

    这个区域规定了两种异常状况:

    如果线程请求的栈深度大于虚拟据所允许的深度,将抛出StackOverflowError异常;

    如果虚拟机无法申请到足够的内存时,会抛出OutOfMemoryError异常。


    3、本地方法栈(Native Method Stack)

    本地方法栈与虚拟机栈所发挥的作用是十分相似的,也是线程私有的,区别在于虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。有的虚拟机甚至将NMS和JVMS合在一起。

    和JVMS一样,NMS会抛出StackOverflowError和OutOfMemoryError异常。


    4、Java堆(Java Heap)

    一般来说这是JVM管理的内存中最大的一块,是所有线程都共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

    JVM规范原话是:The heap is the runtime data area from whick memory for all class instances and arrays is allocated.(所有对象实例和数组都要在堆上分配)。//虽然随着技术的发展,这句话不那么绝对了

    Java堆(由于是GC的主要管理区域,也被称为GC堆)从内存回收的角度来看,由于现在GC基本采用分代收集算法,所以Java堆可以细分为新生代和老年代。再细致可以分为Eden空间、From Survivor空间、To Survivor空间等。

    从内存分配角度来看,线程共享的Java堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。

    根据JVM规范,java堆可以处于物理上不连续的内存空间中,只要逻辑上连续即可。当前的JVM基本都可以通过-Xmx和-Xms控制Java堆的大小。

    当堆中没有内存完成实例分配,并且堆无法再扩展时,就会抛出OutOfMemoryError。


    5、方法区(Method Area)

    方法区和Java堆一样,是各个线程共享的内存区域,用于存储以已经被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然JVM把方法区描述为堆的一个逻辑部分,但是它又一个别名叫做Non-Heap(非堆)方便和Java堆区分。

    JVM规范对方法区的限制十分宽松,不需要连续的内存并且可以固定大小或者可扩展,还可以不实现GC。

    GC在这个区域很少出现,但是不意味着数据进入这个区域就能永久存在,在这个区域的垃圾回收仍然十分重要,否则会出现内存泄漏。

    当方法区无法满足内存分配需求的时候,将抛出OutOfMemoryError异常。


    6、运行时常量池(Runtime Constant Pool)

    这是方法区的一部分,也是线程共享的区域,Class文件中除了有类的版本、字段、方法、接口等描述信息之外,还有一项常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,在类加载后进入方法区的运行时常量池中存放

    运行时常量池相对于Class文件常量池的另一个重要特征就是具有动态性,Java语言不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法去运行时常量池,运行期间也能将新的常量放入池中,比如String类的intern()方法(本质上String类型是常量)。

    由于运行时常量池是方法区的一部分,所以当无法再申请到内存时会抛出OutOfMemoryError异常。


    7、直接内存(Direct Memory)

    这不是JVM运行时数据区的一部分,也不是JVM规范中定义的内存区域,但是这部分内存也会被频繁使用,而且也在无法申请到内存的时候抛出OutOfMemoryError异常。

    在jdk 1.4之后加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,可以使用Native库函数直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,避免在Java堆和Native堆中来回复制数据。

    展开全文
  • JAVA内存区域

    2017-02-23 16:26:39
    1. Java内存区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,分别是:方法区,虚拟机栈,本地方法栈,堆,程序计数器。 **程序计数器:**是一块较小的内存空间,它的作用可以...

    1.内存区域
    Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,分别是:方法区,虚拟机栈,本地方法栈,堆,程序计数器。
    程序计数器:是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖计数器。每个线程都需要独立的计数器,跟线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。如果线程执行的是一个Java方法,计数器记录的是虚拟机字节码指令的地址,如果执行的是一个native方法,则计数器的值为空。计数器内存区域是唯一一个Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
    Java虚拟机栈:它是线程私有的,生命周期和线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表(存放了编译期可知的各种基本数据类型,对象引用和returnAddress类型(指向了一天字节码指令的地址)),操作数栈,动态链接,方法出口等信息,每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。这个区域规定了两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存是会抛出OutOfMemoryError异常。
    本地方法栈:它与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法服务,为本地方法栈则为虚拟机用到的Native方法服务。
    **Java堆:**JAVA堆是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。所有的对象实例以及数组都要在堆上分配。Java堆中还可以细分为:新生代和老年代。Java堆可以处于物理上不连续的内存空间,如果堆中没有内存完成实例分配,并且堆也无法扩展是,将会抛出OutOfMemoryError异常。
    方法区:方法区和堆一样,也是线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。此区也被称为“永久代”。
    运行时常量池:它是方法区的一部分,Class文件除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
    2.对象访问
    由于reference类型在Java虚拟机规范里面只规定了一个指向对象的引用,并没有定义这个引用应该通过哪种方法去定位。主流的访问方法有两种:使用句柄和使用指针
    如果使用句柄访问方式,Java堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息。
    如果使用直接指针访问方式,Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,reference中直接存储的就是对象地址。
    使用句柄方式最大的好处就是reference中存储是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据地址,而不会改变reference。使用指针方式最大的好处就是访问速度快。

    展开全文
  • Java 内存区域

    2016-03-03 16:58:14
    Java内存区域  在Java运行时的数据区里,由JVM管理的内存区域分为几个模块: 1,程序计数器(Program Counter Register): 程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几...

    Java内存区域

      在Java运行时的数据区里,由JVM管理的内存区域分为几个模块:

    1,程序计数器(Program Counter Register):

    程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。

      每个程序计数器只用来记录一个线程的行号,所以它是线程私有(一个线程就有一个程序计数器)的。

      如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native,由C语言编写 完成)方法,则计数器的值为Undefined,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区 域中唯一一个没有定义OutOfMemoryError的区域。

    2,虚拟机栈(JVM Stack):

    一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。

      局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占 用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,局部变量表是在编译时就已经确定 好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。

      虚拟机栈中定义了两种异常,如果线程调用的栈深度大于虚拟机允许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多 数Java虚拟机都允许动态扩展虚拟机栈的大小(有少部分是固定长度的),所以线程可以一直申请栈,知道内存不足,此时,会抛出 OutOfMemoryError(内存溢出)。

      每个线程对应着一个虚拟机栈,因此虚拟机栈也是线程私有的。

    3,本地方法栈(Native Method Statck):

    本地方法栈在作用,运行机制,异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。

      本地方法栈也是线程私有的。

    4,堆区(Heap):

    堆区是理解Java GC机制最重要的区域,没有之一。在JVM所管理的内存中,堆区是最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。

      一般的,根据Java虚拟机规范规定,堆内存需要在逻辑上是连续的(在物理上不需要),在实现时,可以是固定大小的,也可以是可扩展的,目前主 流的虚拟机都是可扩展的。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java heap space异常。

      关于堆区的内容还有很多,将在下节“Java内存分配机制”中详细介绍。

    5,方法区(Method Area):

    在Java虚拟机规范中,将方法区作为堆的一个逻辑部分来对待,但事实 上,方法区并不是堆(Non-Heap);另外,不少人的博客中,将Java GC的分代收集机制分为3个代:青年代,老年代,永久代,这些作者将方法区定义为“永久代”,这是因为,对于之前的HotSpot Java虚拟机的实现方式中,将分代收集的思想扩展到了方法区,并将方法区设计成了永久代。不过,除HotSpot之外的多数虚拟机,并不将方法区当做永 久代,HotSpot本身,也计划取消永久代。本文中,由于笔者主要使用Oracle JDK6.0,因此仍将使用永久代一词。

      方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。

      方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上 执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对 常量池的内存回收和对已加载类的卸载。

      在方法区上进行垃圾收集,条件苛刻而且相当困难,效果也不令人满意,所以一般不做太多考虑,可以留作以后进一步深入研究时使用。

      在方法区上定义了OutOfMemoryError:PermGen space异常,在内存不足时抛出。

      运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译;运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。

    6,直接内存(Direct Memory)

    直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是 JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存,JDK中有一种基于通道(Channel)和缓冲区 (Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。 由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。


    展开全文
  • java内存区域

    2015-11-22 17:48:23
    Java内存区域 我写这个文章只是对学习的一些记录加上自己的理解,所以很简陋,详细可参考周志明的《深入理解Java虚拟机》或者《Java虚拟机规范(Java SE 7版)》的中译本。运行时的数据区域深入理解Java虚拟机中的...

    Java内存区域

      我写这个文章只是对学习的一些记录加上自己的理解,所以很简陋,详细可参考周志明的《深入理解Java虚拟机》或者《Java虚拟机规范(Java SE 7版)》的中译本。

    运行时的数据区域

    深入理解Java虚拟机中的图画得不是特别详细,所以我在网上找了稍微详细一点的!这里写图片描述

    针对这个图我会详细说明程序技术器,Java虚拟机栈,本地方法栈,Java堆,方法区,运行时常量池,还有一个直接内存(图上没有)。

    程序技术器-唯一一个没有OOM的区域

      程序计数器(Program Counter Register),又叫PC寄存器。从图上来看这是线程私有的。如果执行的是java方法,保存的是当前线程正在执行的字节码指令地址;如果正在执行Native,这个计数器为空(Undefined)。

      由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。

      在程序中,分支,循环,跳转,异常处理,线程恢复都需要这个计数器。

    Java虚拟机栈

      从图中看,java虚拟机栈也是线程私有的。
      栈帧:方法运行时的基础数据结构,对应当前每一个没调用的方法,方法被调用的时候就会被压入栈底,也就是说当前执行的任务在栈顶,这也就很好的反应了递归中为什么会出现内存溢出,当递归不结束的时候,或者太长的时候,线程请求的栈的深度就会大于当前虚拟机栈的深度,内存溢出(OOM).

      局部变量表:存储该栈帧对应方法中的局部变量(包括了方法中的申明的非静态变量以及函数形参)。对于八大基本数据类型的变量,直接存储。对于对象引用,存储的是引用。这里注意64位长度的long和double占用两个局部变量空间,其他只有一个。空间大小在编译时确定。

      操作数栈:就和大家学习栈中的表达式求值一样,在方法中所有的计算都是借助于操作数栈来完成的。

      当然除了这些比较重要的以外还有,指向运行时常量池的引用,方法返回地址(当一个方法执行完毕后,要返回之前调用它的地方,因此栈帧中必须保存一个方法返回地址)等等

    本地方法栈

      本地方法栈和java虚拟机栈所发挥的作用是非常相似的。区别是java虚拟机栈为执行Java方法而服务,本地方法为Native方法服务。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

    Java堆

      java堆和C,C++不同。在C中堆是可以由程序员管理的(申请和释放)。

      但在Java中就不行,程序员基本不用关心空间释放的问题,java的垃圾回收机制会自动处理。

      在java虚拟机规范中说:所有的对象实例以及数组都要在堆上分配。但随着JIT编译器的发展与逃逸分析技术逐渐成熟,发生了一些变化,其实也不是那么绝对了。堆被所有线程共享,JVM中只有一个堆。

    方法区

      方法区和java堆一样,是每个线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量等数据,别名叫Non-Heap(非堆),目的是把Java堆区分开。

      在JVM规范中,没有强制要求方法区必须实现垃圾回收。很多人习惯将方法区称为“永久代”,是因为HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将运行时常量池从永久代移除了。

    String str1=new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern() ==str1);
    String str2=new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern()==str2);
    

      
    这两个的输出对jdk1.6和1.7不同。在1.6中两个都是false,在1.6中,intern方法会把首次遇到的字符串实例复制到永久代中,StringBuilder中是在java堆上的,两个引用必然不同,将返回false。JDK1.7的结果一个是true,另一个是false。JDK1.7的intern()实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此str1是true,但是str2这里有个问题首次出现,“java”这个特殊字符串是在之前已经出现过他了,字符串常量池中已经有”java”这个引用,和str2指向堆的引用不同,返回false。

    直接内存

      直接内存并不是虚拟机运行时数据去的一部分,也不是javav虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OOM异常。

      在jdk1.4中新加入了NIO类,引入了一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用来操作,避免了在java堆和Native堆来回复制数据

      直接内存不会受到java堆的大小限制,但是会受到机器的限制。

    展开全文

空空如也

1 2 3 4 5 ... 20
收藏数 22,048
精华内容 8,819
关键字:

java内存区域

java 订阅