精华内容
下载资源
问答
  • 《C++面向对象多线程编程》读后

    千次阅读 2010-12-12 01:33:00
    图书介质:扫描PDF 读后  用了将近2个月的时间读完了《C++面向对象多线程编程》这本书。市面上系统的介绍多线程技术的书并不是很多。当初选择了这本书来读很大层面上是因为译者序言中的好评,...

    开始时间:2010-10-19

    结束时间:2010-12-12

    阅读方式:精读,除附录部分关于非WIN32系统外的API部分。

    图书介质:扫描PDF

    读后感

           用了将近2个月的时间读完了《C++面向对象多线程编程》这本书。市面上系统的介绍多线程技术的书并不是很多。当初选择了这本书来读很大层面上是因为译者序言中的好评,尤其是“书中精辟之处令译者爱不释手甚至忘记了翻译工作”的描述给我留下了深刻的印象。

    这本书的主要内容我觉得可以大致分为三个部分,虽然三个部分并无明显的分界而是互相杂糅。第一部分是关于C++面向对象技术的特点以及如何应用于多线程编程的介绍;第二部分是多线程使用C++面向对象技术实现的具体方式;第三部分是POSIXWin32OS/2三个操作系统中提供的多线程操作相关API的介绍。

    本书的特点是非常鲜明的。首先,它不拘泥于介绍多线程编程本身,而是将所有与多线程实现相关的C++面向对象知识进行详细的介绍。比如它提到异常处理,就会详细介绍异常处理的机制;提到STL就会将STL的结构做较为深入的剖析。所以,在看这本书时,很多时候像是在读一本带有超链接的电子书,将相关知识点漫游式的一一浏览。而且这种介绍有不仅仅是照搬经典定义或教科书,而是充分融入了作者的思想,并且紧密结合了多线程技术的实现。阅读后我对C++语言结构与各种机制的理解有了更深的认识,受益匪浅。

    其次,是关于书的主体部分——利用C++面向对象技术具体实现多线程的精辟实现。作者剥茧抽丝般逐层封装、继承,最终提供了一套完整的多线程实现方案。当看到最后的代码时,我不得不为之一动。因为这绝不仅仅是一个总结或者一种诠释,更是一件不可多得的艺术品,令我叹为观止。

    最后,是这本书中所使用的示例代码。作者在书中穿插使用了POSIXWin32OS/2三种不同的操作系统下的API来对具体实现进行诠释。并且在随书光盘中提供了书中示例的三种版本的全部源码。在书中作者经常枚举三种API的同种功能语句并进行对比,大大拓展了我的思路。我在读这本书时逐渐将“找到一种解决实际问题的方法”的心态转变为“掌握一种解决一类问题的思路和方法”。

    其他方面要提到的是这本书的翻译工作做得十分到位,错字、错句也很少。只是在300-350页间出现错误略为频繁,但总体来说还是非常不错的。

    至于缺点,这本书的附录部分收录了POSIXWin32OS/2三种操作系统关于多线程操作的API,用了150余页。对于一本500页的书来说,确实多了些。另外,这种“超链接”式的风格,对于不需要重温C++面向对象技术,只想快速找到解决实际问题架构的读者来说显然有些冗余。除此之外,就没有什么缺点了。

    总体来讲,这是一本不可多得的好书,很多内容值得二次阅读,细细玩味。

    展开全文
  • 浅析面向对象

    千次阅读 多人点赞 2012-06-17 16:02:56
    浅析面向对象   毕业已经上了日程,马上开始做毕业设计了,java书再一次被我拿了出来。结果看得时候发现一个令人兴奋的现象,发现原来学的时候好多不懂的地方,现在好像都懂了,现在就来简明的谈谈什么是...其中,

                                                              浅析面向对象 

        毕业已经上了日程,马上开始做毕业设计了,java书再一次被我拿了出来。结果看得时候发现一个令人兴奋的现象,发现原来学的时候好多不懂的地方,现在好像都懂了,现在就来简明的谈谈什么是面向对象。

               所有的客观事物都可以看做对象把具有相同状态(静态特征)和行为(动态特征)的对象进行抽象总结,构成模型—类。比如“狗”这种类,都有感官,四肢,还能叫。其中,感官官和四肢是的属性的一部分,“叫”是狗的行为特性。这些属性,行为共同的作用把狗和其他种类的动物区分开。狗这种类只是一个抽象的概念,它并不对应 具体的物体。而真实存在的某只狗,比如谁家养了一条狗,这才是可以称作是一个对象。

          当然狗这种类里面可以再分类,比如大狗和小狗。这两类都具有狗的共有的特性,但是还可以找到一些其他的特性来区分他们。比如小狗的身材较小毛发柔软等。而大狗则不同,等等。那么,当你定义一个小狗狗这种类的继承类时,小狗除了自动继承狗这种类的特性,还可以另外定义一些属性和行为,比如小狗吃奶等。而某个具体的,比如《人狗奇缘》中的将军(狗的名字),则可以称作是类别里面的一个对象。

          对于一个类别里面定义好的行为,可以根据子类的不同进行不同的实现。比如类的大狗和小狗的叫的方式明显的不同。如果只是在类里面就定义好 行为这个特性,并不实现它。而是在大狗小狗各自继承的叫的特性里,做好不同的实现。这样,当你定义一个大狗的对象时,也可把它看做是一个类的对象,当 这个类的对象时,他会自动选择用大狗的方式来。也可以这么说,相同类别的对象们,根据自己所属子类的不同而对同一行为做出不同的表现。这就叫多态。

                这里说了面向对象中的最重要的几个概念,对象,类,继承和多态,这几个概念理解了,或许对面向对象的理解算是初步入门了吧

    展开全文
  • C++类和对象详解

    千次阅读 多人点赞 2020-05-08 00:43:54
    另外一种是在堆上使用 new 关键字创建,必须要用一个指针指向它,读者要记得 delete 掉不再使用的对象。 通过对象名字访问成员使用点号.,通过对象指针访问成员使用箭头->,这和结构体非常类似。 成员变量和函数...

    创建对象

    两种创建对象的方式:一种是在栈上创建,形式和定义普通变量类似;另外一种是在堆上使用 new 关键字创建,必须要用一个指针指向它,读者要记得 delete 掉不再使用的对象。

    通过对象名字访问成员使用点号.,通过对象指针访问成员使用箭头->,这和结构体非常类似。

    成员变量和函数

    类可以看做是一种数据类型,它类似于普通的数据类型,但是又有别于普通的数据类型。类这种数据类型是一个包含成员变量和成员函数的集合。

    类的成员变量和普通变量一样,也有数据类型和名称,占用固定长度的内存。但是,在定义类的时候不能对成员变量赋值,因为类只是一种数据类型或者说是一种模板,本身不占用内存空间,而变量的值则需要内存来存储。

    类的成员函数也和普通函数一样,都有返回值和参数列表,它与一般函数的区别是:成员函数是一个类的成员,出现在类体中,它的作用范围由类来决定;而普通函数是独立的,作用范围是全局的,或位于某个命名空间内。
     

    class Student{
    public:
        //成员变量
        char *name;
        int age;
        float score;
    
        //成员函数
        void say(){
            cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
        }
    };

    但我们一般建议类内只是声明函数,类外定义。 

    class Student{
    public:
        //成员变量
        char *name;
        int age;
        float score;
    
        //成员函数
        void say();  //函数声明
    };
    
    //函数定义
    void Student::say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }

    在类体中直接定义函数时,不需要在函数名前面加上类名,因为函数属于哪一个类是不言而喻的。
    但当成员函数定义在类外时,就必须在函数名前面加上类名予以限定。::被称为域解析符(也称作用域运算符或作用域限定符),用来连接类名和函数名,指明当前函数属于哪个类。

    在类体中和类体外定义成员函数的区别

    在类体中和类体外定义成员函数是有区别的:在类体中定义的成员函数会自动成为内联函数,在类体外定义的不会。当然,在类体内部定义的函数也可以加 inline 关键字,但这是多余的,因为类体内部定义的函数默认就是内联函数。

    内联函数一般不是我们所期望的,它会将函数调用处用函数体替代,所以我建议在类体内部对成员函数作声明,而在类体外部进行定义,这是一种良好的编程习惯,实际开发中大家也是这样做的。

    当然,如果你的函数比较短小,希望定义为内联函数,那也没有什么不妥的。

    如果你既希望将函数定义在类体外部,又希望它是内联函数,那么可以在定义函数时加 inline 关键字。当然你也可以在函数声明处加 inline,不过这样做没有效果,编译器会忽略函数声明处的 inline

    构造函数

    #include <iostream>
    using namespace std;
    
    class Student{
    private:
        char *m_name;
        int m_age;
        float m_score;
    public:
        //声明构造函数
        Student(char *name, int age, float score);
        //声明普通成员函数
        void show();
    };
    
    //定义构造函数
    Student::Student(char *name, int age, float score){
        m_name = name;
        m_age = age;
        m_score = score;
    }
    //定义普通成员函数
    void Student::show(){
        cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
    }
    
    int main(){
        //创建对象时向构造函数传参
        Student stu("小明", 15, 92.5f);
        stu.show();
        //创建对象时向构造函数传参
        Student *pstu = new Student("李华", 16, 96);
        pstu -> show();
    
        return 0;
    }

    该例在 Student 类中定义了一个构造函数Student(char *, int, float),它的作用是给三个 private 属性的成员变量赋值。要想调用该构造函数,就得在创建对象的同时传递实参,并且实参由( )包围,和普通的函数调用非常类似。

    在栈上创建对象时,实参位于对象名后面,例如Student stu("小明", 15, 92.5f);在堆上创建对象时,实参位于类名后面,例如new Student("李华", 16, 96)

    构造函数必须是 public 属性的,否则创建对象时无法调用。当然,设置为 private、protected 属性也不会报错,但是没有意义。

    构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,这意味着:

    • 不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许;
    • 函数体中不能有 return 语句。

    构造函数的重载

    和普通成员函数一样,构造函数是允许重载的。一个类可以有多个重载的构造函数,创建对象时根据传递的实参来判断调用哪一个构造函数。

    构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。

    默认构造函数

    如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作。比如上面的 Student 类,默认生成的构造函数如下:

    Student(){}

    一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。

    一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。

    实际上编译器只有在必要的时候才会生成默认构造函数,而且它的函数体一般不为空。默认构造函数的目的是帮助编译器做初始化工作,而不是帮助程序员。这是C++的内部实现机制,这里不再深究,初学者可以按照上面说的“一定有一个空函数体的默认构造函数”来理解。

    最后需要注意的一点是,调用没有参数的构造函数也可以省略括号。对于示例的代码,在栈上创建对象可以写作Student stu()Student stu,在堆上创建对象可以写作Student *pstu = new Student()Student *pstu = new Student,它们都会调用构造函数 Student()。

    以前我们就是这样做的,创建对象时都没有写括号,其实是调用了默认的构造函数。

    初始化列表

    构造函数的一项重要功能是对成员变量进行初始化,可以在构造函数的函数体中对成员变量一一赋值,还可以采用初始化列表。

    #include <iostream>
    using namespace std;
    
    class Student{
    private:
        char *m_name;
        int m_age;
        float m_score;
    public:
        Student(char *name, int age, float score);
        void show();
    };
    
    //采用初始化列表
    Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
        //TODO:
    }

    如本例所示,定义构造函数时并没有在函数体中对成员变量一一赋值,其函数体为空(当然也可以有其他语句),而是在函数首部与函数体之间添加了一个冒号:,后面紧跟m_name(name), m_age(age), m_score(score)语句,这个语句的意思相当于函数体内部的m_name = name; m_age = age; m_score = score;语句,也是赋值的意思。

    使用构造函数初始化列表并没有效率上的优势,仅仅是书写方便,尤其是成员变量较多时,这种写法非常简单明了。

    注意,成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。

    创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作,例如释放分配的内存、关闭打开的文件等,这个函数就是析构函数。


    析构函数

     

    析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要程序员显式调用(程序员也没法显式调用),而是在销毁对象时自动执行。构造函数的名字和类名相同,而析构函数的名字是在类名前面加一个~符号。

    注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数。如果用户没有定义,编译器会自动生成一个默认的析构函数。

    #include <iostream>
    using namespace std;
    
    class VLA{
    public:
        VLA(int len);  //构造函数
        ~VLA();  //析构函数
    public:
        void input();  //从控制台输入数组元素
        void show();  //显示数组元素
    private:
        int *at(int i);  //获取第i个元素的指针
    private:
        const int m_len;  //数组长度
        int *m_arr; //数组指针
        int *m_p;  //指向数组第i个元素的指针
    };
    
    VLA::VLA(int len): m_len(len){  //使用初始化列表来给 m_len 赋值
        if(len > 0){ m_arr = new int[len];  /*分配内存*/ }
        else{ m_arr = NULL; }
    }
    VLA::~VLA(){
        delete[] m_arr;  //释放内存
    }
    void VLA::input(){
        for(int i=0; m_p=at(i); i++){ cin>>*at(i); }
    }
    void VLA::show(){
        for(int i=0; m_p=at(i); i++){
            if(i == m_len - 1){ cout<<*at(i)<<endl; }
            else{ cout<<*at(i)<<", "; }
        }
    }
    int * VLA::at(int i){
        if(!m_arr || i<0 || i>=m_len){ return NULL; }
        else{ return m_arr + i; }
    }
    
    int main(){
        //创建一个有n个元素的数组(对象)
        int n;
        cout<<"Input array length: ";
        cin>>n;
        VLA *parr = new VLA(n);
        //输入数组元素
        cout<<"Input "<<n<<" numbers: ";
        parr -> input();
        //输出数组元素
        cout<<"Elements: ";
        parr -> show();
        //删除数组(对象)
        delete parr;
    
        return 0;
    }

    析构函数的执行时机

    析构函数在对象被销毁时调用,而对象的销毁时机与它所在的内存区域有关。
    在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数。
    在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数。
    new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Demo{
    public:
        Demo(string s);
        ~Demo();
    private:
        string m_s;
    };
    Demo::Demo(string s): m_s(s){ }
    Demo::~Demo(){ cout<<m_s<<endl; }
    
    void func(){
        //局部对象
        Demo obj1("1");
    }
    
    //全局对象
    Demo obj2("2");
    
    int main(){
        //局部对象
        Demo obj3("3");
        //new创建的对象
        Demo *pobj4 = new Demo("4");
        func();
        cout<<"main"<<endl;
      
        return 0;
    }

    this 是 一个关键字,也是一个 const指针,它指向当前对象,通过它可以访问当前对象的所有成员。

    所谓当前对象,是指正在使用的对象。例如对于stu.show();,stu 就是当前对象,this 就指向 stu。

    注意,this 是一个指针,要用->来访问成员变量或成员函数。
    this 虽然用在类的内部,但是只有在对象被创建以后才会给 this 赋值,并且这个赋值的过程是编译器自动完成的,不需要用户干预,用户也不能显式地给 this 赋值。

    this 原理

    this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。


    this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。

    成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。

    静态变量

    有时候我们希望在多个对象之间共享数据,对象 a 改变了某份数据后对象 b 可以检测到。共享数据的典型使用场景是计数,以前面的 Student 类为例,如果我们想知道班级中共有多少名学生,就可以设置一份共享的变量,每次创建对象时让该变量加 1。

    class Student{
    public:
        Student(char *name, int age, float score);
        void show();
    public:
        static int m_total;  //静态成员变量
    private:
        char *m_name;
        int m_age;
        float m_score;
    };

    static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 m_total 分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。


    注意:static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。反过来说,没有在类外初始化的 static 成员变量不能使用。

    static 成员变量既可以通过对象来访问,也可以通过类来访问。

    //通过类类访问 static 成员变量
    Student::m_total = 10;
    //通过对象来访问 static 成员变量
    Student stu("小明", 15, 92.5f);
    stu.m_total = 20;
    //通过对象指针来访问 static 成员变量
    Student *pstu = new Student("李华", 16, 96);
    pstu -> m_total = 20;

    总结:

    1) 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。

    2) static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。

    3) 静态成员变量必须初始化,而且只能在类体外进行。例如:

    int Student::m_total = 10;

    初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。全局数据区的变量都有默认的初始值 0,而动态数据区(堆区、栈区)变量的默认值是不确定的,一般认为是垃圾值。

    4) 静态成员变量既可以通过对象名访问,也可以通过类名访问当通过对象名访问时,对于不同的对象,访问的是同一份内存。

    静态成员函数

    静态成员函数与普通成员函数的根本区别在于:普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。

    C++ class和struct区别

    cpp中保留了C语言的 struct 关键字,并且加以扩充。在C语言中,struct 只能包含成员变量,不能包含成员函数。而在C++中,struct 类似于 class,既可以包含成员变量,又可以包含成员函数。

    C++中的 struct 和 class 基本是通用的,唯有几个细节不同:

    • 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
    • class 继承默认是 private 继承,而 struct 继承默认是 public 继承
    • class 可以使用模板,而 struct 不能

    C++ 没有抛弃C语言中的 struct 关键字,其意义就在于给C语言程序开发人员有一个归属感,并且能让C++编译器兼容以前用C语言开发出来的项目。

    在编写C++代码时,我强烈建议使用 class 来定义类,而使用 struct 来定义结构体,这样做语义更加明确。

    展开全文
  • 在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。 具体如下图 java 普通对象结构 java 数组对象结构 对象结构组成 对象头 ...

    概念

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

    具体如下图

    java 普通对象结构
    在这里插入图片描述

    java 数组对象结构
    在这里插入图片描述

    对象结构组成

    对象头

    HotSpot虚拟机的对象头包括两部分信息:

    1. Mark Word
      第一部分Mark Word,用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32bit和64bit
    2. 类型指针
      对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例.
    3. 数组长度(只有数组对象有)
      如果对象是一个数组, 那在对象头中还必须有一块数据用于记录数组长度.

    实例数据

    ​ 实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。

    对象引用(reference)类型在64位机器上,关闭指针压缩时占用8bytes, 开启时占用4bytes。

    原生类型(primitive type)的内存占用如下:

    Primitive Type Memory Required(bytes)
    byte, boolean 1 byte
    short, char 2 bytes
    int, float 4 bytes
    long, double 8 bytes

    包装类型

    包装类(Boolean/Byte/Short/Character/Integer/Long/Double/Float)占用内存的大小等于对象头大小加上底层基础数据类型的大小。

    包装类型的对象内存占用情况如下:

    Numberic Wrappers +useCompressedOops -useCompressedOops
    Byte, Boolean 16 bytes 24 bytes
    Short, Character 16 bytes 24 bytes
    Integer, Float 16 bytes 24 bytes
    Long, Double 24 bytes 24 bytes

    对齐填充

    ​ 第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

    HotSpot的对齐方式为8字节对齐:

    (对象头 + 实例数据 + padding) % 8=0且0 <= padding < 8

    jvm相关参数

    上面用到的useCompressedOops这个参数,我们可以看看在命令行输入:java -XX:+PrintCommandLineFlags -version 查看jvm默认参数如图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pKMke0p9-1589680207000)(E:\技术帖子\笔记\基础\图\java对象结构\jvm参数.png)]

    分别是 -XX:+UseCompressedOops 和 -XX:+UseCompressedClassPointers
    这2个参数都是默认开启(+代表开启,-代表关闭)

    UseCompressedOops:普通对象指针压缩(oop是ordinary object pointer的缩写),
    UseCompressedClassPointers:类型指针压缩。

    例如:

    Object o = new Object();
    o指向new Object()的引用就是“普通对象指针”,
    new Object()自身还需要指向Object类型的引用,也就是"类型指针"。

    这2个压缩参数可以有4种组合(++, --, + -, -+),但有1种组合是会抛出警告的:

    -XX:+UseCompressedClassPointers -XX:-UseCompressedOops,不要使用这种参数组合,用这种参数启动jvm时会抛出警告。

    Java HotSpot(TM) 64-Bit Server TIM warning: UseCompressedClassPointers requires UseCompressOops
    

    原因是jvm层面的hotspot源码对jvm的参数组合做了限制,一看就懂:

    // UseCompressedOops must be on for UseCompressedClassPointers to be on.
    if (!UseCompressedOops){
       if (UseCompressedClassPointers){
       warning("UseCompressedClassPointers requires UseCompressOops");
    }
      FLAG_SET_DEFAULT(UseCompressedClassPointers , false);
    }
    

    HotSpot对象模型

    HotSpot中采用了OOP-Klass模型,它是描述Java对象实例的模型,它分为两部分:

    • 类被加载到内存时,就被封装成了klass,klass包含类的元数据信息,像类的方法、常量池这些信息都是存在klass里的,你可以认为它是java里面的java.lang.Class对象,记录了类的全部信息;

    • OOP(Ordinary Object Pointer)指的是普通对象指针,它包含MarkWord 和元数据指针,MarkWord用来存储当前指针指向的对象运行时的一些状态数据;元数据指针则指向klass,用来告诉你当前指针指向的对象是什么类型,也就是使用哪个类来创建出来的;

      那么为何要设计这样一个一分为二的对象模型呢?这是因为HotSopt JVM的设计者不想让每个对象中都含有一个vtable(虚函数表),所以就把对象模型拆成klass和oop,其中oop中不含有任何虚函数,而klass就含有虚函数表,可以进行method dispatch。

    HotSpot中,OOP-Klass实现的代码都在/hotspot/src/share/vm/oops/路径下,oop的实现为instanceOop 和 arrayOop,他们来描述对象头,其中arrayOop对象用于描述数组类型。

    以下就是oop.hhp文件中oopDesc的源码,可以看到两个变量_mark就是MarkWord,_metadata就是元数据指针,指向klass对象,这个指针压缩的是32位,未压缩的是64位;

    volatile markOop _mark;  //标识运行时数据
      union _metadata {
        Klass*      _klass;
        narrowKlass _compressed_klass;
      } _metadata;  //klass指针
    

    一个Java对象在内存中的布局可以连续分成两部分:instanceOop(继承自oop.hpp)和实例数据;

    如图:
    在这里插入图片描述

    通过栈帧中的对象引用reference找到Java堆中的对象,再通过对象的instanceOop中的元数据指针klass来找到方法区中的instanceKlass,从而确定该对象的类型。

    对象大小的计算

    有以下几点:

    1.在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。

    2.在64位系统下,存放Class指针的空间大小是8字节,MarkWord是8字节,对象头为16字节。

    3.64位开启指针压缩的情况下,存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节。

    4.数组长度4字节+数组对象头8字节(对象引用4字节(未开启指针压缩的64位为8字节)+数组markword为4字节(64位未开启指针压缩的为8字节))+对齐4=16字节。

    5.静态属性不算在对象大小内。

    贴网上的一个比较实用的工具类:

    import java.lang.instrument.Instrumentation;  
    import java.lang.reflect.Array;  
    import java.lang.reflect.Field;  
    import java.lang.reflect.Modifier;  
    import java.util.ArrayDeque;  
    import java.util.Deque;  
    import java.util.HashSet;  
    import java.util.Set;  
    
    /** 
    
    ​	*对象占用字节大小工具类 
    
    ​    **/  
    public class SizeOfObject {  
    static Instrumentation inst;  
    
    public static void premain(String args, Instrumentation instP) {  
        inst = instP;  
    }  
    
    /** 
    
     * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br> 
    
    *引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br> 
    
    *但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br> 
    
    *
    
    *@param obj 
    
    *@return 
    */  
    public static long sizeOf(Object obj) {  
    return inst.getObjectSize(obj);  
    }  
    
    /** 
    
    *递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 
    
    *
    
    *@param objP 
    
    *@return 
    
    *@throws IllegalAccessException 
    */  
    public static long fullSizeOf(Object objP) throws IllegalAccessException {  
    Set<Object> visited = new HashSet<Object>();  
    Deque<Object> toBeQueue = new ArrayDeque<Object>();  
    toBeQueue.add(objP);  
    long size = 0L;  
    while (toBeQueue.size() > 0) {  
        Object obj = toBeQueue.poll();  
        //sizeOf的时候已经计基本类型和引用的长度,包括数组  
        size += skipObject(visited, obj) ? 0L : sizeOf(obj);  
        Class<?> tmpObjClass = obj.getClass();  
        if (tmpObjClass.isArray()) {  
            //[I , [F 基本类型名字长度是2  
            if (tmpObjClass.getName().length() > 2) {  
                for (int i = 0, len = Array.getLength(obj); i < len; i++) {  
                    Object tmp = Array.get(obj, i);  
                    if (tmp != null) {  
                        //非基本类型需要深度遍历其对象  
                        toBeQueue.add(Array.get(obj, i));  
                    }  
                }  
            }  
        } else {  
            while (tmpObjClass != null) {  
                Field[] fields = tmpObjClass.getDeclaredFields();  
                for (Field field : fields) {  
                    if (Modifier.isStatic(field.getModifiers())   //静态不计  
                            || field.getType().isPrimitive()) {    //基本类型不重复计  
                        continue;  
                    }  
    
    ​            field.setAccessible(true);  
    ​            Object fieldValue = field.get(obj);  
    ​            if (fieldValue == null) {  
    ​                continue;  
    ​            }  
    ​            toBeQueue.add(fieldValue);  
    ​        }  
    ​        tmpObjClass = tmpObjClass.getSuperclass();  
    ​    }  
    }  
    
    }  
    return size;  
    }  
    
    /** 
    
       * String.intern的对象不计;计算过的不计,也避免死循环 
    
    *
    
    *@param visited 
    
    *@param obj 
    
    *@return 
    */  
    static boolean skipObject(Set<Object> visited, Object obj) {  
    if (obj instanceof String && obj == ((String) obj).intern()) {  
        return true;  
    }  
    return visited.contains(obj);  
    }  
    }
    

    最后举三个例子:

    首先需要创建一个mavean项目,引入包

    <dependency>
      <groupId>org.openjdk.jol</groupId>
      <artifactId>jol-core</artifactId>
      <version>0.9</version>
    </dependency>
    

    1.需要补齐的对象

    代码

    public class User {
        long sex;
        Long mobile;
        String name;
    
        public static void main(String[] args) {
            System.out.println(ClassLayout.parseInstance(new User()).toPrintable());
        }
    }
    

    输出如图
    在这里插入图片描述
    2.不需要padding补齐的对象

    代码:

    public class User {
    
        String name;
        Long mobile;
        int sex;
    
        public static void main(String[] args) {
            System.out.println(ClassLayout.parseInstance(new User()).toPrintable());
        }
    }
    

    输出如图
    在这里插入图片描述
    3.空对象,所占字节数

    代码:

    public class User {
    
        public static void main(String[] args) {
            System.out.println(ClassLayout.parseInstance(new User()).toPrintable());
        }
    }
    

    输出如图
    在这里插入图片描述
    4.数组对象结构

    代码:

    public class ArrayTest {
    
        public static void main(String[] args) {
            System.out.println(ClassLayout.parseInstance(new Integer[7]).toPrintable());
            System.out.println(ClassLayout.parseInstance(new Integer[8]).toPrintable());
            System.out.println(ClassLayout.parseInstance(new int[7]).toPrintabl![在这里插入图片描述](https://img-blog.csdnimg.cn/20200517102321457.png)e());
        }
    }
    

    输出如图
    在这里插入图片描述
    如果大家对java架构相关感兴趣,可以关注下面公众号,会持续更新java基础面试题, netty, spring boot,spring cloud等系列文章,一系列干货随时送达, 超神之路从此展开, BTAJ不再是梦想!

    架构殿堂

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

    千次阅读 2015-08-12 15:35:04
    一、为什么对象可以有数组 类是一种特殊的数据类型,它当然是C++合法的类型,自然就可以定义对象数组。 二、何为对象数组 在一个对象数组中各个元素都是同类对象, 例如:一个班级有50个学生,每个学生具有学号,...
  • 面向对象之设计模式

    千次阅读 2016-11-22 23:15:32
    面向对象之设计模式前言 要想推开架构师的那扇大门,就离不开设计模式这把钥匙。对设计模式的理解与精通,是通往架构师之路的第一步 在任何面向对象语言的开发过程以及个人职业技能成长的道路中,新手与新手或者新手...
  • 什么是面向对象的设计思想?

    千次阅读 2013-12-07 10:58:33
    什么是面向对象的设计思想? ...也许有不少初学者对这个概念还有许多不明白的地方,特别是这个处于新旧思想交替的时代,许多人刚刚学完现在看来是快要淘汰的只是面向过程的语言。...不兴趣者,一带
  • C++ Windows对象和MFC对象的区别

    千次阅读 2015-08-16 03:27:40
    windows对象并不是我们平时所说的“面向对象”程序设计中的“类的对象”,而是一种windows资源实体,如画笔、字体等, 如果想要去使用这些windows对象我们需要用句柄来标识它们。 (好比在共产主义社会,句柄...
  • 5w字长文带你彻底看懂Spring是怎么创建对象的!附10几张图,在创作这篇文章的过程中给官方提交了两个issue
  • 一张图让你看懂javascript各类型的关系 ... 1、内置(Build-in)对象与原生(Naitve)对象的区别在于:前者总是在引擎初始化阶段就被创建好的对象,是后者的一个子集;而后者包括了一些在运行过程中动态创建的
  • 兴趣对象是描述中在功能需求中的、功能用户兴趣的事物,它可以是一个具体的东西,也可以是一个抽象的概念,系统一定要对它执行输入、输出、读或写的动作。 在COSMIC中对兴趣对象有明确的定义,识别兴趣对象的规则...
  • JavaScript中的变量对象,简约却不简单(系列五)

    万次阅读 多人点赞 2020-06-17 18:52:05
    本篇文章:主要介绍执行上下文的组成部分之一——变量对象(VO)和活动对象(AO)。本文是与系列的前几篇文章存在一定的承接关系,大家兴趣的话不妨从头阅读~
  • 面向对象六大原则

    万次阅读 多人点赞 2015-11-30 00:10:44
    既然要训练小民的面向对象设计,那么就必须考虑到可扩展性、灵活性,而检测这一切是否符合需求的最好途径就是开源。用户不断地提出需求、反馈问题,小民的项目需要不断升级以满足用户需求,并且要保证系统的稳定性、...
  • ECMA-262 把本地对象(native object)定义为“独立于宿主环境的 ECMAScript 实现提供的对象”。 看定义有些不清楚,因为还未了解何为“宿主环境”的时候就引用了“宿主环境”这个概念,着实让人有些发晕。 不过,...
  • ARC/OC对象自动管理内存

    千次阅读 2013-12-09 11:13:10
    ARC让开发者专注于兴趣的代码和对象的关系,而不用考虑对象的retain和release。 转自hherima的博客 原文:Transitioning to ARC Release Notes(苹果官方文档)   ARC是一个编译器特征,它提供了对...
  • UML之对象

    千次阅读 热门讨论 2014-02-17 10:56:49
     对象图(ObjectDiagram)描述的是参与交互的各个对象在交互过程中某一时刻的状态。对象图可以被看作是类图在某一时刻的实例。  在UML中,对象图使用的是与类图相同的符号和关系,因为对象就是类的实例。下图显示了...
  • Java对象大小内幕浅析

    万次阅读 2016-03-29 18:59:00
    最近突发奇想,忽然对Java对象的内存大小兴趣,去网上搜集了一些资料,并且做一下整理,希望能够各位帮助。  如果:你能算出new String(“abc”)这个对象在JVM中占用内存大小(64位JDK7中压缩大小48B,未压缩大小...
  • Mybatis通过一条SQL查出关联的对象

    万次阅读 2017-04-24 17:47:08
    Mybatis通过一条SQL查出关联的对象 以往在做对象的查询时如果需要把关联的对象一起查出来是通过resultMap的子查询来进行的。通过子查询来进行的关联对象的查询时,Mybatis会重新发起一次数据库请求,这在有的时候...
  • 对象头的内部结构一张图了解所有细节 1、创建了Customer()实例 和 Account()实例 2、对象头里包括:运行时元数据、类型指针、实例数据、对齐填充 ① 运行时元数据里又包括:哈希值(HashCode)、GC分代年龄、锁状态...
  • Java对象内存布局概述

    千次阅读 多人点赞 2019-09-22 23:17:42
    以HotSpot虚拟机为例,对象在内存中可以分为三块区域:对象头、实例数据和对齐填充。其中,对象头包含Mark Word和类型指针,关于对象头的内容,在...对齐填充则不是必须存在的,只是起占位符的作用,比如Hot Spot...
  • scala 对象的相等

    万次阅读 2018-11-16 15:54:14
    scala 对象的相等 Scala程序设计 第2版 - 原版.pdf 下载: https://download.csdn.net/download/u014646662/10805074 1 equals方法 2==和!=方法 3eq和ne方法 4数组相等和sameElements方法 对人工智能兴趣的...
  • JavaScript深层克隆对象

    千次阅读 2016-10-26 22:37:57
    今天做题看到了深层克隆对象,并且要求在原型链上编程 于是心血来潮索性来复习一下这个知识点克隆对象,这名词看着高大上,其实也没什么,便是拷贝一个长的一模一样的对象 也许有初学的小伙伴在想,那还不简单么,...
  • 如果大家兴趣,我后面专门出一篇文章,全面介绍下逃逸分析。 在Java代码运行时,通过JVM参数可指定是否开启逃逸分析,   -XX:+DoEscapeAnalysis  : 表示开启逃逸分析   -XX:-DoEscapeAnalysis  : ...
  • 对他提出的这个问题很兴趣,深入研究了一下,便有了这篇文章。 更新引用是JVM的职责 任何一款JVM的设计,采用任何一种GC算法进行对象的移动操作时,如何更新对象引用都是JVM的基本职责。也就是说,当移动对象时,...
  • Scala面向对象编程

    万次阅读 2018-11-07 11:18:49
    Scala面向对象编程 Scala程序设计 第2版 - 原版.pdf 下载: https://download.csdn.net/download/u014646662/10805074 目录: 1 类与对象初步 2 引用与值类型 3 价值类 4 父类 5 Scala的构造器 6 类的字段 6.1...
  • 基于Java基础-面向对象实现植物大战僵尸简易版

    万次阅读 多人点赞 2019-03-31 15:15:44
    基于Java基础-面向对象实现植物大战僵尸简易版前言游戏设计游戏对象游戏内容游戏优化放置植物的优化移除植物的优化游戏可玩性的优化添加游戏背景音乐后续优化源码分享 前言 从零开始学习的Java的第一个月,在面向...
  • 真正的面向对象编程思想是怎样的?

    千次阅读 2010-12-20 11:22:00
    真正的面向对象编程思想是怎样的? 真正的面向对象编程思想就是忘掉一切关于计算机的东西,从问题领域考虑问题, :-) 从问题想? 我的理解: 1、将分析过程为抽象的过程:简言之:分成多个小...
  • 在本文将 ,我对 Java 对象创建、对象内存布局、对象访问定位的三个过程 进行了详细介绍,希望你们会喜欢
  • 然而在设计过程中可能会出现许多的问题,比如当前绘制了一个椭圆,但是在动画中仅仅只需要椭圆的一半或是更多更少的部分用作与动画元素,这时候就需要对椭圆对象进行相应的处理才能满足我们的需求,那到底该怎么做...
  • 九种不够面向对象对象

    千次阅读 2009-08-10 14:15:00
    文发表在《程序员》2009年第四期(总第100期) 本文列出了我在平时发现和积累的在面向对象编程中一些常见的“不够面向对象”的情况。 需要指出两点: 1.我们虽然列出了这九种情况,但并不是说出现了下面的情况就一定...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 337,806
精华内容 135,122
关键字:

对象感必须是