精华内容
下载资源
问答
  • 统计对象被创建次数

    千次阅读 2018-12-04 17:38:16
    做法是:在的构造方法里面,写一个静态变量的count++,后面每创建一个对象时,均会自加一. public class Student{ int name; static count = 0; public Student(){ count++; } } public class Test{ public ...

    在被创建的对象个数很多的时候,我们需要知道这些对象个数.
    做法是:在类的构造方法里面,写一个静态变量的count++,后面每创建一个对象时,均会自加一次.

    public class Student{
    	int name;
    	static count = 0;
    	public Student(){
    		count++;
    	}
    }
    public class Test{
    	public static void main(String[] args){
    		Student s1 = new Student();
    		Student s2 = new Student();
    		Student s3 = new Student();
    		System.out.println(s3.count);	//s1调用也可
    	}
    }
    
    展开全文
  • Objects, Classes and ClassLoaders 对象(Objects),(Classes)以及加载器(Class...所以每一个对象都有一个到java.lang.Class(用于描述对象的结构)的实例的引用。 Person boss = new Person(); ...

    Objects, Classes and ClassLoaders

    对象(Objects),类(Classes)以及类加载器(ClassLoaders)

    在Java中一切皆是对象(Object),并且所有对象都是由它们的类(Class)指定的。所以每一个对象都有一个到java.lang.Class(用于描述对象的结构)的实例的引用。

    Person boss = new Person();

    Java虚拟机(JVM)需要理解待创建对象的结构,为此,JVM会查找叫Person的类。并且,如果在程序的这一次运行中,Person类是第一次被访问的话,它必须要被JVM加载(loaded),通常这是从对应的Person.class文件加载的。从磁盘上查找Person.class文件、将其加载到内存中,并解析它的结构的过程,就叫作类的加载(class loading)。保证合适的类被加载是类加载器(ClassLoader)的职责。ClassLoader是java.lang.ClassLoader类的实例,并且Java程序中的每一个类都必须被一些ClassLoader加载,每一个classloader都持有它所加载的所有类的引用。

    Java中一个实例对象被创建的过程

    一、类的加载过程

    首先,Jvm在执行时,遇到一个新的类时,会到内存中的方法区去找class的信息,如果找到就直接拿来用,如果没有找到,就会去将类文件加载到方法区。在类加载时,静态成员变量加载到方法区的静态区域,非静态成员变量加载到方法区的非静态区域。

    静态代码块是在类加载时自动执行的代码,非静态代码块是在创建对象时自动执行的代码,不创建对象不执行该类的非静态代码块。

    加载过程:

    1、JVM会先去方法区中找有没有相应类的.class存在。如果有,就直接使用;如果没有,则把相关类的.clss加载到方法区。

    2、在.class加载到方法区时,先加载父类再加载子类;先加载静态内容,再加载非静态内容

    3、加载静态内容:

    • 把.class中的所有静态内容加载到方法区下的静态区域内
    • 静态内容加载完成之后,对所有的静态变量进行默认初始化
    • 所有的静态变量默认初始化完成之后,再进行显式初始化
    • 当静态区域下的所有静态变量显式初始化完后,执行静态代码块

    4、加载非静态内容:把.class中的所有非静态变量及非静态代码块加载到方法区下的非静态区域内。

    5、执行完之后,整个类的加载就完成了。

    对于静态方法和非静态方法都是被动调用,即系统不会自动调用执行,所以用户没有调用时都不执行,主要区别在于静态方法可以直接用类名直接调用(实例化对象也可以),而非静态方法只能先实例化对象后才能调用。

     

    二、对象的创建过程

    1、new一个对象时,在堆内存中开辟一块空间。

    2、给开辟的空间分配一个地址。

    3、把对象的所有非静态成员加载到所开辟的空间下。

    4、所有的非静态成员加载完成之后,对所有非静态成员变量进行默认初始化。

    5、所有非静态成员变量默认初始化完成之后,调用构造函数。

    6、在构造函数入栈执行时,分为两部分:先执行构造函数中的隐式三步,

            ====①执行super()语句   ②对开辟空间下的所有非静态成员变量进行显示初始化  ③执行构造代码块====

    再执行构造函数中书写的代码。

    7、在整个构造函数执行完并弹栈后,把空间分配的地址赋给引用对象。

    注:  super语句,可能出现以下三种情况:

    1)构造方法体的第一行是this()语句,则不会执行隐式三步,而是调用this()语句所对应的的构造方法,最终肯定会有第一行不是this语句的构造方法。

    2)构造方法体的第一行是super()语句,则调用相应的父类的构造方法, 

    3)构造方法体的第一行既不是this()语句也不是super()语句,则隐式调用super(),即其父类的默认构造方法,这也是为什么一个父类通常要提供默认构造方法的原因。

    转载自:Java类的加载和对象创建流程的详细分析

     

    Java的内存泄漏基本上按照内存区域的划分可以分为:

    1. 堆(heap)内存泄漏:大家都比较熟悉

    2. 栈(stack)内存泄漏:当前线程运行期间维护的中间变量等信息过多,例如常见的死循环引起stack over flow

    3. 方法区(permanent heap)内存泄漏:分析其原因的文章较少,本文的着重点。

    运行时数据区分类

    Java虚拟机的运行时数据区一般分类如下(不一定是物理划分):

    1. 堆:主要存放对象实例,线程共享
    2. 栈:主要存储特定线程的方法调用状态,线程独占
    3. 本地方法栈:存储本地方法的调用状态,线程独占
    4. PC寄存器:学过操作系统课程的都知道,线程独占
    5. 方法区:主要存储了类型信息,线程共享

    方法区可以简单的等价为所谓的PermGen区域(永久存储区),在很多虚拟机相关的文档中,也将其称之为"永久堆"(permanent heap),作为堆空间的一部分存在。介于此,我们可以简单说明一下我们常用的几个堆内存配置的参数关系:
    *-XX: PermSize:*永久堆(Pergen区域)大小默认值
    *-XX:MaxPermSize:*永久堆(Pergen区域)最大值
    *-Xms:*堆内存大小默认值
    *-Xmx:*堆内存最大值

    运行时数据区访问方式总结

    从开发者角度,虚拟机运行时数据区的访问方式简要归纳如下:

    1. 活动的线程可以通过对应的栈来访问运行时数据区信息
    2. 栈是堆访问的入口
    3. 堆上Java.lang.Class实例是访问PermGen区域中类型信息的入口 

    PermGen OOM原因总结

    通过上面的测试程序分析,我们发现PermGen OOM发生的原因和类型装载、类型卸载有直接的关系,可以对PermGen OOM发生的原因做如下大致的总结:

    1. 为PermGen区域分配的堆空间过小,可以通过合理的设置-XX: PermSize参数和-XX:MaxPermSize参数来解决。
    2. 类型卸载不及时,过时无效的类型信息占用了空间,我们不妨称其为"永久堆"的内存泄漏,需要通过深入分析类型卸载的原理来寻找对应的防范措施。

    常见的类加载器和类型卸载的可能性总结

         通过前面的讨论,我们知道如果加载某种类型的类加载器实例没有处于unreachable状态,则该类型就不会被卸载,该类型不被卸载,则对应的类型信息在PermGen区域中占有的堆内存就不会被释放。下面,针对典型的Java应用分类,分析一下常用类加载器加载的类型被下载的可能性。


    【普通Java应用】
    系统类加载器:由于其负责加载虚拟机的核心类型,所以由其加载的类型在整个程序运行期间不可能被卸载,对应类型信息占用的PermGen区域堆空间不可能得到释放。
    扩展类加载器:负责加载JDK扩展路径下的类型,扩展类加载器同时又作为系统类加载器的父类加载器,所以,由其加载的类型在整个程序运行期间基本上不可能被卸载,对应类型信息占用的PermGen区域堆空间基本不可能得到释放。
    系统类加载器:负责加载程序类路径上面的类型,由其加载的类型在整个程序运行期间基本上不可能被卸载,对应类型信息占用的PermGen区域堆空间基本不可能得到释放。
    用户自定义类加载器:对于其加载的类型,满足类型卸载要求的可能性比较容易控制,只要是其实例本身处于unreachable状态,其加载的类型会被卸载,PermGen区域中对应的空间占有也会被释放。

     

    【插件开发】
    系统类加载器:由于其负责加载虚拟机的核心类型,所以由其加载的类型在插件应用运行期间不可能被卸载,对应类型信息占用的PermGen区域堆空间不可能得到释放。
    插件类加载器:系统插件类加载器负责加载OSGI实现的相关类型,所以由其加载的类型在插件应用运行期间不可能被卸载;用户开发的插件所使用的默认插件类加载器,和特定的插件本身进行域绑定,插件之间存在一定的类型引用关系,并且特定插件在整个插件应用的运行时被停止的可能性也很小,所以类型卸载发生几率极小。
    用户自定义类加载器:对于其加载的类型,满足类型卸载要求的可能性比较容易控制,只要是其实例本身处于unreachable状态,其加载的类型会被卸载,PermGen区域中对应的空间占有也会被释放。

    PermGen内存溢出的应对措施

         通过上面的PermGen OOM的原因的分析,不难看出对应的应对措施:

    1. 合理的设置-XX: PermSize和-XX:MaxPermSize参数(主要的有效措施)
    2. 有效的利用的虚拟机类型卸载的机制(针对程序进行调优)

    合理设置参数(针对普通用户和开发者)

         通过设置合理的XX: PermSize和-XX:MaxPermSize参数值是减少和有效避免PermGen OOM发生的最有效最主要的措施,尤其是针对普通用户而言,这基本上是唯一的办法。关于合理设置这两个参数,建议如下:

    1. XX: PermSize参数的设置尽量建立在基准测试的基础之上,可以利用监控工具对稳定运行期间PermGen区域的大小进行统计,取合理的平均值。网上的很多资料中,建议XX: PermSize和XX:MaxPermSize设置为相同的数值,个人觉得这是不正确的,因为两个参数的出发点是不一样的。XX: PermSize设置的过大肯定会在应用运行的大部分时间中浪费堆内存,有可能会明显增加存放普通对象实例的堆空间的垃圾收集的次数。
    2. XX:MaxPermSize参数的设置应该着眼于PermGen区域使用的峰值,因为这是避免PermGen OOM的最后一道屏障,其设置最好也是建立在性能监控工具的统计结果之上。
    3. 和虚拟机有关的性能参数较多的分为两类,一类是初始值或默认值,一类是峰值。如果该性能参数是会涉及到的虚拟机垃圾收集机制的,关于初始值或者默认值的设置尽量要建立在测试基础之上,尽量做到在单次垃圾收集时间和垃圾收集频率之间保持一个平衡,否则很有可能适得其反。

    有效利用虚拟机类型卸载机制(针对开发者)

    此部分的建议可以作为开发者进行性能调优或者日常开发时候的参考,尽量能够配合相应的性能监控工具进行:

    1. 检查是否由于程序设计本身上的缺陷,导致加载了大量实际上并不需要的类型。较新版本的Java虚拟机实现,一般都遵循动态解析的建议,所以不是人为设计的缺陷,一般不会诱发加载了大量实际上并不需要的类型。结合插件开发的应用场景,个人觉得插件功能模块的划分(其中包括了插件依赖关系的设计和有关扩展点的扩展收集等)和第三方jar的使用可能是诱发此问题的两个重要根源。
    2. 对象缓存的使用是否得当,通过前面的分析,我们知道这可能是导致类型不能被卸载的重要原因。缓存的使用,既要认识到其可以提高时间性能的有点,也要分析其可能会给普通对象堆空间和PermGen区域造成的负担。
    3. 自定义类加载器的合理使用,相关的几个注意要点包括:
      1. 是否不恰当的利用的类型更新的特性,也就是说是否对类加载器实例的unreachable状态做了有效的判断。考虑如下场景,假设用户开发了一个自定义类加载器来加载工程输出目录下的临时类型,对临时类型做了不必要的缓存,这肯定会导致所有被加载过的临时类型都不会得到卸载,会直接加重PermGen区域的负担。
      2. 自定义类加载器和其他已有类加载器的协作关系是否合理,是否合理的利用了Java类加载的双亲委派机制。我们知道,不同的类加载器实例(哪怕是同一种类加载器类型的不同实例)加载的同一种自定义类型在虚拟机内部都会被放置到不同的命名空间中作为不同类型来处理,所以合理的设置父类加载器变得很重要,不合理的设置会导致大量不必要的"新"类型被创造出来,况且这些不必要的"新"类型是否能够被及时卸载还是个未知数。
    4. 慎重检查自定义类加载器实例是否被不恰当的缓存了,原因不言而喻。

    转载自:

     

     

    展开全文
  • C++对象数组

    万次阅读 多人点赞 2018-05-14 23:47:15
    //有10元素的学生类对象数组 2:对象数组的访问形式:数组名[下标].成员名; Eg: asa[j].print(); 3:对象数组的初始化:对象数组的初始化通常也是由构造函数来完成的。 Eg: #include”student.h” int main ...

    C++类和对象数组

    1】对象数组

    1:对象数组的定义:类名 数组名[元素个数]

    Eg: student asa[10];//10个元素的学生类对象数组

    2:对象数组的访问形式:数组名[下标].成员名;

    Eg: asa[j].print();

    3:对象数组的初始化:对象数组的初始化通常也是由构造函数来完成的。

    Eg: #include”student.h”

    int main

    {

    student asa[4]={student1,“LiMing,98,student(2,”Bill”,89),student(3,”Chlily”,99),student(4,”Saraea”,96)};

    for(int i=0;i<4;i++)

    asa[i].print();

    return 0;

    }

    2】指向对象的指针

    1:对象指针的用法:定义对象指针的语法和定义其它基本数据类型指针的语法相同。在使用对象指针之前,要把它指向一个已经创建的对象,然后才能访问该对象的成员。在使用对象名来访问对象成员时,要使用点运算符“.。如果要通过指向对象的指针来访问对象的成员,那么必须使用箭头运算符“->

    3this指针

        Eg: class student

    {

        int number;

        char name[15];

        float score;

        void display()

    {

        cout<<”number: ”<<number;

    cout<<”name: <<name;

    cout<<”score: <<score;<<endl;

    }

    };

    1:在成员函数display()中有一个隐藏的指针作为参数,该指针参数使用关键字this命名,它是指向该类对象的指针,成员函数可以通过它来访问该类对象的数据成员。voiddisplay(student *this)//this指针由编译器自动生成。

    2this指针是由C++编译器自动产生的一个隐含指针,编译器将该指针自动插入到类的所有成员函数的参数列表中,成员函数使用该指针来存取对象的数据成员。

    3:内存中只有一份成员函数的备份,而且对象的实例变量和成员函数是分开存放的,egX const *this。当对象调用该函数时,编译器自动将该对象的地址作为实参传递给相应的成员函数的this指针。

    Eg:void date::set(date* constthis,int d,int m,int y)

    {

        this->day=d;

        this->month=m;

        this->year=y;

    }

    注意:this指针由编译器自动生成,程序员不能将this指针的说明写在函数中,否则将出现编译时间错误信息。

    4this指针是系统的一个内部指针,通常以隐式方式使用,但也可以被程序员显式使用,例如,当运算符重载和建立双向链表时,显式使用this指针十分必要。

    4】对象的赋值:如果两个对象属于同一种类类型,换句话说,两个对象是由同一个类声明的,那么我们可以将其中的一个对象的值(属性值)赋给另一个对象。默认情况下,当将一个对象赋值给另一个对象时,第一个对象的数据将被按位复制到第二个对象中。对象的赋值仅仅是使两个对象的值相等,而这两个对象仍然是相互独立的,即修改一个对象的值将不会影响到另一个对象。

    Eg:Rectangle rect1(10,20),rect2(0,0);

        rect2=rect1;

    5】对象作为函数参数:分类:传值调用,传址调用

        1:传值调用:传值调用,传送的是作为实际参数的对象的拷贝而不是实际对象的本身。因此在函数中改变对象的值不会影响作为实际参数对象本身。

        Class Square

    {

            int side;

        public:

           void set(int x) {side=x;}

           void out() {cout<<side<<”\n”;}

    };

    void f(square x)

    {

        x.out();

        x.set(100);

        x.out();

    }

        int main()

    {

        Square s;

        s.set(10);

        f(s);

        s.out();

    return 0;

    }  

        2:传址调用:当函数调用时传递的是作为实际参数的对象的地址,就是传址调用方式。在函数中对作为形式参数的对象的修改实际上就是对作为实际参数的对象的修改。我们既可以使用对象的引用来实现传址调用,也可以用指针来实现。

    使用对象的引用来实现传址调用:

    Class Square

    {

            int side;

        public:

           void set(int x){side=x;}

           void out() {cout<<side<<”\n”;}

    };

    void f(square &x)//对象的引用作为函数的参数

    {

        x.out();

        x.set(100);

        x.out();

    }

        int main()

    {

        Square s;

        s.set(10);

        f(s);

        s.out();

    return 0;

    }

    使用指向对象的指针来实现传址调用的例子

    Class Square

    {

            int side;

        public:

           void set(int x){side=x;}

           void out() {cout<<side<<”\n”;}

    };

    void f(square *x)

    {

        x.out();

        x.set(100);

        x.out();

    }

        int main()

    {

        Square s;

        s.set(10);

        f(&s);

        s.out();

    return 0;

    }

    6】从函数返回对象:当函数返回对象时,函数创建了一个临时对象来保存要返回的值,而函数所返回的对象实际上是这个临时对象。在对象的值被返回之后,临时对象将被销毁。

        Eg:mystring input()

    {

        char instr[80];

        mystring str;

       cin>>instr;

    str.set(instr);

    return str;//返回一个mystring对象

    }

    int main()

    {

        Mystring ob;

        ob=.input();

        ob.print()

        return 0

    }

    7】类的静态成员

    1:静态成员:C中通过在变量定义的前面加static关键字就可以将变量声明为静态变量。采用局部静态变量可以减少全局变量的使用:将全局变量声明为静态的可以降低全局变量的副作用。C++中通过在类的成员函数前面加上static关键字将其成员声明为静态成员,一个类的静态成员既可以是静态数据成员,也可以是静态成员函数。

    1. 静态数据成员

      声明类的静态数据成员的方式是在变量定义的前面加static关键字

      classstudent

      {

        private:

            int num;

            char *name;

            float score;

            static int count;

            static float sum;//类student的静态数据成员的声明

        public:

            ……

      };

      intstudent::count=0;

      floatstudent::sum=0; //静态成员变量应在创建类的对象前定义并进行初始化

      一个类的静态数据成员为该类的所有对象所共有,不管创造了这个类的多少个对象,其静态数据成员在内存中只有一份拷贝。由于只有一个类的静态成员只有一份拷贝,所以我们经常使用“类名::”来访问静态数据成员。例如:counter::count=0;

          静态成员变量的最一般的作用就是对一些共享资源提供存取控制。

      静态成员好处就是减少了全局变量的使用。

      静态成员主要作用就是定义类的各个对象所共用的数据,如统计总数,平均数等。

     2:静态成员函数:在类的成员函数前面加上static关键字说明的函数就是静态成员函数。只使用静态数据成员的函数可以定义为静态成员函数。对静态成员函数的引用使用所属的类名加作用域分辨运算符来限制,即“类名::”。

    Eg: classStudent

    {

        private:

           ……

        public:

    static void init()

    {

                   Student::count=0;

                  Student::sum=0;

    }

    Static float average();

    };

    float student::average()

    {

        cout<<”sum is <<sum<<\tcount is <<count<<endl;

        cout<<”average is”<<sum/count<<endl;

        return sum/count;

    }

    静态成员函数可以在创建该类的任何成员之前处理静态数据成员,这是普通成员函数不能实现的。一般来说,类的静态成员函数是用来访问静态数据成员的,当然可以访问全局变量。静态成员函数不具有this指针。也就是说,静态成员函数不与某个具体的对象相联系,它只属于该类。

    8】类的友元

    1:友元函数:将关键字friend放在函数名的前面就将该函数说明为友元函数。一个类的友元函数是在该类中说明的一个非成员函数,但允许访问该类对象的所有成员。友元函数可以访问类的私有成员,节省了调用成员函数的开销,从而提高了程序的运行效率。如果需要一个函数同时访问多个类,则它可以定义为多个类的友元函数。1)使用友元破坏了封装性,降低了程序的可维护性。因此使用友元时要慎重的权衡得失后再决定。2)因为友元函数不是类的成员,故在友元函数中没有this指针,因此必须通过函数参数来传递对象。

    2:友元类:若一个类为另一个类的友元,则次类的所有成员函数都能访问对方类的私有成员。下例所示:类B的所有成员函数都是类A的友元函数,都能够访问类A的所有成员。但是类A不是类B的友元类

    Eg:class A

    {

        ……

    public:

        friend class B;

        ……

    };

     

    展开全文
  • Dalvik虚拟机为新创建对象分配内存的过程分析

    万次阅读 热门讨论 2014-12-08 01:00:57
    在Java堆为对象分配内存需要解决内存碎片和内存不足两问题。要解决内存碎片问题,就要找到块大小最合适的空闲内存分配给对象使用。而内存不足有可能是内存配额用完引起的,也有可能是垃圾没有及时回收引起的,要...

            在前面一文中,我们分析了Dalvik虚拟机创建Java堆的过程。有了Java堆之后,Dalvik虚拟机就可以在上面为对象分配内存了。在Java堆为对象分配内存需要解决内存碎片和内存不足两个问题。要解决内存碎片问题,就要找到一块大小最合适的空闲内存分配给对象使用。而内存不足有可能是内存配额用完引起的,也有可能是垃圾没有及时回收引起的,要区别对待。本文就详细分析Dalvik虚拟机是如何解决这些问题的。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

           内存碎片问题其实是一个通用的问题,不单止Dalvik虚拟机在Java堆为对象分配内存时会遇到,C库的malloc函数在分配内存时也会遇到。Android系统使用的C库bionic使用了Doug Lea写的dlmalloc内存分配器。也就是说,我们调用函数malloc的时候,使用的是dlmalloc内存分配器来分配内存。这是一个成熟的内存分配器,可以很好地解决内存碎片问题。关于dlmalloc内存分配器的设计,可以参考这篇文章:A Memory Allocator

           前面Dalvik虚拟机垃圾收集机制简要介绍和学习计划一文提到,Dalvik虚拟机的Java堆的底层实现是一块匿名共享内存,并且将其抽象为C库的一个mspace,如图1所示:

    图1 Dalvik虚拟机Java堆

          于是,Dalvik虚拟机就很机智地利用C库里面的dlmalloc内存分配器来解决内存碎片问题!

          为了应对可能面临的内存不足问题,Dalvik虚拟机采用一种渐进的方法来为对象分配内存,直到尽了最大努力,如图2所示:

    图2 Dalvik虚拟机为对象分配内存的过程

            接下来,我们就详细分析这个过程,以便可以了解Dalvik虚拟机是如何解决内存不足问题的,以及分配出来的内存是如何管理的。

            Dalvik虚拟机实现了一个dvmAllocObject函数。每当Dalvik虚拟机需要为对象分配内存时,就会调用函数dvmAllocObject。例如,当Dalvik虚拟机的解释器遇到一个new指令时,它就会调用函数dvmAllocObject,如下所示:

    HANDLE_OPCODE(OP_NEW_INSTANCE /*vAA, class@BBBB*/)
        {
            ClassObject* clazz;
            Object* newObj;
    
            EXPORT_PC();
    
            vdst = INST_AA(inst);
            ref = FETCH(1);
            ......
            clazz = dvmDexGetResolvedClass(methodClassDex, ref);
            if (clazz == NULL) {
                clazz = dvmResolveClass(curMethod->clazz, ref, false);
                ......
            }
    
            ......
    
            newObj = dvmAllocObject(clazz, ALLOC_DONT_TRACK);
            ......
    
            SET_REGISTER(vdst, (u4) newObj);
        }
        FINISH(2);
    OP_END
            这个代码段定义在文件dalvik/vm/mterp/out/InterpC-portable.cpp中。

            关于Dalvik虚拟机的解释器的实现,可以参考Dalvik虚拟机的运行过程分析一文,上面这段代码首先是找到要创建的对象的类型clazz,接着以其作为参数调用函数dvmAllocObject为要创建的对象分配内存。另外一个参数ALLOC_DONT_TRACK是告诉Dalvik虚拟机的堆管理器,要分配的对象是一个根集对象,不需要对它进行跟踪。因为根集对象在GC时是会自动被追踪处理的。

            函数dvmAllocObject的实现如下所示:

    Object* dvmAllocObject(ClassObject* clazz, int flags)
    {
        Object* newObj;
    
        assert(clazz != NULL);
        assert(dvmIsClassInitialized(clazz) || dvmIsClassInitializing(clazz));
    
        /* allocate on GC heap; memory is zeroed out */
        newObj = (Object*)dvmMalloc(clazz->objectSize, flags);
        if (newObj != NULL) {
            DVM_OBJECT_INIT(newObj, clazz);
            dvmTrackAllocation(clazz, clazz->objectSize);   /* notify DDMS */
        }
    
        return newObj;
    }
            这个函数定义在文件dalvik/vm/alloc/Alloc.cpp中。

            函数dvmAllocObject调用函数dvmMalloc从Java堆中分配一块指定大小的内存给新创建的对象使用。如果分配成功,那么接下来就先使用宏DVM_OBJECT_INIT来初始化新创建对对象的成员变量clazz,使得新创建的对象可以与某个特定的类关联起来,接着再调用函数dvmTrackAllocation记录当前的内存分配信息,以便通知DDMS。

            函数dvmMalloc返回的只是一块内存地址,这是没有类型的。但是由于每一个Java对象都是从Object类继承下来的,因此,函数dvmAllocObject可以将获得的没有类型的内存块强制转换为一个Object对象。

            Object类的定义如下所示:

    struct Object {
        /* ptr to class object */
        ClassObject*    clazz;
    
        /*
         * A word containing either a "thin" lock or a "fat" monitor.  See
         * the comments in Sync.c for a description of its layout.
         */
        u4              lock;
    };
            这个类定义在文件dalvik/vm/oo/Object.h中。

            Object类有两个成员变量:clazz和lock。其中,成员变量clazz的类型为ClassObject,它对应于Java层的java.lang.Class类,用来描述对象所属的类。成员变量lock是一个锁,正是因为有了这个成员变量,在Java层中,每一个对象都可以当锁使用。

            理解了Object类的定义之后,我们继续分析函数dvmMalloc的实现,如下所示:

    void* dvmMalloc(size_t size, int flags)
    {
        void *ptr;
    
        dvmLockHeap();
    
        /* Try as hard as possible to allocate some memory.
         */
        ptr = tryMalloc(size);
        if (ptr != NULL) {
            /* We've got the memory.
             */
            if (gDvm.allocProf.enabled) {
                Thread* self = dvmThreadSelf();
                gDvm.allocProf.allocCount++;
                gDvm.allocProf.allocSize += size;
                if (self != NULL) {
                    self->allocProf.allocCount++;
                    self->allocProf.allocSize += size;
                }
            }
        } else {
            /* The allocation failed.
             */
    
            if (gDvm.allocProf.enabled) {
                Thread* self = dvmThreadSelf();
                gDvm.allocProf.failedAllocCount++;
                gDvm.allocProf.failedAllocSize += size;
                if (self != NULL) {
                    self->allocProf.failedAllocCount++;
                    self->allocProf.failedAllocSize += size;
                }
            }
        }
    
        dvmUnlockHeap();
    
        if (ptr != NULL) {
            /*
             * If caller hasn't asked us not to track it, add it to the
             * internal tracking list.
             */
            if ((flags & ALLOC_DONT_TRACK) == 0) {
                dvmAddTrackedAlloc((Object*)ptr, NULL);
            }
        } else {
            /*
             * The allocation failed; throw an OutOfMemoryError.
             */
            throwOOME();
        }
    
        return ptr;
    }
            这个函数定义在文件dalvik/vm/alloc/Heap.cpp中。

            在Java堆分配内存前后,要对Java堆进行加锁和解锁,避免多个线程同时对Java堆进行操作。这分别是通过函数dvmLockHeap和dvmunlockHeap来实现的。真正执行内存分配的操作是通过调用另外一个函数tryMalloc来完成的。如果分配成功,则记录当前线程成功分配的内存字节数和对象数等信息。否则的话,就记录当前线程失败分配的内存字节数和对象等信息。有了这些信息之后,我们就可以通过DDMS等工具来对应用程序的内存使用信息进行统计了。

            最后,如果分配内存成功,并且参数flags的ALLOC_DONT_TRACK位设置为0,那么需要将新创建的对象增加到Dalvik虚拟机内部的一个引用表去。保存在这个内部引用表的对象在执行GC时,会添加到根集去,以便可以正确地判断对象的存活。

            另一方面,如果分配内存失败,那么就是时候调用函数throwOOME抛出一个OOM异常了。

            我们接下来继续分析函数tryMalloc的实现,如下所示:

    static void *tryMalloc(size_t size)
    {
        void *ptr;
        ......
    
        ptr = dvmHeapSourceAlloc(size);
        if (ptr != NULL) {
            return ptr;
        }
    
        if (gDvm.gcHeap->gcRunning) {
            ......
            dvmWaitForConcurrentGcToComplete();
        } else {
            ......
            gcForMalloc(false);
        }
    
        ptr = dvmHeapSourceAlloc(size);
        if (ptr != NULL) {
            return ptr;
        }
    
        ptr = dvmHeapSourceAllocAndGrow(size);
        if (ptr != NULL) {
            ......
            return ptr;
        }
    
        gcForMalloc(true);
        ptr = dvmHeapSourceAllocAndGrow(size);
        if (ptr != NULL) {
            return ptr;
        }
       
        ......
    
        return NULL;
    }
            这个函数定义在文件dalvik/vm/alloc/Heap.cpp中。

            函数tryMalloc的执行流程就如图2所示:

            1. 调用函数dvmHeapSourceAlloc在Java堆上分配指定大小的内存。如果分配成功,那么就将分配得到的地址直接返回给调用者了。函数dvmHeapSourceAlloc在不改变Java堆当前大小的前提下进行内存分配,这是属于轻量级的内存分配动作。

            2. 如果上一步内存分配失败,这时候就需要执行一次GC了。不过如果GC线程已经在运行中,即gDvm.gcHeap->gcRunning的值等于true,那么就直接调用函数dvmWaitForConcurrentGcToComplete等到GC执行完成就是了。否则的话,就需要调用函数gcForMalloc来执行一次GC了,参数false表示不要回收软引用对象引用的对象。

            3. GC执行完毕后,再次调用函数dvmHeapSourceAlloc尝试轻量级的内存分配操作。如果分配成功,那么就将分配得到的地址直接返回给调用者了。

            4. 如果上一步内存分配失败,这时候就得考虑先将Java堆的当前大小设置为Dalvik虚拟机启动时指定的Java堆最大值,再进行内存分配了。这是通过调用函数dvmHeapSourceAllocAndGrow来实现的。

            5. 如果调用函数dvmHeapSourceAllocAndGrow分配内存成功,则直接将分配得到的地址直接返回给调用者了。

            6. 如果上一步内存分配还是失败,这时候就得出狠招了。再次调用函数gcForMalloc来执行GC。参数true表示要回收软引用对象引用的对象。

            7. GC执行完毕,再次调用函数dvmHeapSourceAllocAndGrow进行内存分配。这是最后一次努力了,成功与事都到此为止。

            这里涉及到的关键函数有三个,分别是dvmHeapSourceAlloc、dvmHeapSourceAllocAndGrow和gcForMalloc。后面一个我们在接下来一篇文章分析Dalvik虚拟机的垃圾收集过程时再分析。现在重点分析前面两个函数。

            函数dvmHeapSourceAlloc的实现如下所示:

    void* dvmHeapSourceAlloc(size_t n)
    {
        HS_BOILERPLATE();
    
        HeapSource *hs = gHs;
        Heap* heap = hs2heap(hs);
        if (heap->bytesAllocated + n > hs->softLimit) {
            ......
            return NULL;
        }
        void* ptr;
        if (gDvm.lowMemoryMode) {
            ......
            ptr = mspace_malloc(heap->msp, n);
            if (ptr == NULL) {
                return NULL;
            }
            uintptr_t zero_begin = (uintptr_t)ptr;
            uintptr_t zero_end = (uintptr_t)ptr + n;
            ......
            uintptr_t begin = ALIGN_UP_TO_PAGE_SIZE(zero_begin);
            uintptr_t end = zero_end & ~(uintptr_t)(SYSTEM_PAGE_SIZE - 1);
            ......
            if (begin < end) {
                ......
                madvise((void*)begin, end - begin, MADV_DONTNEED);
                ......
                memset((void*)end, 0, zero_end - end);
                ......
                zero_end = begin;
            }
            memset((void*)zero_begin, 0, zero_end - zero_begin);
        } else {
            ptr = mspace_calloc(heap->msp, 1, n);
            if (ptr == NULL) {
                return NULL;
            }
        }
    
        countAllocation(heap, ptr);
        ......
        if (gDvm.gcHeap->gcRunning || !hs->hasGcThread) {
            ......
            return ptr;
        }
        if (heap->bytesAllocated > heap->concurrentStartBytes) {
            ......
            dvmSignalCond(&gHs->gcThreadCond);
        }
        return ptr;
    }

            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            从前面Dalvik虚拟机Java堆创建过程分析一文可知,gHs是一个全局变量,它指向一个HeapSource结构。在这个HeapSource结构中,有一个heaps数组,其中第一个元素描述的是Active堆,第二个元素描述的是Zygote堆。

           通过宏hs2heap可以获得HeapSource结构中的Active堆,保存在本地变量heap中。宏hs2heap的实现如下所示:

    #define hs2heap(hs_) (&((hs_)->heaps[0]))
           这个宏定义在文件dalvik/vm/alloc/HeapSource.cpp中。

           在前面Dalvik虚拟机Java堆创建过程分析一文中,我们解释了Java堆有起始大小、最大值、增长上限值、最小空闲值、最大空闲值和目标利用率等参数。在Dalvik虚拟机内部,还有一个称为软限制(Soft Limit)的参数。堆软限制是一个与堆目标利率相关的参数。

           Java堆的Soft Limit开始的时候设置为最大允许的整数值。但是每一次GC之后,Dalvik虚拟机会根据Active堆已经分配的内存字节数、设定的堆目标利用率和Zygote堆的大小,重新计算Soft Limit,以及别外一个称为理想大小(Ideal Size)的值。如果此时只有一个堆,即只有Active堆没有Zygote堆,那么Soft Limit就等于Ideal Size。如果此时有两个堆,那么Ideal Size就等于Zygote堆的大小再加上Soft Limit值,其中Soft Limit值就是此时Active堆的大小,它是根据Active堆已经分配的内存字节数和设定的堆目标利用率计算得到的。

            这个Soft Limit值到底有什么用呢?它主要是用来限制Active堆无节制地增长到最大值的,而是要根据预先设定的堆目标利用率来控制Active有节奏地增长到最大值。这样可以更有效地使用堆内存。想象一下,如果我们一开始Active堆的大小设置为最大值,那么就很有可能造成已分配的内存分布在一个很大的范围。这样随着Dalvik虚拟机不断地运行,Active堆的内存碎片就会越来越来重。相反,如果我们施加一个Soft Limit,那可以尽量地控制已分配的内存都位于较紧凑的范围内。这样就可以有效地减少碎片。

           回到函数dvmHeapSourceAlloc中,参数n描述的是要分配的内存大小,而heap->bytesAllocated描述的是Active堆已经的内存大小。由于函数dvmHeapSourceAlloc是不允许增长Active堆的大小的,因此当(heap->bytesAllocated + n)的值大于Active堆的Soft Limit时,就直接返回一个NULL值表示分配内存失败。

            如果要分配的内存不会超过Active堆的Soft Limit,那么就要考虑Dalivk虚拟机在启动时是否指定了低内存模式。我们可以通过-XX:LowMemoryMode选项来让Dalvik虚拟机运行低内存模式下。在低内存模式和非低内存模块中,对象内存的分配方式有所不同。

            在低内存模式中,Dalvik虚拟机假设对象不会马上就使用分配到的内存,因此,它就通过系统接口madvice和MADV_DONTNEED标志告诉内核,刚刚分配出去的内存在近期内不会使用,内核可以该内存对应的物理页回收。当分配出去的内存被使用时,内核就会重新给它映射物理页,这样就可以做按需分配物理内存,适合在内存小的设备上运行。这里有三点需要注意。

           第一点是Dalvik虚拟机要求分配给对象的内存初始化为0,但是在低内存模式中,是使用函数mspace_malloc来分配内存,该函数不会将分配的内存初始化为0,因此我们需要自己去初始化这块内存。

           第二点是对于被系统接口madvice标记为MADV_DONTNEED的内存,是不需要我们将它初始化为0的,一来是因为这是无用功(对应的物理而可能会被内核回收),二来是因为当这些内存在真正使用时,内核在为它们映射物理页的同时,也会同时映射的物理页初始为0。

           第三点是在调用系统接口madvice时,指定的内存地址以及内存大小都必须以页大小为边界的,但是函数mspace_malloc分配出来的内存的地址只能保证对齐到8个字节,因此,我们是有可能不能将所有分配出来的内存都通过系统接口madvice标记为MADV_DONTNEED的。这时候对于不能标记为MADV_DONTNEED的内存,就需要调用memset来将它们初始化为0。

           在非低内存模式中,处理的逻辑就简单很多了,直接使用函数mspace_calloc在Active堆上分配指定的内存大小即可,同时该函数还会将分配的内存初始化为0,正好是可以满足Dalvik虚拟机的要求。

           注意,由于内存碎片的存在,即使是要分配的内存没有超出Active堆的Soft Limit,在调用函数mspace_malloc和函数mspace_calloc的时候,仍然有可能出现无法成功分配内存的情况。在这种情况下,都直接返回一个NULL值给调用者。

           在分配成功的情况下,函数dvmHeapSourceAlloc还需要做两件事情。

           第一件事情是调用函数countAllocation来计账,它的实现如下所示:

    static void countAllocation(Heap *heap, const void *ptr)
    {
        assert(heap->bytesAllocated < mspace_footprint(heap->msp));
    
        heap->bytesAllocated += mspace_usable_size(ptr) +
                HEAP_SOURCE_CHUNK_OVERHEAD;
        heap->objectsAllocated++;
        HeapSource* hs = gDvm.gcHeap->heapSource;
        dvmHeapBitmapSetObjectBit(&hs->liveBits, ptr);
    
        assert(heap->bytesAllocated < mspace_footprint(heap->msp));
    }
            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            函数countAllocation要计的账有三个:

            1. 记录Active堆当前已经分配的字节数。

            2. 记录Active堆当前已经分配的对象数。

            3. 调用函数dvmHeapBitmapSetObjectBit将新分配的对象在Live Heap Bitmap上对应的位设置为1,也就是说将新创建的对象标记为是存活的。关于Live Heap Bitmap,可以参考前面Dalvik虚拟机Java堆创建过程分析一文。

            回到函数dvmHeapSourceAlloc中,它需要做的第二件事情是检查当前Active堆已经分配的字节数是否已经大于预先设定的Concurrent GC阀值heap->concurrentStartBytes。如果大于的话,那么就需要通知GC线程执行一次Concurrent GC。当然,如果当前GC线程已经在进行垃圾回收,那么就不用通知了。当gDvm.gcHeap->gcRunning的值等于true时,就表示GC线程正在进行垃圾回收。

            这样,函数dvmHeapSourceAlloc的实现就分析完成了,接下来我们继续分析另外一个函数dvmHeapSourceAllocAndGrow的实现,如下所示:

    void* dvmHeapSourceAllocAndGrow(size_t n)
    {
        ......
    
        HeapSource *hs = gHs;
        Heap* heap = hs2heap(hs);
        void* ptr = dvmHeapSourceAlloc(n);
        if (ptr != NULL) {
            return ptr;
        }
    
        size_t oldIdealSize = hs->idealSize;
        if (isSoftLimited(hs)) {
            ......
            hs->softLimit = SIZE_MAX;
            ptr = dvmHeapSourceAlloc(n);
            if (ptr != NULL) {
                ......
                snapIdealFootprint();
                return ptr;
            }
        }
    
        ptr = heapAllocAndGrow(hs, heap, n);
        if (ptr != NULL) {
            ......
            snapIdealFootprint();
        } else {
            ......
            setIdealFootprint(oldIdealSize);
        }
        return ptr;
    }

            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            函数dvmHeapSourceAllocAndGrow首先是在不增加Active堆的前提下,调用我们前面分析的函数dvmHeapSourceAlloc来分配大小为n的内存。如果分配成功,那么就可以直接返回了。否则的话,继续往前处理。

            在继续往前处理之前,先记录一下当前Zygote堆和Active堆的大小之和oldIdealSize。这是因为后面我们可能会修改Active堆的大小。当修改了Active堆的大小,但是仍然不能成功分配大小为n的内存,那么就需要恢复之前Zygote堆和Active堆的大小。

            如果Active堆设置有Soft Limit,那么函数isSoftLimited的返回值等于true。在这种情况下,先将Soft Limit去掉,再调用函数dvmHeapSourceAlloc来分配大小为n的内存。如果分配成功,那么在将分配得到的地址返回给调用者之前,需要调用函数snapIdealFootprint来修改Active堆的大小。也就是说,在去掉Active堆的Soft Limit之后,可以成功地分配到大小为n的内存,这时候就需要相应的增加Soft Limit的大小。

           如果Active堆没有设置Soft Limit,或者去掉Soft Limit之后,仍然不能成功地在Active堆上分配在大小为n的内存,那么这时候就得出大招了,它会调用函数heapAllocAndGrow将Java堆的大小设置为允许的最大值,然后再在Active堆上分配大小为n的内存。

           最后,如果能成功分配到大小为n的内存,那么就调用函数snapIdealFootprint来重新设置Active堆的当前大小。否则的话,就调用函数setIdealFootprint来恢复之前Active堆的大小。这是因为虽然分配失败,但是前面仍然做了修改Active堆大小的操作。

           为了更好地理解函数dvmHeapSourceAllocAndGrow的实现,我们继续分析一下涉及到的函数isSoftLimited、setIdealFootprint、snapIdealFootprint和heapAllocAndGrow的实现。

           函数isSoftLimited的实现如下所示:

    static bool isSoftLimited(const HeapSource *hs)
    {
        /* softLimit will be either SIZE_MAX or the limit for the
         * active mspace.  idealSize can be greater than softLimit
         * if there is more than one heap.  If there is only one
         * heap, a non-SIZE_MAX softLimit should always be the same
         * as idealSize.
         */
        return hs->softLimit <= hs->idealSize;
    }
            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            根据我们前面的分析,hs->softLimit描述的是Active堆的大小,而hs->idealSize描述的是Zygote堆和Active堆的大小之和。

            当只有一个堆时,即只有Active堆时,如果设置了Soft Limit,那么它的大小总是等于Active堆的大小,即这时候hs->softLimit总是等于hs->idealSize。如果没有设置Soft Limit,那么它的值会被设置为SIZE_MAX值,这会就会保证hs->softLimit大于hs->idealSize。也就是说,当只有一个堆时,函数isSoftLimited能正确的反映Active堆是否设置有Soft Limit。

            当有两个堆时,即Zygote堆和Active堆同时存在,那么如果设置有Soft Limit,那么它的值就总是等于Active堆的大小。由于hs->idealSize描述的是Zygote堆和Active堆的大小之和,因此就一定可以保证hs->softLimit小于等于hs->idealSize。如果没有设置Soft Limit,即hs->softLimit的值等于SIZE_MAX,那么就一定可以保证hs->softLimit的值大于hs->idealSize的值。也就是说,当有两个堆时,函数isSoftLimited也能正确的反映Active堆是否设置有Soft Limit。

            函数setIdealFootprint的实现如下所示:

    static void setIdealFootprint(size_t max)
    {
        HS_BOILERPLATE();
    
        HeapSource *hs = gHs;
        size_t maximumSize = getMaximumSize(hs);
        if (max > maximumSize) {
            LOGI_HEAP("Clamp target GC heap from %zd.%03zdMB to %u.%03uMB",
                    FRACTIONAL_MB(max),
                    FRACTIONAL_MB(maximumSize));
            max = maximumSize;
        }
    
        /* Convert max into a size that applies to the active heap.
         * Old heaps will count against the ideal size.
         */
        size_t overhead = getSoftFootprint(false);
        size_t activeMax;
        if (overhead < max) {
            activeMax = max - overhead;
        } else {
            activeMax = 0;
        }
    
        setSoftLimit(hs, activeMax);
        hs->idealSize = max;
    }
            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            函数setIdealFootprint的作用是要将Zygote堆和Active堆的大小之和设置为max。在设置之前,先检查max值是否大于Java堆允许的最大值maximum。如果大于的话,那么就属于将max的值修改为maximum。

            接下为是以参数false来调用函数getSoftFootprint来获得Zygote堆的大小overhead。如果max的值大于Zygote堆的大小overhead,那么从max中减去overhead,就可以得到Active堆的大小activeMax。如果max的值小于等于Zygote堆的大小overhead,那么就说明要将Active堆的大小activeMax设置为0。

            最后,函数setIdealFootprint调用函数setSoftLimit设置Active堆的当前大小,并且将Zygote堆和Active堆的大小之和记录在hs->idealSize中。

            这里又涉及到两个函数getSoftFootprint和setSoftLimit,我们同样对它们进行分析。

            函数getSoftFootprint的实现如下所示:

    static size_t getSoftFootprint(bool includeActive)
    {
        HS_BOILERPLATE();
    
        HeapSource *hs = gHs;
        size_t ret = oldHeapOverhead(hs, false);
        if (includeActive) {
            ret += hs->heaps[0].bytesAllocated;
        }
    
        return ret;
    }
            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            函数getSoftFootprint首先调用函数oldHeapOverhead获得Zygote堆的大小ret。当参数includeActive等于true时,就表示要返回的是Zygote堆的大小再加上Active堆当前已经分配的内存字节数的值。而当参数includeActive等于false时,要返回的仅仅是Zygote堆的大小。

            函数oldHeapOverhead的实现如下所示:

    static size_t oldHeapOverhead(const HeapSource *hs, bool includeActive)
    {
        size_t footprint = 0;
        size_t i;
    
        if (includeActive) {
            i = 0;
        } else {
            i = 1;
        }
        for (/* i = i */; i < hs->numHeaps; i++) {
    //TODO: include size of bitmaps?  If so, don't use bitsLen, listen to .max
            footprint += mspace_footprint(hs->heaps[i].msp);
        }
        return footprint;
    }
           这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

           从这里就可以看出,当参数includeActive等于true时,函数oldHeapOverhead返回的是Zygote堆和Active堆的大小之和,而当参数includeActive等于false时,函数oldHeapOverhead仅仅返回Zygote堆的大小。注意,hs->heaps[0]指向的是Active堆,而hs->heaps[1]指向的是Zygote堆。这一点可以参考前面Dalvik虚拟机Java堆创建过程分析一文。

           回到函数setIdealFootprint中,我们继续分析函数setSoftLimit的实现,如下所示:

    static void setSoftLimit(HeapSource *hs, size_t softLimit)
    {
        ......
        mspace msp = hs->heaps[0].msp;
        size_t currentHeapSize = mspace_footprint(msp);
        if (softLimit < currentHeapSize) {
            ......
            mspace_set_footprint_limit(msp, currentHeapSize);
            hs->softLimit = softLimit;
        } else {
            ......
            mspace_set_footprint_limit(msp, softLimit);
            hs->softLimit = SIZE_MAX;
        }
    }
            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            函数setSoftLimit首先是获得Active堆的当前大小currentHeapSize。如果参数softLimit的值小于Active堆的当前大小currentHeapSize,那么就意味着要给Active堆设置一个Soft Limit,这时候主要就是将参数softLimit的保存在hs->softLimit中。另一方面,如果参数softLimit的值大于等于Active堆的当前大小currentHeapSize,那么就意味着要去掉Active堆的Soft Limit,并且将Active堆的大小设置为参数softLimit的值。

            回到函数dvmHeapSourceAlloc中,我们继续分析最后两个函数snapIdealFootprint和heapAllocAndGrow的实现,

            函数snapIdealFootprint的实同如下所示:

    static void snapIdealFootprint()
    {
        HS_BOILERPLATE();
    
        setIdealFootprint(getSoftFootprint(true));
    }
           这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

           函数snapIdealFootprint通过调用前面分析的函数getSoftFootprint和setIdealFootprint来调整Active堆的大小以及Soft Limit值。回忆一下函数dvmHeapSourceAlloc调用snapIdealFootprint的情景,分别是在修改了Active堆的Soft Limit或者将Active堆的大小设置为允许的最大值,并且成功在Active堆分配了指定大小的内存之后进行的。这样就需要调用函数snapIdealFootprint将Active堆的大小设置为实际使用的大小(而不是允许的最大值),以及重新设置Soft Limit值。

           函数heapAllocAndGrow的实现如下所示:

    static void* heapAllocAndGrow(HeapSource *hs, Heap *heap, size_t n)
    {
        ......
        size_t max = heap->maximumSize;
    
        mspace_set_footprint_limit(heap->msp, max);
        void* ptr = dvmHeapSourceAlloc(n);
    
        ......
        mspace_set_footprint_limit(heap->msp,
                                   mspace_footprint(heap->msp));
        return ptr;
    }
            这个函数定义在文件dalvik/vm/alloc/HeapSource.cpp中。

            函数heapAllocAndGrow使用最激进的办法来在参数heap描述的堆上分配内存。在我们这个情景中,参数heap描述的就是Active堆上。它首先将Active堆的大小设置为允许的最大值,接着再调用函数dvmHeapSourceAlloc在上面分配大小为n的内存。接着再通过函数mspace_footprint获得分配了n个字节之后Active堆的大小,并且将该值设置为Active堆的当前大小限制。这就相当于是将Active堆的当前大小限制值从允许设置的最大值减少为一个刚刚合适的值。

            至此,我们就分析完成了Dalvik虚拟机为新创建的对象分配内存的过程。只有充分理解了对象内存的分配过程之后,我们才能够更好地理解对象内存的释放过程,也就是Dalvik虚拟机的垃圾收集过程。在接下来的一篇文章中,我们就将详细分析Dalvik虚拟机的垃圾收集过程,敬请关注!更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

    展开全文
  • R语言学习笔记1——对象创建

    千次阅读 2012-03-03 20:54:21
    ——对象创建 杨旭东(2012-3-3) 从昨天开始学习R语言,主要参考资料是网上下的《R软件中文版教材》。 R既可以说是种编程语言,又可以说是一套完整的软件套件,包括IDE和各种库。R主要用与统计分析,可以...
  • 在python中创建对象(object)

    万次阅读 2019-06-15 06:54:02
    该系列文章: 《python入门,编程基础概念介绍(变量,...在上篇文章《python中的数据类型(list,tuple,dict,set,None)》的1.2小节里我们就简要介绍过对象(object)跟(class)的概念。也知道了python...
  • ART运行时为新创建对象分配内存的过程分析

    万次阅读 多人点赞 2015-01-22 00:59:22
    ART运行时和Dalvik虚拟机一样,在堆上为对象分配内存时都要解决内存碎片和内存不足问题。内存碎片问题可以使用dlmalloc技术解决。内存不足问题则通过垃圾回收和在允许范围内增长堆大小解决。由于垃圾回收会影响程序...
  • 它们创建于那些现有统计对象中不存在直方图的列上,名字包括列名和对象ID的十六进制格式:_WA_Sys_<column_name>_。这些统计信息用于查询优化器决定使用何种优化后的执行计划。 可以通过以下语句启用自动统计信息...
  • 在正式找工作前还有几月的时间,做东西,尝试新的技术固然很爽,但是基础也很重要,在这短短的几月的时间里,我将把以前学过的一些知识,Java,数据结构,算法,网络,OS&Linux,J2EE等等知识查缺补漏
  • 一次线程池引发的BUG,差点祭天

    万次阅读 多人点赞 2020-06-04 18:58:36
    } 它会线程池包装成 RunnableFuture 对象,而最终它其实是一个 FutureTask 对象,在添加到线程池的工作队列,然后调用 start() 方法后, FutureTask 对象的 run() 方法开始运行,即本任务开始执行。 public ...
  • 统计类数据挖掘

    千次阅读 2010-03-31 22:45:00
    统计数据挖掘技术统计:借助于数学模型手段,对数据进行那个归纳、推断和预测,寻找数据间的模式。统计研究中的抽样推断方法,相关与回归分析方法,统计推算与预测,统计假设检验等...count()用于统计对象的个数,su
  • 国内专业的移动应用统计分析平台。我们帮助移动应用开发商统计和分析流量来源、内容使用、用户属性和行为数据,以便开发商利用数据进行产品、运营、推广策略的决策。 个人对友盟统计的使用总结
  • Qt创建多线程的两种方法

    万次阅读 多人点赞 2017-12-25 15:33:54
    Qt有两种线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法...
  • var str = "kobebryant"; var obj = {}; //遍历str,统计每个字符出现的次数 for (var i = 0, length = str.length; i < length; i++) { ... //char就是对象obj的一个属性,obj[char]是属性值,存储...
  • Unity3D 内存优化(对象

    千次阅读 2016-03-21 20:37:50
    关于U3D内存优化,一直是游戏开发者头疼的事情,由于在项目中我们会频繁地创建和...设想一下,我们是否能做一个池子,将一些常用的对象创建之后放入池中,每次使用都直接从池子中查找获取,这样在提高对象利用率降低
  • 在【守望先锋】学习C++的对象

    千次阅读 多人点赞 2021-05-21 18:51:40
    C++是一门OOP(面向对象)的语言。 而C语言只是一名面向过程的语言。 里面有些思路需要重新改变。 为一个球队统计数据 如果是面向过程的程序员,可能会考虑:
  • 不管是自动化测试,还是测试开发,或者高级测试工程师,国内很公司都...给出一个字符串,统计出每个字母出现的次数。 思路分析:  需要统计字母和字母出现次数,这个第一反应就想起了编程语言中的字典概念,字母
  • 统计一个含有英文单词的文本文件的单词个数。 2. public void getWordNumFile(String formfileName, String word)  统计指定的文件中含有指定单词的个数。 假设指定单词是 “You” 在程序开发中,经常需要对
  • Web开发中的四对象:  有范围小到大:page(jsp有效) request(一次请求) session(一次会话) application(当前web应用)  page域指的是pageContext.  request域指的是request HttpServletRequest session 域...
  • 我想在大地上画满窗子,让所有习惯黑暗的眼睛习惯光明——顾城《我是一个任性的孩子》 这一节主要介绍一些理论层面的东西,主要针对SQL Server,为后面的做铺垫,如果从实操层面考虑可以跳过,但是我强烈建议还是要...
  • MFC线程的创建,包括工作线程和用户界面线程

    万次阅读 多人点赞 2013-01-04 16:41:40
    一个应用程序的执行都有一个主线程,这个主线程也是从CWinThread继承而来的。可以利用CWinThread对象创建应用程序执行的其它线程。 MFC用CWinThread对象来表示所有线程。利用MFC可以创建两种线程,分别称之为...
  • Hadoop-MapReduce初步应用-统计单词

    万次阅读 2015-11-30 20:55:57
    关于hadoop在电脑上安装的过程,请参考我的上篇博文: Hadoop-利用java API操作HDFS文件我的安装和配置环境是Windows下伪分布模式hadoop下使用eclipse进行开发。 上面的文中有关于安装和配置hadoop的视频和安装...
  • java对象池commons-pool-1.6详解(

    万次阅读 多人点赞 2017-12-23 17:14:39
    对象创建和销毁在一定程度上会消耗系统的资源,虽然jvm的性能在近几年已经得到了很大的提高,对于多数对象来说,没有必要利用对象池技术来进行对象创建和管理。但是对于有些对象来说,其创建的代价还是比较昂贵...
  • C++对象对象数组的排序

    千次阅读 多人点赞 2015-06-24 12:20:35
    C++对象对象数组的排序
  • 1、 创建一个球员,并且该最多只允许创建十一个对象。提示利用 static 和 封装性来完成。 [必做题] 类图如下: 效果如下: Player 代码: public class Player { // sum变量用来统计当前创建的...
  • 深入理解Java中的面向对象

    万次阅读 多人点赞 2016-08-03 09:13:10
    深入理解Java中的面向对象
  • 如图所示,我们的程序需要实现的功能是:向客户机发送数据并接受来自客户机的数据,同时能够统计发送到的和接收到的字节数,并能够在完成任务后清空计数以及发送区和接收区的数据。 由于使用的是UDP通讯方式,首先...
  • 一个应用程序的执行都有一个主线程,这个主线程也是从CWinThread继承而来的。可以利用CWinThread对象创建应用程序执行的其它线程。 MFC用CWinThread对象来表示所有线程。利用MFC可以创建两种线程,分别

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 197,929
精华内容 79,171
关键字:

统计一个类的对象被创建多次