精华内容
下载资源
问答
  • Java对象内存分配

    千次阅读 2017-11-07 16:39:37
    Java对象与其内存分配Java中一切都是对象对象是Java运行的单元,知道对象是如何存在的、什么时候是可见的,才知道怎样运用对象来完成相应的操作。 Java运行时对象和变量等都是在内存中,可以根据内存中的数据来...

    ##Java对象与内存分配
    Java中一切都是对象,对象是Java运行的单元,知道对象是如何存在的、什么时候是可见的,才知道怎样运用对象来完成相应的操作。
    Java运行时对象和变量等都是在内存中,可以根据内存中的数据来判断这些对象的可见性。下面了解一下Java对象在内存中的分配。
    内存主要分为:程序计数器、虚拟机栈、堆、方法区、本地方法栈。
    程序计数器可以看作是当前线程所执行的字节码的行号指示器。它是线程私有的。
    虚拟机栈也是线程私有的,描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    本地方法栈同虚拟机栈非常相似,,为虚拟机使用到的Native方法服务。也是线程私有的。
    Java堆是所有线程共享的,是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建。几乎所有的对象实例以及数组都要在堆上分配内存。Java堆是垃圾收集器管理的主要区域,因此也称作“GC堆”。Java堆在计算机实际内存中可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
    方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
    C语言可以直接操控电脑的内存,所以C语言在操作数据时要手动分配内存给数据,在数据用完后要及时释放内存,以免发生内存溢出问题。Java是用C语言写的,它对内存的分配和回收机制已经处理了,所以不需要程序员再去手动的处理内存的申请与释放操作。
    Java虽然有回收机制(GC),也不能肆无忌惮的创建对象。建的多了GC频繁的回收也会影响效率,也会导致内存溢出。了解Java对象的内存分配有助于服务性能的提高。

    Java变量分为局部变量和成员变量。局部变量主要是:a、形参,即方法的参数;b、方法中的变量;c、代码块中的变量。成员变量主要是非静态变量即实例变量和静态变量即类变量。
    变量的定义和初始化的内存分配的时机是有先后顺序的,如下:
    a、类变量和静态代码块为同一级,优先分配内存,其次是实例变量和代码块这一级别,最后是为构造器初始化分配内存;
    b、Java要求定义处在内存分配中同一级别的变量时,必须采用合法的前向引导。通俗的讲就是类变量和静态代码块定义和初始化的变量要有先后顺序,实例变量和代码块定义和初始化要有先后顺序;
    c、内存分配中同一级别的变量,先按顺序定义,都定义好后再按顺序初始化。
    下面举例说明:

    public class MemoryTest {     //第一步
        int surplus = num - count;          //a //第六步
        static int count = 0;     //第二步
        int max;  //第七步
        {  //第八步
            System.out.println("代码块初始化前surplus=" + surplus);
            count = 2;
            surplus = num - count;
            persons = 1;                  //b
            int var = 3;
            System.out.println("代码块初始化后surplus=" + surplus);
        }
        static int num = 5;     //第三步
        int persons = 2; //第九步
        {//第十步
            max = count -persons + 1;     //c
        }
    
        public static void main(String[] args){  //第四步
            MemoryTest memory = new MemoryTest();  //第五步
            System.out.println(memory.persons);
        }
    }
    

    执行结果是

    代码块初始化前surplus=5
    代码块初始化后surplus=3
    2
    

    这个例子很好的说明了变量的内存分配和分配时机。这里的变量存在实例变量、类变量、代码块中的局部变量。这里对代码分析下内存分配和分配时机.
    在第六步实例变量surplus初始化时用到了类变量num和count,num和count的定义和初始化是在后面代码,之所以实例变量可以操作类变量num和count是因为类变量是属于类本身的,就是在加载MemoryTest类时就对类变量进行定义和初始化.
    类初始化时先将count和num定义到栈内存中,并分配内存空间,此时两个变量的默认值都是0.再根据初始化代码进行初始化.如图:
    这里写图片描述
    在第五步创建类MemoryTest对象new MemoryTest()时会在堆栈中为实例memory分配内存并赋默认值null,再在堆内存中创建对象。下面用图来分析MemoryTest对象的创建过程。
    首先,在堆内存中分配空间,将对象的实例变量逐个定义,类型为int型,默认值都是0,如图:
    这里写图片描述
    其次,根据代码和变量的先后顺序为变量初始化,实例变量和非静态代码块按顺序执行,如第六步,
    这里写图片描述
    在执行第八步代码块时,会对persons赋值,虽然定义persons的代码是在代码块后,但对象定义变量是在初始化和代码块之前执行,所以在代码块中为persons赋值不会报错。
    这里写图片描述
    第八步代码块中定义的局部变量var的作用域仅是代码块,代码块结束后var也随记被销毁,空间被回收。
    这里写图片描述
    这里需要注意一个问题,在第八步代码块中,persons只能被赋值,不能作为变量用于其他语句的计算或引用等操作,原因是persons是在第九步定义被赋值,代码现在执行在第八步,我们说实例变量和代码块是顺序执行的,还未执行到第九步,也就是persons还未结束定义初始化,所以不能操作persons变量,但在第九步初始化结束,persons变量就可以使用了。
    这里写图片描述
    MemoryTest对象创建完之后memory指向对象地址,调用对象的persons实例变量。
    这里写图片描述
    main方法执行完之后,堆栈中的memory将被销毁,空间回收,此时MemoryTest对象将没有实例指向它,它也会被GC回收。

    展开全文
  • java对象内存中的分配

    千次阅读 2018-08-22 11:05:55
    java中内存主要包含4块,即heap(堆内存)、stack(栈内存)、data segment(静态变量或是常量存放区)...堆内存中存放的是new出的对象,new出的对象只包含成员变量。  栈内存中:存放的是局部成员变量。对于基本的数据...

    转自:https://blog.csdn.net/qq_30753945/article/details/54974899

    java中内存主要包含4块,即heap(堆内存)、stack(栈内存)、data segment(静态变量或是常量存放区)、codesegment(方法区). 
    堆内存中存放的是new出的对象,new出的对象只包含成员变量。 
    栈内存中:存放的是局部成员变量。对于基本的数据类型存放的是基本变量的值,而对于对象变量,存放的是堆内存的地址。 
    静态、常量区:存放的是静态变量(类变量)或是常量。 
    方法区:存放的是对象的方法。因此即使new出多个对象也是只是存在一个方法。 

    如 
    A a = new A(); A中含有一个work方法,2个成员变量a,b。那么对应的内存分配为 
    则 a是分配在栈内存中。里面存放了一个指向堆内存中存放的new A()的地址。 
    new A()会导致在堆内存中分配一块空间,该内存中的A对象同时会含有a和b。 
    work()方法会在codesegment区中分配内存。 
    如果此时 A b = a;则表示把a的值复制给b,即b的值为a中保存的地址

     

    java程序运行时内存分配详解 

    一、  基本概念

       每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享。Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量。虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被创建的对象的本身,而在栈中分配的内存只是存放指向这个堆对象的引用而已。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。

    具体的概念: JVM的内存可分为3个区: 堆(heap)、栈(stack)和方法区(method,也叫静态区): 

    堆区: 

    1.存储的全部是对象,每个对象都包含一个与之对应的class的信息(class的目的是得到操作指令) ; 
    2.jvm只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身和数组本身;

    栈区:  
    1.每个线程包含一个栈区,栈中只保存基础数据类型本身和自定义对象的引用; 
    2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问; 
    3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令);

    方法区(静态区):  
    1.被所有的线程共享,方法区包含所有的class(class是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和static变量。  
    2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。  
     

    二、实例演示

     1   AppMain.java 
     2  
     3   public   class  AppMain                //运行时, jvm 把appmain的代码全部都放入方法区     
     4   {     
     5   public   static   void  main(String[] args)  //main 方法本身放入方法区。     
     6   {     
     7   Sample test1 = new  Sample( " 测试1 " );   //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面     
     8   Sample test2 = new  Sample( " 测试2 " );     
     9      
    10   test1.printName();     
    11   test2.printName();     
    12   }     
    13   }     
    14      
    15   public   class  Sample        //运行时, jvm 把Sample的信息都放入方法区     
    16   {     
    17   /** 范例名称 */     
    18   private String name;      //new Sample实例后, name 引用放入栈区里, name 对应的 String 对象放入堆里     
    19      
    20   /** 构造方法 */     
    21   public  Sample(String name)     
    22   {     
    23   this .name = name;     
    24   }     
    25      
    26   /** 输出 */     
    27   public   void  printName()   //在没有对象的时候,print方法跟随sample类被放入方法区里。     
    28   {     
    29   System.out.println(name);     
    30   }     
    31   }   

          运行该程序时,首先 启动一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据,然后把Appmain类的类信息存放到运行时数据区的 方法区 中,这就是AppMain类的加载过程。 

       接着,Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是: 

    Sample test1=new Sample("测试1");

    该语句的执行过程: 
        1、 Java虚拟机到方法区找到Sample类的类型信息,没有找到,因为Sample类还没有加载到方法区(这里可以看出,java中的内部类是单独存在的,而且刚开始的时候不会跟随包含类一起被加载,等到要用的时候才被加载)。Java虚拟机立马加载Sample类,把Sample类的类型信息存放在方法区里。  
        2、 Java虚拟机首先在堆区中为一个新的Sample实例分配内存, 并在Sample实例的内存中存放一个方法区中存放Sample类的类型信息的内存地址。 
        3、 JVM的进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。

        4、位于“=”前的Test1是一个在main()方法中定义的一个变量(一个Sample对象的引用),因此,它被会添加到了执行main()方法的主线程的JAVA方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例。 
        5、JVM在堆区里继续创建另一个Sample实例,并在main方法的方法调用栈中添加一个Test2变量,该变量指向堆区中刚才创建的Sample新实例。

        6、JVM依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而获得printName()方法的字节码,接着执行printName()方法包含的指令,开始执行。  
    三、辨析

    在Java语言里堆(heap)和栈(stack)里的区别 :

        1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。  
    2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享(详见下面的介绍)。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。 

    Java中的2种数据类型:

    一种是 基本类型 (primitive types),  四类八种:整型byte shot int long 浮点型float double 逻辑型boolean 字符型chat(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。 自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在 。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。 这些 字面值 的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存 在于栈中 。  

        栈有一个很重要的特性:存在栈中的数据可以共享。 假设我们同时定义:  int a = 3; int b = 3;   编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,如果没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。 

    这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与 b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。  
    另一种是 包装类数据, 如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部 存在于堆中, Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

    展开全文
  • C++对象内存分配问题

    千次阅读 2016-10-18 22:14:26
    用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内存...

    如果一个人自称为程序高手,却对内存一无所知,那么我可以告诉你,他一定在吹牛。用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存的分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内存泄漏,比如悬挂指针。笔者今天在这里并不是要讨论如何避免这些问题,而是想从另外一个角度来认识C++内存对象。

      我们知道,C++将内存划分为三个逻辑区域:堆、栈和静态存储区。既然如此,我称位于它们之中的对象分别为堆对象,栈对象以及静态对象。那么这些不同的内存对象有什么区别了?堆对象和栈对象各有什么优劣了?如何禁止创建堆对象或栈对象了?这些便是今天的主题。

      一.基本概念

      先来看看栈。栈,一般用于存放局部变量或对象,如我们在函数定义中用类似下面语句声明的对象:

    Type stack_object ;

    stack_object便是一个栈对象,它的生命期是从定义点开始,当所在函数返回时,生命结束。

      另外,几乎所有的临时对象都是栈对象。比如,下面的函数定义:
      

    Type fun(Type object) ;

    这个函数至少产生两个临时对象,首先,参数是按值传递的,所以会调用拷贝构造函数生成一个临时对象object_copy1 ,在函数内部使用的不是使用的不是object,而是object_copy1,自然,object_copy1是一个栈对象,它在函数返回时被释放;还有这个函数是值返回的,在函数返回时,如果我们不考虑返回值优化(NRV),那么也会产生一个临时对象object_copy2,这个临时对象会在函数返回后一段时间内被释放。比如某个函数中有如下代码:

    Type tt ,result ; //生成两个栈对象 
    tt = fun(tt) ; //函数返回时,生成的是一个临时对象object_copy2

    上面的第二个语句的执行情况是这样的,首先函数fun返回时生成一个临时对象object_copy2 ,然后再调用赋值运算符执行

    tt = object_copy2 ; //调用赋值运算符

    看到了吗?编译器在我们毫无知觉的情况下,为我们生成了这么多临时对象,而生成这些临时对象的时间和空间的开销可能是很大的,所以,你也许明白了,为什么对于“大”对象最好用const引用传递代替按值进行函数参数传递了。

      接下来,看看堆。堆,又叫自由存储区,它是在程序执行的过程中动态分配的,所以它最大的特性就是动态性。在C++中,所有堆对象的创建和销毁都要由程序员负责,所以,如果处理不好,就会发生内存问题。如果分配了堆对象,却忘记了释放,就会产生内存泄漏;而如果已释放了对象,却没有将相应的指针置为NULL,该指针就是所谓的“悬挂指针”,再度使用此指针时,就会出现非法访问,严重时就导致程序崩溃。

      那么,C++中是怎样分配堆对象的?唯一的方法就是用new(当然,用类malloc指令也可获得C式堆内存),只要使用new,就会在堆中分配一块内存,并且返回指向该堆对象的指针。

      再来看看静态存储区。所有的静态对象、全局对象都于静态存储区分配。关于全局对象,是在main()函数执行前就分配好了的。其实,在main()函数中的显示代码执行之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局对象的的构造及初始化工作。而在main()函数结束之前,会调用由编译器生成的exit函数,来释放所有的全局对象。比如下面的代码:

    void main(void)
    {
     … …// 显式代码
    }

    实际上,被转化成这样:

    void main(void)
    {
     _main(); //隐式代码,由编译器产生,用以构造所有全局对象
     … … // 显式代码
     … …
     exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象
    }

    所以,知道了这个之后,便可以由此引出一些技巧,如,假设我们要在main()函数执行之前做某些准备工作,那么我们可以将这些准备工作写到一个自定义的全局对象的构造函数中,这样,在main()函数的显式代码执行之前,这个全局对象的构造函数会被调用,执行预期的动作,这样就达到了我们的目的。 刚才讲的是静态存储区中的全局对象,那么,局部静态对象了?局部静态对象通常也是在函数中定义的,就像栈对象一样,只不过,其前面多了个static关键字。局部静态对象的生命期是从其所在函数第一次被调用,更确切地说,是当第一次执行到该静态对象的声明代码时,产生该静态局部对象,直到整个程序结束时,才销毁该对象。

      还有一种静态对象,那就是它作为class的静态成员。考虑这种情况时,就牵涉了一些较复杂的问题。

      第一个问题是class的静态成员对象的生命期,class的静态成员对象随着第一个class object的产生而产生,在整个程序结束时消亡。也就是有这样的情况存在,在程序中我们定义了一个class,该类中有一个静态对象作为成员,但是在程序执行过程中,如果我们没有创建任何一个该class object,那么也就不会产生该class所包含的那个静态对象。还有,如果创建了多个class object,那么所有这些object都共享那个静态对象成员。

      第二个问题是,当出现下列情况时:

    class Base
    {
     public:
      static Type s_object ;
    }
    class Derived1 : public Base / / 公共继承
    {
     … …// other data 
    }
    class Derived2 : public Base / / 公共继承
    {
     … …// other data 
    }
    
    Base example ;
    Derivde1 example1 ;
    Derivde2 example2 ;
    example.s_object = …… ;
    example1.s_object = …… ; 
    example2.s_object = …… ;

    请注意上面标为黑体的三条语句,它们所访问的s_object是同一个对象吗?答案是肯定的,它们的确是指向同一个对象,这听起来不像是真的,是吗?但这是事实,你可以自己写段简单的代码验证一下。我要做的是来解释为什么会这样? 我们知道,当一个类比如Derived1,从另一个类比如Base继承时,那么,可以看作一个Derived1对象中含有一个Base型的对象,这就是一个subobject。一个Derived1对象的大致内存布局如下:
      
      让我们想想,当我们将一个Derived1型的对象传给一个接受非引用Base型参数的函数时会发生切割,那么是怎么切割的呢?相信现在你已经知道了,那就是仅仅取出了Derived1型的对象中的subobject,而忽略了所有Derived1自定义的其它数据成员,然后将这个subobject传递给函数(实际上,函数中使用的是这个subobject的拷贝)。

      所有继承Base类的派生类的对象都含有一个Base型的subobject(这是能用Base型指针指向一个Derived1对象的关键所在,自然也是多态的关键了),而所有的subobject和所有Base型的对象都共用同一个s_object对象,自然,从Base类派生的整个继承体系中的类的实例都会共用同一个s_object对象了。上面提到的example、example1、example2的对象布局如下图所示:

      二.三种内存对象的比较

      栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;而且栈对象的创建速度一般较堆对象快,因为分配堆对象时,会调用operator new操作,operator new会采用某种内存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指针就可以了。但是要注意的是,通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。

      堆对象,其产生时刻和销毁时刻都要程序员精确定义,也就是说,程序员对堆对象的生命具有完全的控制权。我们常常需要这样的对象,比如,我们需要创建一个对象,能够被多个函数所访问,但是又不想使其成为全局的,那么这个时候创建一个堆对象无疑是良好的选择,然后在各个函数之间传递这个堆对象的指针,便可以实现对该对象的共享。另外,相比于栈空间,堆的容量要大得多。实际上,当物理内存不够时,如果这时还需要生成新的堆对象,通常不会产生运行时错误,而是系统会使用虚拟内存来扩展实际的物理内存。
    接下来看看static对象。

      首先是全局对象。全局对象为类间通信和函数间通信提供了一种最简单的方式,虽然这种方式并不优雅。一般而言,在完全的面向对象语言中,是不存在全局对象的,比如C#,因为全局对象意味着不安全和高耦合,在程序中过多地使用全局对象将大大降低程序的健壮性、稳定性、可维护性和可复用性。C++也完全可以剔除全局对象,但是最终没有,我想原因之一是为了兼容C。

      其次是类的静态成员,上面已经提到,基类及其派生类的所有对象都共享这个静态成员对象,所以当需要在这些class之间或这些class objects之间进行数据共享或通信时,这样的静态成员无疑是很好的选择。

      接着是静态局部对象,主要可用于保存该对象所在函数被屡次调用期间的中间状态,其中一个最显著的例子就是递归函数,我们都知道递归函数是自己调用自己的函数,如果在递归函数中定义一个nonstatic局部对象,那么当递归次数相当大时,所产生的开销也是巨大的。这是因为nonstatic局部对象是栈对象,每递归调用一次,就会产生一个这样的对象,每返回一次,就会释放这个对象,而且,这样的对象只局限于当前调用层,对于更深入的嵌套层和更浅露的外层,都是不可见的。每个层都有自己的局部对象和参数。

      在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。

      三.使用栈对象的意外收获

      前面已经介绍到,栈对象是在适当的时候创建,然后在适当的时候自动释放的,也就是栈对象有自动管理功能。那么栈对象会在什么会自动释放了?第一,在其生命期结束的时候;第二,在其所在的函数发生异常的时候。你也许说,这些都很正常啊,没什么大不了的。是的,没什么大不了的。但是只要我们再深入一点点,也许就有意外的收获了。

      栈对象,自动释放时,会调用它自己的析构函数。如果我们在栈对象中封装资源,而且在栈对象的析构函数中执行释放资源的动作,那么就会使资源泄漏的概率大大降低,因为栈对象可以自动的释放资源,即使在所在函数发生异常的时候。实际的过程是这样的:函数抛出异常时,会发生所谓的stack_unwinding(堆栈回滚),即堆栈会展开,由于是栈对象,自然存在于栈中,所以在堆栈回滚的过程中,栈对象的析构函数会被执行,从而释放其所封装的资源。除非,除非在析构函数执行的过程中再次抛出异常――而这种可能性是很小的,所以用栈对象封装资源是比较安全的。基于此认识,我们就可以创建一个自己的句柄或代理来封装资源了。智能指针(auto_ptr)中就使用了这种技术。在有这种需要的时候,我们就希望我们的资源封装类只能在栈中创建,也就是要限制在堆中创建该资源封装类的实例。

    四.禁止产生堆对象

      上面已经提到,你决定禁止产生某种类型的堆对象,这时你可以自己创建一个资源封装类,该类对象只能在栈中产生,这样就能在异常的情况下自动释放封装的资源。

      那么怎样禁止产生堆对象了?我们已经知道,产生堆对象的唯一方法是使用new操作,如果我们禁止使用new不就行了么。再进一步,new操作执行时会调用operator new,而operator new是可以重载的。方法有了,就是使new operator 为private,为了对称,最好将operator delete也重载为private。现在,你也许又有疑问了,难道创建栈对象不需要调用new吗?是的,不需要,因为创建栈对象不需要搜索内存,而是直接调整堆栈指针,将对象压栈,而operator new的主要任务是搜索合适的堆内存,为堆对象分配空间,这在上面已经提到过了。好,让我们看看下面的示例代码:

    #include <stdlib.h> //需要用到C式内存分配函数 
    class Resource ; //代表需要被封装的资源类 
    class NoHashObject 
    { 
     private: 
      Resource* ptr ;//指向被封装的资源 
      ... ... //其它数据成员 
      void* operator new(size_t size) //非严格实现,仅作示意之用 
      { 
       return malloc(size) ; 
      } 
      void operator delete(void* pp) //非严格实现,仅作示意之用 
      { 
       free(pp) ; 
      } 
     public: 
      NoHashObject() 
      { 
       //此处可以获得需要封装的资源,并让ptr指针指向该资源 
       ptr = new Resource() ; 
      } 
      ~NoHashObject() 
      { 
       delete ptr ; //释放封装的资源 
      } 
    };

    NoHashObject现在就是一个禁止堆对象的类了,如果你写下如下代码:

    NoHashObject* fp = new NoHashObject() ; //编译期错误! 
    delete fp ;

    上面代码会产生编译期错误。好了,现在你已经知道了如何设计一个禁止堆对象的类了,你也许和我一样有这样的疑问,难道在类NoHashObject的定义不能改变的情况下,就一定不能产生该类型的堆对象了吗?不,还是有办法的,我称之为“暴力破解法”。C++是如此地强大,强大到你可以用它做你想做的任何事情。这里主要用到的是技巧是指针类型的强制转换。

    void main(void) 
    { 
     char* temp = new char[sizeof(NoHashObject)] ; 
    
     //强制类型转换,现在ptr是一个指向NoHashObject对象的指针 
     NoHashObject* obj_ptr = (NoHashObject*)temp ; 
    
     temp = NULL ; //防止通过temp指针修改NoHashObject对象 
    
     //再一次强制类型转换,让rp指针指向堆中NoHashObject对象的ptr成员 
     Resource* rp = (Resource*)obj_ptr ; 
    
     //初始化obj_ptr指向的NoHashObject对象的ptr成员 
     rp = new Resource() ; 
     //现在可以通过使用obj_ptr指针使用堆中的NoHashObject对象成员了 
     ... ... 
    
     delete rp ;//释放资源 
     temp = (char*)obj_ptr ; 
     obj_ptr = NULL ;//防止悬挂指针产生 
     delete [] temp ;//释放NoHashObject对象所占的堆空间。 
    }

    上面的实现是麻烦的,而且这种实现方式几乎不会在实践中使用,但是我还是写出来路,因为理解它,对于我们理解C++内存对象是有好处的。对于上面的这么多强制类型转换,其最根本的是什么了?我们可以这样理解:

      某块内存中的数据是不变的,而类型就是我们戴上的眼镜,当我们戴上一种眼镜后,我们就会用对应的类型来解释内存中的数据,这样不同的解释就得到了不同的信息。

      所谓强制类型转换实际上就是换上另一副眼镜后再来看同样的那块内存数据。

      另外要提醒的是,不同的编译器对对象的成员数据的布局安排可能是不一样的,比如,大多数编译器将NoHashObject的ptr指针成员安排在对象空间的头4个字节,这样才会保证下面这条语句的转换动作像我们预期的那样执行:
      

    Resource* rp = (Resource*)obj_ptr ;

    但是,并不一定所有的编译器都是如此。

      既然我们可以禁止产生某种类型的堆对象,那么可以设计一个类,使之不能产生栈对象吗?当然可以。

      五.禁止产生栈对象

      前面已经提到了,创建栈对象时会移动栈顶指针以“挪出”适当大小的空间,然后在这个空间上直接调用对应的构造函数以形成一个栈对象,而当函数返回时,会调用其析构函数释放这个对象,然后再调整栈顶指针收回那块栈内存。在这个过程中是不需要operator new/delete操作的,所以将operator new/delete设置为private不能达到目的。当然从上面的叙述中,你也许已经想到了:将构造函数或析构函数设为私有的,这样系统就不能调用构造/析构函数了,当然就不能在栈中生成对象了。

      这样的确可以,而且我也打算采用这种方案。但是在此之前,有一点需要考虑清楚,那就是,如果我们将构造函数设置为私有,那么我们也就不能用new来直接产生堆对象了,因为new在为对象分配空间后也会调用它的构造函数啊。所以,我打算只将析构函数设置为private。再进一步,将析构函数设为private除了会限制栈对象生成外,还有其它影响吗?是的,这还会限制继承。

      如果一个类不打算作为基类,通常采用的方案就是将其析构函数声明为private。

      为了限制栈对象,却不限制继承,我们可以将析构函数声明为protected,这样就两全其美了。如下代码所示:

    class NoStackObject 
    { 
     protected: 
      ~NoStackObject() { } 
     public: 
      void destroy() 
      { 
       delete this ;//调用保护析构函数 
      } 
    };

    接着,可以像这样使用NoStackObject类:

    NoStackObject* hash_ptr = new NoStackObject() ; 
    ... ... //对hash_ptr指向的对象进行操作 
    hash_ptr->destroy() ;

    呵呵,是不是觉得有点怪怪的,我们用new创建一个对象,却不是用delete去删除它,而是要用destroy方法。很显然,用户是不习惯这种怪异的使用方式的。所以,我决定将构造函数也设为private或protected。这又回到了上面曾试图避免的问题,即不用new,那么该用什么方式来生成一个对象了?我们可以用间接的办法完成,即让这个类提供一个static成员函数专门用于产生该类型的堆对象。(设计模式中的singleton模式就可以用这种方式实现。)让我们来看看:

    class NoStackObject 
    { 
     protected: 
      NoStackObject() { } 
      ~NoStackObject() { } 
     public: 
      static NoStackObject* creatInstance() 
      { 
       return new NoStackObject() ;//调用保护的构造函数 
      } 
      void destroy() 
      { 
       delete this ;//调用保护的析构函数 
      } 
    };

    现在可以这样使用NoStackObject类了:

    NoStackObject* hash_ptr = NoStackObject::creatInstance() ; 
    ... ... //对hash_ptr指向的对象进行操作 
    hash_ptr->destroy() ; 
    hash_ptr = NULL ; //防止使用悬挂指针

    现在感觉是不是好多了,生成对象和释放对象的操作一致了。

      ok,讲到这里,已经涉及了较多的东西,如果要把内存对象讲得更深入更全面,那可能需要写成一本书了,而就我自己的功力而言,可能是很难完全把握的。如果上面所写的能使你有所收获或启发,我就满足了。如果你要更进一步去了解内存对象方面的知识,那么我可以推荐你看看《深入探索C++对象模型》这本书。

    转载自:http://blog.csdn.net/c504665913/article/details/7797859?locationNum=4

    展开全文
  • JVM是如何自动进行内存管理的呢?本文详细对象分配策略,栈上分配与TLAB,相信相信大家看完已经掌握JVM是如何管理,本文适合点赞+收藏。

    JVM是如何自动进行内存管理的呢?本文详细对象的分配策略,栈上分配与TLAB,相信相信大家看完已经掌握JVM是如何管理,本文适合点赞+收藏。有什么错误希望大家直接指出~

    前面我们学习了JVM内存区域JVM中的对象及引用,这节首先大家想一个问题:平时写代码需要去编写对象被分配在内存的什么位置了吗?是的,就像是不需要考虑垃圾回收具体什么时间点回收,JVM已经自动进行内存管理了,JVM这么做也是有原因的。

    Java自动内存管理概述

    Java所支持的自动内存管理针对的是对象内存的自动分配和回收,原因如下:

    1、在Java的内存区域中,本地方法栈、虚拟机栈、程序计数器这三块内存区域的分配和回收具有确定性,他们在编译阶段就能确定需要分配的空间大小。此外,这些内存区域属于线程私有,随线程生而生,随线程灭而灭。综上,虚拟机不需要在这部分内存区域花费太多精力用于垃圾回收。
    2、方法区存储的是类信息、静态变量、常量、即时编译器编译过的代码,这部分数据的回收条件较为苛刻,垃圾回收的“成绩”并不是那么令人满意,因此不是垃圾收集器需要重点关注的区域。
    3、Java堆存储所有线程的对象,这些对象内存空间的分配是在程序运行期间才进行的,因此具有不确定性。此外,对象的生命周期长短不一,为了提高垃圾收集的效率,需要针对不同生命周期的对象设置不同的垃圾收集算法,这也就增加了内存管理的复杂度。

    对象分配策略

    对象优先在 Eden 区分配

    大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间分配时,虚拟机将发起一次 Minor GC。

    大对象直接进入老年代

    大对象就是指需要大量连续内存空间的 Java 对象,最典型的大对象便是那种很长的字符串,或者元素数量很庞大的数组。
    大对象对虚拟机的内存分配来说就是一个不折不扣的坏消息,比遇到一个大对象更加坏的消息就是遇到“朝生夕灭”的“短命大对象”,我们写程序的时候应注意避免。
    在 Java 虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好它们。而当复制对象时,大对象就意味着高额的内存复制开销。
    HotSpot 虚拟机提供了-XX:PretenureSizeThreshold 参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在 Eden 区及两个 Survivor区之间来回复制,产生大量的内存复制操作。
    这样做的目的:1.避免大量内存复制,2.避免提前进行垃圾回收,明明内存有空间进行分配。
    PretenureSizeThreshold 参数只对 Serial 和 ParNew 两款收集器有效。-XX:PretenureSizeThreshold=4m

    长期存活对象进入老年区

    HotSpot 虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活对象应当放在新生代,哪些存活对象放在老年代中。为做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器,存储在对象头中。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1,对象在 Survivor区中每熬过一次 Minor GC,年龄就增加 1,当它的年龄增加到一定程度(并发的垃圾回收器默认为 15),CMS 是 6 时,就会被晋升到老年代中。-XX:MaxTenuringThreshold 调整。

    空间分配担保

    在发生 MinorGC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么 MinorGC 可以确保是安全 的。如果不成立,则虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。

    如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 MinorGC,尽管这次 MinorGC 是有风险的(因为判断的是平均大小,有可能这次的晋升对象比平均值大很多),如果担保失败则会进行一次 FullGC;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 FullGC。

    BUT

    在《深入理解Java虚拟机》书中,有这么一句话:“对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在堆上分配”。这里没有说所有的对象都在堆上进行分配,而是使用了“几乎所有”一词进行描述,那么今天就来简单聊一聊,除了堆以外的对象分配。

    对Java对象分配的过程进行了分析,分析后可知为了解决线程安全问题并且提高效率,有另外两个地方也是可以存放对象的这两个地方分别是栈和TLAB。

    栈上分配

    再问一个问题:如果确定一个对象的作用域不会逃逸出方法之外,那可不可以将这个对象分配在栈上?这样的话,对象所占用的内存空间就可以随着栈帧的出栈而销毁。而且,在一般应用中,不会逃逸的局部对象所占的比例很大,所以如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以还可以减小垃圾收集器的负载。

    分析完以后给出栈上分配官方定义:JVM允许将线程私有的对象打散分配在栈上,而不是分配在堆上。分配在栈上的好处是可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能。栈上分配只是JVM虚拟机提供的一种优化技术,对象主要还是分配在堆上的

    逃逸分析

    栈上分配也是有前提的,并不是所有的对象都可以栈上分配,首先需要进行逃逸分析的,所以逃逸分析是栈上分配的技术基础那什么是逃逸分析呢?逃逸分析是指判断对象的作用域是否有可能逃逸出函数体,关于具体的逃逸分析算法和技术此篇不讨论Java SE 6u23版本之后,HotSpot中默认就开启了逃逸分析,可以通过选项-XX:+PrintEscapeAnalysis查看逃逸分析的筛选结果。

    如果是逃逸分析出来的对象可以在栈上分配的话,那么该对象的生命周期就跟随线程了,就不需要垃圾回收,如果是频繁的调用此方法则可以得到很大的性能提高。

    逃逸分析的几种情况:

    public class EscapeAnalysisTest {
        static V global_v;
        public void a_method() {
            V v = b_method();
            c_method();
        }
        public V b_method() {
            V v = new V();
            return v;
        }
        public void c_method() {
            global_v = new V();
        }
    }
    

    采用了逃逸分析后,满足逃逸的对象在栈上分配

    /**
     * 逃逸分析-栈上分配
     * -XX:-DoEscapeAnalysis
     *
     * @author macfmc
     * @date 2020/8/1-19:57
     */
    public class EscapeAnalysisTest {
        private static class Stu {
            String a;
            int b;
            public Stu(String a, int b) {
                this.a = a;
                this.b = b;
            }
        }
        public static void alloc() {
            Stu stu = new Stu("小明", 22);
        }
        public static void main(String[] args) {
            long b = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
                alloc();
            }
            long e = System.currentTimeMillis();
            System.out.println(e - b);
        }
    }
    

    运行结果:没有开启逃逸分析,对象都在堆上分配,会频繁触发垃圾回收(垃圾回收会影响系统性能),导致代码运行慢。

    // 1、参数为:-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC
    [GC (Allocation Failure)  2048K->728K(9728K), 0.0017996 secs]
    [GC (Allocation Failure)  2776K->696K(9728K), 0.0013323 secs]
    10
    
    // 2、参数为:-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC
    [GC (Allocation Failure)  2760K->712K(9728K), 0.0004889 secs]
    ...疯狂GC
    [GC (Allocation Failure)  2760K->712K(9728K), 0.0004889 secs]
    [GC (Allocation Failure)  2760K->712K(9728K), 0.0003785 secs]
    [GC (Allocation Failure)  2760K->712K(9728K), 0.0008545 secs]
    1955

    总结:1、栈上分配可以提升代码性能,降低在多线程情况下的锁使用,但是会受限于其空间的大小。
    2、进行逃逸分析之后,产生的后果是所有的对象都将由栈上分配,而非从JVM内存模型中的堆来分配。
    3、栈上分配可以提升代码性能,降低在多线程情况下的锁使用,但是会受限于其空间的大小。
    4、分析找到未逃逸的变量,将变量类的实例化内存直接在栈里分配(无需进入堆),分配完成后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。
    5、能在方法内创建对象,就不要再方法外创建对象。

    TLAB(线程本地分配缓冲)

    TLAB的全称是Thread Local Allocation Buffer,即线程本地分配缓存区。

    为什么需要TLAB?
    创建对象时,需要在堆上为新生的对象申请指定大小的内存,如果同时有大量线程申请内存的话,可以通过锁机制确保不会申请到同一块内存,在JVM运行中,内存分配是一个极其频繁的动作,使用锁这种方式势必会降低性能。
    所以就出现了TLAB,JVM通过使用TLAB来避免多线程冲突,每个线程使用自己的TLAB,这样就保证了不使用同步,也不会出现线程安全问题,提高了对象分配的效率。(为新对象分配内存空间时,让每个 Java 应用线程能在使用自己专属的分配指针来分配空间,减少同步开销。)

    TLAB是什么?
    TLAB本身占用eden区空间,在开启TLAB的情况下,虚拟机会为每个Java线程分配一块TLAB空间。参数-XX:+UseTLAB开启TLAB,默认是开启的。
    TLAB空间的内存非常小,缺省情况下仅占有整个Eden空间的1%,当然可以通过选项-XX:TLABWasteTargetPercent设置TLAB空间所占用Eden空间的百分比大小。
    TLAB只是让每个线程有私有的分配指针,但底下存对象的内存空间还是给所有线程访问的,只是其它线程无法在这个区域分配而已。当一个TLAB用满,就新申请一个TLAB,而在老TLAB里的对象还留在原地什么都不用管——它们无法感知自己是否是曾经从TLAB分配出来的,而只关心自己是在eden里分配的。
    TLAB空间由于比较小,因此很容易装满。比如,一个100K的空间,已经使用了80KB,当需要再分配一个30KB的对象时,肯定就无能为力了。这时虚拟机会有两种选择,第一,废弃当前TLAB,这样就会浪费20KB空间;第二,将这30KB的对象直接分配在堆上,保留当前的TLAB,这样可以希望将来有小于20KB的对象分配请求可以直接使用这块空间。实际上虚拟机内部会维护一个叫作refill_waste的值,通俗一点来说就是可允许浪费空间的值,当TLAB剩余的空间小于新申请对象的大小,且这个剩余的空间大于refill_waste(可允许浪费空间的值)时,会选择在堆中分配(保留当前的TLAB);若剩余的空间小于refill_waste(可允许浪费空间的值)时,则会废弃当前TLAB,新建TLAB来分配对象。这个阈值可以使用TLABRefillWasteFraction来调整,它表示TLAB中允许产生这种浪费的比例。默认值为64,即表示使用约为1/64的TLAB空间作为refill_waste。默认情况下,TLAB和refill_waste都会在运行时不断调整的,使系统的运行状态达到最优。

    再举两个通俗易懂的例子帮助理解:大家可以花两分钟时间跟着下边的例子算一下,算完后,对refill_waste会有更到位的理解

    假设TLAB大小为100KB,refill_waste(可允许浪费空间的值)为5KB
      1、假如当前TLAB已经分配96KB,还剩下4KB,但是现在new了一个对象需要6KB的空间,显然TLAB的内存不够了,这时可以简单的重新申请一个TLAB,原先的TLAB交给Eden管理,这时只浪费4KB的空间,在refill_waste 之内。
      2、假如当前TLAB已经分配90KB,还剩下10KB,现在new了一个对象需要11KB,显然TLAB的内存不够了,这时就不能简单的抛弃当前TLAB,因为此时抛弃的话,就会浪费10KB的空间,10KB是大于咱们设置的refill_waste(可允许浪费空间的值)5KB的,所以此时会保留当前的TLAB不动,会把这11KB会被安排到Eden区进行申请。

    总结

    名称针对点处于对象分配流程的位置
    栈上分配避免gc无谓负担1
    TLAB加速堆上对象的分配

    2

    对象分配流程图

    相信大家已经掌握JVM是如何自动进行内存管理,本文适合点赞+收藏。有什么错误希望大家直接指出~

    参考与致谢:用大白话来聊一聊Java对象的栈上分配和TLAB享学课堂第三期JVM

    展开全文
  • 很多人都知道C++类是由结构体发展得来的,所以他们的成员变量(C语言的结构体只有成员变量)的内存分配机制是一样的。下面我们以类来说明问题,如果类的问题通了... 一个类对象的地址就是类所包含的这一片内存空间
  • 内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在堆上分配”。这里没有说所有的对象都在堆上进行分配,而是使用了“几乎所有”一词进行描述,那么今天就来简单聊一聊,除了堆以外的对象分配。 通过前...
  • 在堆栈中分配内存空间

    千次阅读 2014-03-24 12:01:32
    1.申请方式  stack:   由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间  heap:   需要程序员自己申请,并指明大小,在c中malloc函数  
  • malloc()函数实现内存动态分配

    千次阅读 2020-12-26 17:07:42
    1.使用malloc()函数为数组分配内存 2.释放动态分配内存 3. 其他动态内存分配函数
  • 上次面试中被问到了static和final修饰的变量的内部存储空间,虽然我答出来了,但是我也是有点不确定,于是准备总结一波!运行时的数据区1.程序计数器:我们在程序中无法控制。最快的保存区域,位于处理器内部,由...
  • c++对象内存分配问题

    2014-11-23 11:21:59
    用C或C++写程序,需要更多地关注内存,这不仅仅是因为内存分配是否合理直接影响着程序的效率和性能,更为主要的是,当我们操作内存的时候一不小心就会出现问题,而且很多时候,这些问题都是不易发觉的,比如内存...
  • 对象分配策略

    2021-03-25 19:36:14
    对象的分配策略对象的分配原则对象优先在Eden区分配大对象直接进入老年代长期存活对象进入老年区对象年龄动态判断空间分配担保逃逸分析逃逸分析的原理 对象的分配原则 对象优先在Eden区分配 大多数情况下,对象在...
  • 创建对象 当 JVM 收到一个 new 指令...使用指针碰撞的前提是堆内存是完全工整的,用过的内存和没用的内存各在一边每次分配的时候只需要将指针向空闲内存一方移动一段和内存大小相等区域即可。 当堆中已经使用的内存
  • 计算机内存分配

    千次阅读 2015-03-03 23:33:22
    计算机内存分配,程序的内存空间,堆与栈的比较,new/delete与malloc/free详解
  • 文章目录1 内存概念1.1 内存作用1.2 逻辑地址VS物理地址1.3 装入的三种方式1.4 链接的三种方式1.5 内存的基础知识小结2 ...内存空间分配2.4.1 单一连续分配2.4.2 固定分区分配2.4.3 动态分区分配2.4.4 连续分配管理小结...
  • vector的数据安排以及操作方式,与array非常相似。两者的唯一区别在于空间的运用的灵活性。array是静态空间,一旦配置了就不能改变;要换个大(或小)一点的房子,可以,一切琐细都得...
  • 对于计算机程序设计而言,变量和对象内存中的分配都是编译器在编译程序时安排好的,这带来了极大的不便,如数组必须大开小用,(大开小用就是现在定义一个数组容量是100,实际使用了10,造成了极大的内存浪费),...
  • 当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配...
  • 编译原理(7):运行存储分配

    千次阅读 2020-02-16 13:40:08
    对于那些在编译时刻就可以确定大小的数据对象,可以在编译时刻就为它们分配存储空间,这样的分配策略称为静态存储分配 反之,如果不能在编译时完全确定数据对象的大小,就要采用动态存储分配的...
  • C++内存空间分配

    2013-09-24 15:52:36
    在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。  栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等...
  • 对于那些在编译时刻就可以确定大小的数据对象,可以在编译时刻就为它们分配存储空间,这样的分配策略称为静态存储分配 如果不能在编译时完全确定数据对象的大小,就要采用动态存储分配的策略。即在编译时仅产生各种...
  • c 程序内存分配管理

    千次阅读 2018-09-23 15:44:33
    给变量分配内存空间可分为静态内存分配和动态内存分配。 静态内存分配属于编译时给变量分配空间,动态分配属于在程序运行时给变量分配空间 静态分配属于栈分配,动态分配属于堆分配 运行效率上,静态内存比...
  • 内存分配的概念

    千次阅读 2018-03-08 11:35:05
    我们写过很多c/c++代码(或者其他编程语言),然后通过编译器进行编译再运行一个程序,要使用数据对象(例如变量、类对象)是得要分配内存的,而大家可能不太熟悉这些数据对象是如何分配的。那么接下来,笔者为大家...
  • 说明:以32位系统为例,以class举例说明(C++的struct和class基本是一样的)内存分配的问题。...new需要分配不同的内存地址,不能分配内存大小为0的空间 避免除以 sizeof(T)时得到除以0错误 故使...
  • 数组的基本认识和内存分配

    千次阅读 2018-08-10 17:21:14
    1.栈是如何分配内存的 栈可以算是受限的线性表。它只允许在表的一端进行数据的输入,删除操作。放入数据的入口即为栈的栈顶,将数据压入栈后,数据所存在的位置即为栈的栈。它在进行插入和删除工作时必须从栈顶元素...
  • windows下 内存区域的分配

    千次阅读 2017-02-15 10:39:15
    内存分配方式: 1. 静态存储分配  内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。  2. 栈区分配 在执行函数时,函数内局部变量的存储单元都可以在栈上...
  • 对象创建与内存分配

    2018-08-07 09:21:03
    对象的创建与内存分配 创建对象 当 JVM 收到一个 new 指令时,会检查指令中的参数在常量池是否有这个符号的引用,还会检查该类是否已经被 加载过了,如果没有的话则要进行一次类加载。 接着就是分配内存了,...
  • C语言变量声明及内存分配 一个由c/C++编译的程序...2、堆区(heap) — 在内存开辟另一块存储区域。一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类
  • c++ STL容器的内存分配

    千次阅读 2016-10-27 23:59:59
    要了解问题的原因,我们就要了解C++中stl容器的内存分配策略。我们才知道在哪些操作下可能导致迭代器失效,引用(指针)失效。二.问题分类首先我们把以上的问题分成两类: 容器的迭代器为什么会失效? 容器元素的引用...
  • C++对象内存模型

    千次阅读 2016-11-21 14:37:07
    谈VC++对象模型 (美)简.格雷 程化 译 译者前言 一个C++程序员,想要进一步提升技术水平的话,应该多了解一些语言的语意细 节。对于使用VC++的程序员来说,还应该了解一些VC++对于C++的诠释。 Inside the ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 31,823
精华内容 12,729
关键字:

对象内存空间分配安排