精华内容
下载资源
问答
  • jvm内存划分

    2019-09-12 09:54:54
    1,jvm中的空间根据不同的功能进行划分部分:栈内存,堆内存,方法,本地方法栈,程序计数器。 2,栈内存:用于执行方法的内存,每方法在执行的时候,都需要一段独立的空间,这段空间就在栈内存中给分配的。...

    1,jvm中的空间根据不同的功能进行划分为五部分:栈内存,堆内存,方法区,本地方法栈,程序计数器。
    2,栈内存:用于执行方法的内存,每个方法在执行的时候,都需要一段独立的空间,这段空间就在栈内存中给分配的。每个方法分配的内存称为栈帧。特点:先进后出。
    3,堆内存:用于存储数组,对象等引用数据类型的数据。空间较大,但速度相对慢。
    4,方法区:用于存储类的字节码对象,存储常量,存储静态变量。
    5,本地方法栈:用于执行本地方法,使用c语言c++写好的方法。
    6,程序计数器:控制程序的执行。

    展开全文
  • C++内存地址分配和内存区划分简介

    千次阅读 2018-02-07 11:58:49
    C++内存地址分配和内存区划分简介 原文地址:http://blog.csdn.net/liuhuiyi/article/details/7530137 内存类型简介 内核:在一些系统中,当系统调用发生时,操作系统或者操作系统内核会编程应用程序内存的一...

    C++内存地址分配和内存区划分简介

    原文地址:http://blog.csdn.net/liuhuiyi/article/details/7530137

    内存类型简介

    这里写图片描述
    内核:在一些系统中,当系统调用发生时,操作系统或者操作系统内核会编程应用程序内存的一部分。
    栈:栈中包含活动记录,其中包含当前活动函数调用的返回地址和局部变量等信息。
    共享库:为了动态链接共享库文件而创建的一个内存片段
    堆内存:被用作堆内存来使用和分配的一块内存空间。如果运行时需要一些可变大小的小内存块,那么这些内存就是从这个区域中分配的
    未初始化的数据: 没有初始化的全局变量被放在固定地址中。通常,这段区域都会被初始化为0。
    初始化的数据: 任何被赋予了初始值的数据都被组织在内存中的一段连续的区域内,因此他们就能够与程序页一同被有效地载入
    程序页:构成应用程序的机器代码指令就是程序页。当有硬件支持时,程序页通常是只读的,这样就可以防止程序意外的重写其自身指令区域。
    第0页:通常,一个以地址0为开始的内存片段会被保留下来被设置为不可读区域,他可以用来捕捉一种常见的错误,这种错误就是使用一个NULL指针访问数据。

    第一部分 C++内存地址分配简介

    1 内存地址是从高地址到低地址进行分配的:

    int i=1;  
    int j=1;  
    cout<<&i<<endl<<&j<<endl;   //输出:0012FF60(高地址处) 0012FF54(低地址处)  

    2 函数参数列表的存放方式是,先对最右边的形参分配地址,后对最左边的形参分配地址。

    3 Little-endian模式的CPU对操作数的存放方式是从低字节到高字节的

    0x1234的存放方式入下:

    0X4000 0x34

    0X4001 0x12

    4 Big-endian模式的CPU对操作数的存放方式是从高字节到低字节的

    0x1234的存放方式入下:

    0x4000 0x12

    0x4001 0x34

    (关于这两种模式的更多解释:https://www.cnblogs.com/passingcloudss/archive/2011/05/03/2035273.html

    5 联合体union的存放顺序是所有成员都从低地址开始存放。

    6 一个变量的地址是由它所占内存空间中的最低位地址表示的。

    0X4000 0x34

    0X4001 0x12

    0x1234 的地址位0x4000

    7 堆栈的分配方式是从高内存地址向低内存地址分配的。

    int ivar=0;

    int iarray[2]={11, 22};

    注意iarray[2]越界使用,比如对其赋值

    iarray[2]=0;

    那么则同时对ivar赋值为0,可能产生死循环,因为它们的地址相同,即&ivar等于&iarray[2]。

    第二部分 C/C++内存区划分

    一 在C中分为这几个存储区
    1.栈 由编译器自动分配释放;
    2.堆 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收;
    3.全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束释放;
    4.另外还有一个专门放常量的地方。 程序结束释放。

    在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的”adgfdf”这样的字符串存放在常量区。比如:

    int  a = 0; //全局初始化区  
    char *p1; //全局未初始化区  
    void main()  
    {       
    int b;                   //栈       
    char s[] = "abc"; //栈       
    char *p2;         //栈       
    char *p3 = "123456";       //123456{post.content}在常量区,p3在栈上       
    static int c = 0;          //全局(静态)初始化区       
    p1 = (char *)malloc(10);   //分配得来得10字节的区域在堆区       
    p2 = (char *)malloc(20);   //分配得来得20字节的区域在堆区       
    strcpy(p1, "123456");      
    //123456{post.content}放在常量区,编译器可能会将它与p3所指向的"123456"优化成一块  
    }  

    二.在C++中,内存分成5个区

    他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区
    1.栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
    2.堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
    3.自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
    4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
    5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改)。
    在bbs上,堆与栈的区分问题,似乎是一个永恒的话题。   
    首先,我们举一个例子:

    void f() 
    {
        int* p=new int[5];
    } 

    这条短短的一句话就包含了堆与栈,看到new,我们首先就应该想到,我们分配了一块堆内存,那么指针p呢?它分配的是一块栈内存,所以这句话的意思就是:在栈内存中存放了一个指向一块堆内存的指针p。

    在程序会先确定在堆中分配内存的大小,然后调用operator new分配内存,然后返回这块内存的首地址,放入栈中,他在VC6下的汇编代码如下:

    00401028 push 14h 
    0040102A call operator new (00401060) 
    0040102F add esp,4 
    00401032 mov dword ptr [ebp-8],eax `这里写代码片`
    00401035 mov eax,dword ptr [ebp-8] 
    00401038 mov dword ptr [ebp-4],eax 

      这里,我们为了简单并没有释放内存,那么该怎么去释放呢?是delete p么?错了,应该是delete []p,这是为了告诉编译器:我删除的是一个数组,VC6就会根据相应的Cookie信息去进行释放内存的工作。

      好了,我们回到我们的主题:堆和栈究竟有什么区别?

      主要的区别由以下几点:

      1、管理方式不同;

      2、空间大小不同;

      3、能否产生碎片不同;

      4、生长方向不同;

      5、分配方式不同;

      6、分配效率不同;

      管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。

      空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:

      打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。

      注意:Reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

      碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在它弹出之前,在它上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。

      生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

      分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,不需要我们手工实现。

      分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高(我的注释:关于EBP寄存器请参考另一篇文章)。

        堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法
        (具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间
        (可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,
        然后进行返回。显然,堆的效率比栈要低得多。
    

      从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。

      虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。

      无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生意想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:)

    展开全文
  • JVM内存划分

    万次阅读 多人点赞 2018-09-06 15:33:43
    目录   1. 概述 2. 运行时数据区域  2.1 程序计数器 2.2 Java虚拟机栈 2.3 本地方法栈 ...2.5 方法 ...3.1 内存划分 3.2 对象的创建、内存布局、访问定位 3.2.1 对象的创建 3.2.2 对象的内存布局...

    目录

     

    1. 概述

    2. 运行时数据区域

     2.1 程序计数器

    2.2 Java虚拟机栈

    2.3 本地方法栈

    2.4 Java堆

    2.5 方法区

    2.6 补充

    2.6.1 运行时常量池和Class文件常量池

    2.6.2 直接内存

    3. HotSpot虚拟机

    3.1 内存划分

    3.2 对象的创建、内存布局、访问定位

    3.2.1 对象的创建

    3.2.2 对象的内存布局

    3.2.3 对象的访问定位

    4. OOM

    4.1 堆OOM

    4.2 虚拟机栈OOM

    4.3 元空间OOM

    4.4 直接内存OOM

    x. 参考资料


    1. 概述

    对于Java程序员,在虚拟机自动内存管理机制的帮助下,不再需要为每个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题。

    2. 运行时数据区域

    Java虚拟机在执行Java程序的过程中会把它管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(Java SE 7版)》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域:程序计数器,Java虚拟机栈,本地方法栈,Java堆,方法区。

    下图为各个区域以及进一步细化图:

     2.1 程序计数器

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

    由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,每个线程之间计数器互不影响,独立存储。

    备注:

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

    (b)此内存区域是唯一在Java虚拟机规范中没有规定任何OOM情况的区域。

    2.2 Java虚拟机栈

    Java Virtual Machine Stacks,也是线程私有的,它的生命周期与线程相同。

    虚拟机栈描述的是Java方法执行的内存模型(非native方法)

    每个方法在执行的同时都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。

    每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程,当方法被调用则入栈,一旦完成调用则出栈。所有的栈帧都出栈后,线程就结束了。

    局部变量表存放了编译器可知的各种基本数据类型、对象引用、returnAddress类型。局部变量表所需的内存空间在编译器完成分配。当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

    基本类型:boolean, byte, char, short, int, float, long, double,其中long和double占用2个局部变量空间slot其余的占用1个;

    对象引用:reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置;

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

    在Java虚拟机规范中,对这个区域规定了两种异常:线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(目前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),如果扩展时无法申请到足够的内存,抛出OutOfMemoryError异常。

    2.3 本地方法栈

    Native Method Stack与虚拟机栈的作用非常相似,区别是:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法。

    备注:HotSpot直接把本地方法栈和虚拟机栈合二为一。本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

    2.4 Java堆

    Java Heap是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此区域的唯一目的就是存放对象实例(Java虚拟机规范中的描述时:所有的对象实例以及数组都要在堆上分配)。

    Java堆是GC的主要区域,因此很多时候也被称为GC堆。

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

    从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可以细分为:新生代和老年代,在细致一点的有Eden空间,From Survivor空间,To Survivor空间等。

    备注:有OOM异常

    2.5 方法区

    Method Area是各个线程共享内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载

    在HtoSpot虚拟机中该区域叫永久代,即方法区是虚拟机规范,而永久代是HotSpot实现的方法区。

    绝大部分Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区(永久代)。不过方法区和“PermGen space”又有着本质的区别。前者是 JVM 的规范,而后者则是JVM 规范的一种实现,并且只有 HotSpot 才有 “PermGen space”,而对于其他类型的虚拟机,如JRockit(Oracle)、J9(IBM)并没有“PermGen space”。

    注:从JDK7开始永久代的移除工作,存储在永久代的一部分数据已经转移到了Java Heap或者是Native Heap。但永久代仍然存在于JDK7,并没有完全的移除:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。

    备注:有OOM异常

    2.6 补充

    2.6.1 运行时常量池和Class文件常量池

    Runtime Constant Pool是方法区的一部分。Class文件中除了有类的版本,字段,方法,接口等描述信息,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

    运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性。

    备注:有OOM异常

    2.6.2 直接内存

    Direct Memory,并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。

    在jdk1.4加入的NIO类,引入了一种基于通道(Chanel)与缓冲区(Buffer)的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

    备注:本机直接内存的分配不会受到Java堆大小的限制,受到本机总内存和处理器寻址空间的限制,有OOM异常。

     

    3. HotSpot虚拟机

    3.1 内存划分

    根据JVM规范,内存可分为:虚拟机栈,本地方法栈,堆,方法区,程序计数器五个部分。

    但是各种虚拟机HotSpot,JRockit实现却与JVM不尽相同。

    HotSpot主要有:虚拟机栈,堆,程序计数器,Metaspace,直接内存四个部分。

    如下为JDK8的内存划分:

     

    其中,元空间(Metaspace)的本质和永久代类似,都是对JVM规范中方法区的实现。

    不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。

    因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

    -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
    -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

     

    3.2 对象的创建、内存布局、访问定位

    基于虚拟机HotSpot深入探讨Java堆中对象分配、布局和访问的全过程。

    3.2.1 对象的创建

    (1)虚拟机遇到new指令后,首先检查new指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载,解析和初始化过。如果没有,那必须先执行类的加载过程;

    (2)类加载检查通过之后,虚拟机将为新生对象分配内存(对象所需内存的大小在类加载完成后可完全确定);

    (3)内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值;

    (4)对对象进行必要的设置,例如这个对象是哪个类的实例,如果才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息(这些对象存放在对象的对象头Object Header中)

    (5)执行init方法,把对象按照程序员的意愿进行初始化。

    指针碰撞空闲列表两种分配方式:选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的GC是否带有压缩整理功能决定;因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法时指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

    此外还有对象创建的并发问题:

    (1)堆分配内存空间的动作进行同步处理--采用CAS配上失败重试的方式保证更新操作的原子性;

    (2)把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓存(Thread Local Allocation Buffer,TLAB)

    注:指针碰撞:内存是规整的,空闲列表:内存不是规整的

    3.2.2 对象的内存布局

    在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头、实例数据、对齐填充。

    如图:

    1. 对象头(存储在堆)包括两部分:

    (1)存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标识,线程持有的锁,偏向线程ID,偏向时间戳等,官方称为Mark Word。Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。

    存储内容标志位状态
    对象哈希码,对象分代年龄01未锁定
    指向锁记录的指针00轻量级锁定
    指向重量级锁的指针10膨胀(重量级锁定)
    空,不需要记录信息11GC标记
    偏向线程ID,偏向时间戳,对象分代年龄01可偏向

    (2)类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

    2. 实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。

    3. 对齐填充并不是必然存在的,也没有特别的含义。HotSpot自动内存管理系统要求对象起始地址必须是8字节的倍数,也就是说对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数,因此,当对象实例数据不符没有对齐时,就需要通过对齐填充来补全。

    3.2.3 对象的访问定位

    Java程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在Java虚拟机规范中规定了一个指向对象的引用,并没有定义这个引用应该通过何种方式去定位,访问堆中的对象的具体位置,所以对象访问方式也是取决于虚拟机实现而言的。目前主流的访问方式有使用句柄直接指针两种。

    (1)句柄访问

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

    (2)直接指针,reference中存储的是对象地址,而对象实例的对象头中有类型指针,指向类型数据。

     

    两种对象访问各有优势,使用句柄来访问的最大好处是reference中存储的是稳定的句柄地址,对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。

    直接指针最大好处就是速度更快,节省了一次指针定位的时间开销。

    HotSpot使用了直接指针方式进行对象访问。

    备注:reference应该指向堆中Java对象的内存地址,而对象的内存布局里面包括:对象头和对象数据,对象头里面有对象类型指针。

    4. OOM

    OOM:OutOfMemoryError异常

    在Java虚拟机规范中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OOM异常的可能。

    下面的代码都是基于JDK1.8,因此对堆,虚拟机栈,Metaspace和直接内存做验证和分析。

    4.1 堆OOM

    代码:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    import java.util.ArrayList;

    import java.util.List;

    /**

     * VM -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

     *

     */

    public class HeapOOM {

     

        public static void main(String[] args) {

            List<HeapOOM> list = new ArrayList<HeapOOM>();

            while(true)

                list.add(new HeapOOM());

        }

     

    }

    -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

    Java堆的大小为20MB,不可扩展;通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照。

    运行结果:

    4.2 虚拟机栈OOM

    HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,统一是虚拟机栈。

    栈容量只由-Xss参数设定。

    在Java虚拟机规范中描述了2种异常:

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

    (2)如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OOM。

     代码:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    /**

     * VM -Xss108k

     *

     */

    public class JavaVMStackSOF {

        private int stackLength = 1;

     

        public void stackLeak() {

            stackLength++;

            stackLeak();

        }

         

        public static void main(String[] args) {

            JavaVMStackSOF oom = new JavaVMStackSOF();

            try {

                oom.stackLeak();

            catch (Throwable t) {

                System.out.println(oom.stackLength);

                throw t;

            }

        }

     

    }  

    -Xss指定栈内存容量。

    运行结果:

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

    4.3 元空间OOM

    4.4 直接内存OOM

     

    x. 参考资料

     深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)

    https://www.cnblogs.com/paddix/p/5309550.html

    展开全文
  • Java 内存划分

    2015-08-22 13:29:45
     学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆、栈以及静态数据。那么在Java语言当中,内存又是如何划分的呢?  由于Java程序是交由JVM执行的,所以...

     JVM的内存区域划分

      学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆、栈以及静态数据区。那么在Java语言当中,内存又是如何划分的呢?

      由于Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分。在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程:

                                           

      如上图所示,首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存。因此,在Java中我们常常说到的内存管理就是针对这段空间进行管理(如何分配和回收内存空间)。

      在知道了JVM内存是什么东西之后,下面我们就来讨论一下这段空间具体是如何划分区域的,是不是也像C语言中一样也存在栈和堆呢?

    一.运行时数据区包括哪几部分?

      根据《Java虚拟机规范》的规定,运行时数据区通常包括这几个部分:程序计数器(Program Counter Register)、Java栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap)。

      如上图所示,JVM中的运行时数据区应该包括这些部分。在JVM规范中虽然规定了程序在执行期间运行时数据区应该包括这几部分,但是至于具体如何实现并没有做出规定,不同的虚拟机厂商可以有不同的实现方式。

    二.运行时数据区的每部分到底存储了哪些数据?

      下面我们来了解一下运行时数据区的每部分具体用来存储程序执行过程中的哪些数据。

    1.程序计数器

      程序计数器(Program Counter Register),也有称作为PC寄存器。想必学过汇编语言的朋友对程序计数器这个概念并不陌生,在汇编语言中,程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。

      虽然JVM中的程序计数器并不像汇编语言中的程序计数器一样是物理概念上的CPU寄存器,但是JVM中的程序计数器的功能跟汇编语言中的程序计数器的功能在逻辑上是等同的,也就是说是用来指示 执行哪条指令的。

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

      在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。

      由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

    2.Java栈

      Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈,跟C语言的数据段中的栈类似。事实上,Java栈是Java方法执行的内存模型。为什么这么说呢?下面就来解释一下其中的原因。

      Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。讲到这里,大家就应该会明白为什么 在 使用 递归方法的时候容易导致栈内存溢出的现象了以及为什么栈区的空间不用程序员去管理了(当然在Java中,程序员基本不用关系到内存分配和释放的事情,因为Java有自己的垃圾回收机制),这部分空间的分配和释放都是由系统自动实施的。对于所有的程序设计语言来说,栈这部分空间对程序员来说是不透明的。下图表示了一个Java栈的模型:

      局部变量表,顾名思义,想必不用解释大家应该明白它的作用了吧。就是用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的

      操作数栈,想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

      指向运行时常量池的引用,因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。

      方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

      由于每个线程正在执行的方法可能不同,因此每个线程都会有一个自己的Java栈,互不干扰。

    3.本地方法栈

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

    4.堆

      在C语言中,堆这部分空间是唯一一个程序员可以管理的内存区域。程序员可以通过malloc函数和free函数在堆上申请和释放空间。那么在Java中是怎么样的呢?

      Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)。只不过和C语言中的不同,在Java中,程序员基本不用去关心空间释放的问题,Java的垃圾回收机制会自动进行处理。因此这部分空间也是Java垃圾收集器管理的主要区域。另外,堆是被所有线程共享的,在JVM中只有一个堆。

    5.方法区

      方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

      在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

      在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

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

      以上为个人看法和观点,如有不正之处希望谅解并欢迎指正。

      参考资料:

      http://blog.csdn.net/ns_code/article/details/17565503

      http://www.cnblogs.com/sunada2005/p/3577799.html

      《深入理解Java虚拟机》

      《Java虚拟机规范 SE7》

      转载请标明地址:http://www.cnblogs.com/dolphin0520/p/3613043.html

    作者: 海子
             
    本博客中未标明转载的文章归作者 海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    展开全文
  • 内存划分五大区

    2016-12-22 13:35:00
    代码,全局(静态),常量,堆,栈 1、代码 2、全局(静态):全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量放在...5、堆:一般由程序员管理,比如alloc申请内存,一般的对象...
  • Jvm对自己的内存划分五个区域

    千次阅读 2017-08-10 10:38:23
  • C++中内存划分

    2010-12-14 17:28:00
    在C++中,内存划分成5个区,分别是:堆、栈、自由存储、全局/静态存储、常量存储。   1、堆  由new分配的内存块,释由应用程序控制,编译器不用管。一般一new对应一delete。如果...
  • 内存划分

    2007-09-23 21:51:00
    在C++中,内存分成5个区,他们分别是堆、栈、自由存储、全局/静态存储和常量存储。 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储。里面的变量通常是局部变量、函数参数等。...
  • 程序的内存分配 一、一由C/C++编译的程序占用的内存分为以下几部分 1、栈(stack) 2、堆(heap) 3、全局(静态)(static) 4、文字常量 5、程序代码
  •  一由C/C++编译的程序占用的内存分为以下几部分   1、栈(stack)  由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。   2、堆(heap)  一般由...
  • C语言的内存划分

    2019-04-03 20:40:22
    代码指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次(每进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现。 代码的指令中包括操作码和要操作的对象(或对象地址引用)...
  • JVM内存划分总结

    千次阅读 2016-04-23 08:20:55
    JDK8 虚拟机内存划分 概述 在说jvm内存划分之前,先来说下java程序具体的执行流程:     Java源文件经过java编译器编译后变成class字节码文件, Jvm的classloader加载class文件完成后,交由execution ...
  • 内存划分

    2014-09-24 12:46:52
    内存划分: 1,寄存器。 2,本地方法。 3,方法。 4,栈内存。 存储的都是局部变量。 而且变量所属的作用域一旦结束,该变量就自动释放。 5,堆内存。 存储是数组和对象(其实数组就是对象) 凡是new建立在堆...
  • 4.JVM内存划分

    2018-03-07 13:54:35
    JVM的内存划分:首先来看一下JVM内存结构,它是由堆、栈、本地方法栈、方法等部分组成。JVM中内存JVM中内存通常划分为三部分,分别为堆内存与栈内存,程序计数器。栈内存主要用执行线程方法存放本地临时变量与...
  • java1.8的内存划分

    2019-11-17 10:48:02
    java1.8中的内存划分 很多人对于jvm的认识来自于周志明的《深入理解java虚拟机》一书,这也是一本很经典的书。 遗憾的是,这本书里面的很多内容是基于java1.7以前的版本来描述的,对比java1.8后的版本,已经存在很多...
  • java 堆内存划分

    2017-09-28 15:04:04
    转载自http://blog.csdn.net/sunny243788557/article/details/52796904在JVM中堆空间划分如下图所示 ...2.新生代可以划分为三个区,Eden,两幸存 在JVM运行时,可以通过配置以下参数改变整个JVM堆的配置比例1.JV
  • 内存区域划分

    2012-11-05 21:18:29
    内存划分区: 1.寄存器 2.本地方法 3.方法 4.栈内存 存储的都是局部变量 5.堆内存  存储的数组和对象(其实数组就是对象),new 过的都在堆中。
  • java虚拟机的内存划分

    2017-06-05 10:19:52
    1、java虚拟机的内存划分为三部分:Eden,From Survivor,To Survivor 、老年代,永久代这四部分; 2、java虚拟机的堆内存包括:Eden,From Survivor,To Survivor,老年代区域,不包括永久代; 4、...
  • java的内存划分概述

    千次阅读 2020-06-08 17:59:33
    java的内存划分概述 概念示意图 总括—分为五个部分 栈空间(Stack):存放方法中的局部变量. 堆空间(Heap):存放动态创建的变量和对象 方法(Method Area) :存放.class文件,包括方法的信息 本地方法栈(Native Method...
  • Java的内存划分

    2013-12-21 17:23:28
    内存划分: 1、寄存器。我们在程序中无法控制 2、本地方法。 3、方法。 4、栈内存。 存储的都是局部变量。 变量的所属的作用域一旦结束,给变量自动释放。 5、堆内存。 存储的是数组和对象(其实数组...
  • JAVA内存区域划分

    千次阅读 2018-07-13 21:58:58
    大家好,今天和大家分享java内存区域划分知识。 通常我们把java的内存区域粗略划分为栈内存和堆内存,但是这只能说明... JAVA内存划分为以下几区域: 1.程序计数器 2.虚拟机栈 3.本地方法栈 4.方法 5...
  • 在c中分为这几存储 1.栈 - 由编译器自动分配释放 2.堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 3.全局(静态),全局变量和静态变量的存储是放在一块的,初始化的全局变量和...
  • JVM内存区域划分

    千次阅读 2018-11-29 11:06:20
    Java程序是交由JVM执行的,所以我们在谈Java内存区域划分的时候事实上是指JVM内存区域划分。在讨论JVM内存区域划分之前,先来看一下Java程序具体执行的过程:   首先Java源代码文件(.java后缀)会被Java编译器编译...
  • Java 内存划分

    2015-07-25 17:57:01
    内存划分: 1,寄存器。 2,本地方法。 3,方法。  数据共享 静态变量存放在方法 4,栈内存。 存储的都是局部变量。 而且变量所属的作用域一旦结束,该变量就自动释放。 5,堆内存。 存储是数组和对象...
  • 内存区域的划分

    千次阅读 2017-08-12 16:38:48
    本文主要讲解应用层(c/c++内存划分)、linux内核层(X86体系和ARM系统)关于内存上面的划分相关知识点。 一、应用层 1. 在c中分为这几存储:堆、栈、全局(静态)、常量 (1).栈 - 由编译器自动分配...
  • 小白都能看懂的java虚拟机内存区域划分

    万次阅读 多人点赞 2019-11-26 17:21:27
    目录 一、虚拟机 二、虚拟机组成 1.栈 栈帧 2.程序计数器 ...3.方法 ...5.堆 ...同样的java代码在不同平台生成的机器码肯定是...同一java代码在windows上生成的机器码可能是0101.......,在linux上生成的可能是1100....

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 111,044
精华内容 44,417
关键字:

内存划分成五个区