编程思想

1. 编程思想

1.1 面向函数

面向对象的编程和面向函数的编程有着同样悠久的历史。但早期编程大都是面向函数的。因为早期的计算机都是用来做数据分析使用。是一种分析数据并获得结果的过程。同样的数据集会被不同的函数所使用。在这个时期里面向函数也被称为面向过程。每个函数有着标准的输入,输出和处理。输入和输出的数据并不相同,写在不同的纸带上。所以不用担心数据更改导致的状态错误。后来因为内存和硬盘的出现。函数输入和输出的目标统一变成了内存和硬盘。不但要考虑函数执行的过程是否会出现错误。还要考虑输入和输出的结果是否会导致迭代的错误。所谓迭代的错误是指函数重复执行是否会导致内存或硬盘的数据出错。所以在这个时期被叫做面向函数编程。

面向表达式语言也称为应用式语言(App!icative Language)或函数式语言(Functional Language),是一种全新的程序设计语言。函数式程序没有诸如指令计数器、数据存储器和程序当前状态之类的概念。这种语言的程序是纯数学意义上的函数,它作用于程序的输入,得到的结果值就是程序的输出。因此它不具有副作用,保证了程序各部分的并发执行,不会影响正确结果的获得,对高度并行的系统结构,更适宜于采用函数式语言来设计并发程序。

在函数式程序设计系统中,程序是一个表达式,计算过程仅由函数来描述。对运算的顺序并无显式的描写,运算顺序只蕴涵于各函数调用之间的依赖关系中,且与运行程序的实际运算结构无关。

构成函数程序的主要成分是原函数(Primitive Functions)、复合函数和定义。原函数的主要功能是实现由对象到对象的映射,或是把一个对象变换成另一个对象。常用的原函数有选择函数,算术函数,交叉置换函数,比较、测试函数,加1、减1函数,附加序列函数,分配函数,取序列尾函数等。复合函数的主要功能是利用函数构成算符,将已有的函数构成新的复杂函数,常用的程序构成算符有:组合算符“O”,构造算符“[]”,条件算符“→”,插入算符“/”,作用于全体(Apply to A11)算符“α”等。复合函数只有经定义“f”并输入到机器后方能应用。用户定义的函数由形式参数表和函数体两部分组成。函数定义的过程也就是建立形式参数表中形式参数与函数体中的变量之间约束关系的过程。

归约机是一种面向函数式程序设计语言的计算机,属于需求驱动的系统结构。指令的执行顺序取决于对这些指令产生结果数据的需求,而这种需求又源于函数式程序设计语言对表达式的归约。归约操作主要是函数作用和子表达式变换。归约过程就是将每个最内层可归约表达式用其值来替换,这样又形成了新的最内层可归约表达式,然后再对其进行归约,最后,整个程序全部被归约,仅留下最终结果。 [1]

主要特点

函数式语言的主要特点是:

1)直接由原函数构成,层次分明。所编程序为静态的、非重复的,便于错误检测。

2)程序与数据分开,数据结构不是程序的组成部分,同一个函数程序可处理不同的对象,程序具有通用性。

3)程序中包括了固有并行性,便于检测和并行。

4)程序无状态并采用数据存储单元。

5)无赋值语句,不使用GOTO类控制语句,所使用的程序构成算符,遵从许多基本代数定理,因而由函数式语言所编写的程序的正确性,易于得到证明。 [1] 

结构

归约机按其归约模型可分为串归约机和图归约机两类,两者的区别主要是对函数表达式所使用的存储方式不同,串归约机以字符串形式存储,图归约机以图的形式存储。

串归约机

串归约机的例子是Mago提出的细胞树形结构多处理机FFP。FFP规约机的结构如图1所示。

图1

图1中线性L单元阵列是一个带有逻辑功能的存储系统,L单元不仅存放FFP表达式(程序和数据),还执行机器中大部分的工作,因而它又是一个PE(处理单元)。相邻的L单元互联成一个线性阵列,这一线性连接仅为了进行存储管理。前端机控制整个系统,包括对FFP机使用的基本操作进行定义,控制辅助存储器与管理I/O。辅助存储器同L阵列的两端相连,随后机器从辅助存储器取出信息到阵列,使其开始工作。若L阵列中的信息超出其存储容量,把溢出部分移入辅助存储器,L单元间经由互联网进行通信,并具有某些处理功能。

FFP机的实现方案有多种,图2所示的二叉树互联网结构是最简单的方案,二叉树网络的内部节点称T单元,叶节点称L单元,树根连到前端机。这种结构具有易于构造和易于扩展的优点。

图2

FFP机的工作过程可分为多个操作周期,每个周期又可分为三个阶段:分解阶段、执行阶段和存储管理阶段。FFP机能较好地完成程序自动分解,机器运行时按程序的实际需要可不断重构系统。机器以函数式语言FFP编程,无需程序员干预而自动地开发并行性。

图3表示向量(3,2,5,1)各元素平方和与各元素相除的计算过程。

图3

图归约机

图归约机将函数定义、表达式和目标以图的形式存储于机器中,因此进行归约的对象是图,最常用的是二叉树和N叉树。英国帝国理工学院的ALICE多处理器系统,其目标语言HOPE是纯函数式语言,程序由函数定义集组成。函数通过重写来加以归约,由存放在函数定义数据库中的代码替换函数加以实现。ALICE图归约机样机的结构如图1所示,它是一个由16个处理器和24个存储模块构成的多处理器系统,由全互连的Delta交叉开关网实现处理器和存储模块之间的互连。图4中示出了处理器及存储模块的内部结构。每个处理器含有五个Transputer,其中两个从事重写操作,每个带有64KB Cache;第三个用作Cache的管理;另外两个分别作接受和发送信息包用。存储模块的内部较简单,只有一个Transputer进行信息包的发送和接收操作,存储容量为2MB。 [1] 

1.2 面向过程

面向过程的结构化程序设计分三种基本结构:顺序结构、选择结构、循环结构

原则:

1,自顶向下:指从问题的全局下手,把一个复杂的任务分解成许多易于控制和处理的子任务,子任务还可能做进一步分解,如此 重复,直到每个子任务都容易解决为止。

2,逐步求精

3,模块化:指解决一个复杂问题是自顶向下逐层把软件系统划分成一个个较小的、相对独立但又相互关联的模块的过程。

1.3 面向对象

对象的含义是指具体的某一个事物,即在现实生活中能够看得见摸得着的事物。在面向对象程序设计中,对象所指的是计算机系统中的某一个成分。在面向对象程序设计中,对象包含两个含义,其中一个是数据,另外一个是动作。对象则是数据和动作的结合体。对象不仅能够进行操作,同时还能够及时记录下操作结果。 [3]

方法是指对象能够进行的操作,方法同时还有另外一个名称,叫做函数。方法是类中的定义函数,其具体的作用就是对对象进行描述操作。 [3] 

继承简单地说就是一种层次模型,这种层次模型能够被重用。层次结构的上层具有通用性,但是下层结构则具有特殊性。在继承的过程中类则可以从最顶层的部分继承一些方法和变量。类除了可以继承以外同时还能够进行修改或者添加。通过这样的方式能够有效提高工作效率。在这里举一个例子,当类X继承了类Y后,此时的类X则是一个派生类,而类Y属于一个基类。 [3]  继承是从一般演绎到特殊的过程,可以减少知识表示的冗余内容,知识库的维护和修正都非常方便。更有利于衍生复杂的系统。 [4]

是具有相同特性(数据元素)和行为(功能)的对象的抽象。因此,对象的抽象是类,类的具体化就是对象,也可以说类的实例是对象,类实际上就是一种数据类型。类具有属性,它是对象的状态的抽象,用数据结构来描述类的属性。类具有操作,它是对象的行为的抽象,用操作名和实现该操作的方法来描述。 [2] 类映射的每一个对象都具有这些数据和操作方法,类的继承具有层次性和结构性,高层次对象封装复杂行为,具体细节对该层次知识保持透明,可以减小问题求解的复杂度。

封装是将数据和代码捆绑到一起,对象的某些数据和代码可以是私有的,不能被外界访问,以此实现对数据和代码不同级别的访问权限。防止了程序相互依赖性而带来的变动影响,面向对象的封装比传统语言的封装更为清晰、更为有力。有效实现了两个目标:对数据和行为的包装和信息隐藏。 [1]

多态是指不同事物具有不同表现形式的能力。多态机制使具有不同内部结构的对象可以共享相同的外部接口,通过这种方式减少代码的复杂度。一个接口,多种方式。 [1] 

动态绑定指的是将一个过程调用与相应代码链接起来的行为。动态绑定是指与给定的过程调用相关联的代码只有在运行期才可知的一种绑定,它是多态实现的具体形式。 [1] 

消息传递:对象之间需要相互沟通,沟通的途径就是对象之间收发信息。消息内容包括接收消息的对象的标识,需要调用的函数的标识,以及必要的信息。消息传递的概念使得对现实世界的描述更容易。 [1] 

面向对象的方法就是利用抽象、封装等机制,借助于对象、类、继承、消息传递等概念进行软件系统构造的软件开发方法。 [1] 

2. 虚拟机中实现

2.1 关键字实现

–todo

2.2 函数实现

–todo

2.3 对象实现

Java跟c++语言一样,都是面向对象的语言,那么面向对象的语言都有个共同的两点

在面向对象的软件中,对象(Object)是某个类(Class)的实例。

一切皆对象

在JVM的内存结构中,对象保存在堆内存中,而我们在对对象进行操作时,其实操作的是对象的引用。
那么对象本身在JVM中的结构是什么样的呢?这个就需要基于HotSpot虚拟机来研究了

简单的介绍一下HotSpot虚拟机
HotSpot虚拟机是基于c++来实现的,他是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的虚拟机 。他的热点代码探测能力可以通过执行计数器找出最具有编译价值的代码,然后通知JIT编译器以方法为单位进行编译。 如果一个方法被频繁调用,或方法中有效循环次数很多,将会分别触发标准编译和OSR(栈上替换)编译动作。 通过编译器与解释器恰当地协同工作,可以在最优化的程序响应时间与最佳执行性能中取得平衡,而且无须等待本地代码输出才能执行程序, 即时编译的时间压力也相对减小,这样有助于引入更多的代码优化技术,输出质量更高的本地代码。

在HotSpot虚拟机中,有个叫oop-klass model的设计模型
Java的对象模型定义在oops目录下,模型主要定义了Java语言相关的模型结构,是Java面向对象特征在虚拟机中的映射。在模型树中主要由klass,oop,metadata,handle来组织(klass继承自metadata),这里我们主要讲klass,和oop两个模型

在oopsHierarchy.hpp里定义了oop和klass各自的体系

oop-klass结构

oop模型

Java对象在虚拟机中的表示
hotspot/src/share/vm/oops/oopsHierarchy.hpp

//java对象中oop的偏移量而
typedef juint narrowOop;
// 如果压缩klass指针,则使用窄klass
typedef juint narrowKlass;
typedef void* OopOrNarrowOopStar;
//对象头标记
typedef class markOopDesc* markOop;

ifndef CHECK_UNHANDLED_OOPS

//对象头(包含markOop),包含Kclass
typedef class oopDesc* oop;
//Java类实例对象(包含oop)
typedef class instanceOopDesc* instanceOop;
//Java数组(包含oop)
typedef class arrayOopDesc* arrayOop;
//Java对象数组(包含arrayOop)
typedef class objArrayOopDesc* objArrayOop;
//Java基本类型数组(包含arrayOop)
typedef class typeArrayOopDesc* typeArrayOop;

else

上面列出的是整个oops模块的组成结构,其中包含多个子模块。每个子模块对应一个类型,每一个类型的OOP都代表一个在JVM内部使用的特定对象的类型。

从上面的代码中可以看到,有一个变量oop的类型是oopDesc,oops类的共同基类型为oopDesc。

在Java程序运行过程中,每个创建一个新的对象,在JVM内部就会相应的创建一个对应类型的oop对象,也可以理解为hotspot直接copy一份java的东西给了c++。在HotSpot中,根据JVM内部使用的对象业务类型,具有多种oopDesc的子类。除了oopDesc类型外,oop体系中还有很多instanceOopDesc、arrayoopDesc等类型的实例,他们都是oopDesc的子类。

这些oops在JVM内部中有着不同的用途,例如,instanceOopDesc表示类实例,arrayOopDesc表示数组。也就是说,当我们使用new创建一个Java对象实例的时候,JVM会创建一个instanceOopDesc对象来表示这个Java对象。同理,当我们使用new创建一个Java数组实例的时候,JVM会创建一个arrayOopDesc对象来表示这个数组对象。

在HotSpot中,oopDesc类定义在oop。hpp中,instanceOopDesc定义在instanceOop.hpp中,arrayoopDesc定义在arrayOop.hpp中。

简单的看一下相关的定义

class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark;
union _metadata {
wideKlassOop _klass;
narrowOop _compressed_klass;
} _metadata;

private:
// field addresses in oop
void* field_base(int offset) const;

  jbyte*    byte_field_addr(int offset)   const;
  jchar*    char_field_addr(int offset)   const;
  jboolean* bool_field_addr(int offset)   const;
  jint*     int_field_addr(int offset)    const;
  jshort*   short_field_addr(int offset)  const;
  jlong*    long_field_addr(int offset)   const;
  jfloat*   float_field_addr(int offset)  const;
  jdouble*  double_field_addr(int offset) const;
  address*  address_field_addr(int offset) const;

}

class instanceOopDesc : public oopDesc {
}

class arrayOopDesc : public oopDesc {
}

从上面的源码可以看出来,instanceOopDesc实际上就是继承了oopDesc,并没有增加其他的数据结构,也就是说instanceOopDesc中主要包含了一下几部分数据:markOop _mark和union _metadata以及一下不同类型的field。

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头、实例数据和对齐填充。在虚拟机内部,一个Java对象对应一个instanceOopDesc的对象。其中对象头包含了两部分内容: _mark和 _metadata,而实例数据则保存在oopDesc中定义的各种field中

_mark
对象头中有和锁相关的运行时数据,这些运行时数据是synchronized以及其他类型的锁实现的重要基础,而关于锁标记,GC分代等信息均保存在这里。

_metadata
这是一个共同体,其中_klass是普通指针,_compressed_klass是压缩类指针。在深入介绍之前,就要来到oop-Klass中另外一个主角klass了

klass模型

Java类在虚拟机中的表示
hotspot/src/share/vm/oops/oopsHierarchy.hpp

// klass层次结构与oop层次结构是分开的。

//继承自 Metadata, 维护着类继承结构的类信息,对应一个ClassLoaderData
class Klass;
//继承自Klass,维护着对应的Java类相关信息(注解,字段,接口,虚表,版本,线程等信息),以及类所处的状 态
class InstanceKlass;
//继承自InstanceKlass,用于java.lang.Class实例,与反射相关,除了类的普通字段之外,它们还包含类的静态字段
class InstanceMirrorKlass;
//继承自InstanceKlass,它是为了遍历这个类装入器指向的类装入器的依赖关系。
class InstanceClassLoaderKlass;
//继承自InstanceKlass,与java/lang/ref/Reference相关
class InstanceRefKlass;
//继承自Klass,数组相关
class ArrayKlass;
//继承自ArrayKlass,对象数组
class ObjArrayKlass;
//继承自ArrayKlass,基本类型数组
class TypeArrayKlass;

和oopDesc是其他oop类型的父类一样,klass类是其他klass类型的父亲。

Klass向JVM提供了两个功能

实现语言层面的Java类(在Klass基类中已经实现)

实现Java对象的分发功能(由Klass的子类提供虚拟数实现)

在hotsprot的设计之初,HotSpot JVM的设计者不想让每个对象中都含有一个虚函数表。
于是把对象一拆为二,分为klass和oop。其中oop的主要职能在于表示对象的实例数据,所以其中不含有任何虚函数。而klass为了实现虚函数的多态,所以提供了虚函数表。所以,关于Java的多态,其实也有虚函数的影子在。

_metadata是一个共同体,其中_klass是普通指针,_compressed_klass是压缩类指针。这两个指针都指向instanceKlass对象,他用来描述对象的具体类型。

instanceKlass
JVM在运行时,需要一种用来标识Java内部类型的机制。在HotSpot中的解决方案是:为每个已加载的Java类创建一个instanceKlass对象,用来在JVM层表示Java类。

instanceKlass的内部结构

//类拥有的方法列表
objArrayOop _methods;
//描述方法顺序
typeArrayOop _method_ordering;
//实现的接口
objArrayOop _local_interfaces;
//继承的接口
objArrayOop _transitive_interfaces;
//域
typeArrayOop _fields;
//常量
constantPoolOop _constants;
//类加载器
oop _class_loader;
//protected域
oop _protection_domain;

可以看到,一个类该具有的东西,这里面基本上都包含了。

还有一点需要简单的介绍一下

在JVM中,对象在内存中的基本存在的形式就是oop。那么,对象所属的类,在JVM中也是一种对象,因此他们实际上也会被组织成一种oop,即klassOop。同样的,对于klassOop,也有对应的一个klass来描述,他就是klassKlass,也是klass的一个子类。klassKlass作为oop的klass链的端点。关于对象和数组的klass链大致如下图:

在这种设计下,JVM对内存的分配和回收,都可以采用统一的方式来管理。oop-klass-klassKlass关系如图:

内存存储

关于一个Java对象,他的存储是怎样的,一般很多人都会回答:对象存储在堆上。稍微好一点的人会回答:对象存储在堆上,对象的引用存储在栈上。我们再来一个更牛逼的回答

对象的实例(instantOopDesc)保存在堆上,对象的元数据(instantKlass)保存在方法区,对象的引用保存在栈上。

其实如果细追究的话,上面这句话优点故意卖弄的意思,因为我们都知道,方法区用于存储虚拟机加载的类信息、常量、静态变量、即使编译器后的代码等数据。所谓加载的类信息,其实不就是给每个人被加载的类都创建一个instantKlass对象。

可以参考下面这段代码

class Model
{
public static int a = 1;
public int b;

public Model(int b) {
    this.b = b;
}

}

public static void main(String[] args) {
int c = 10;
Model modelA = new Model(2);
Model modelB = new Model(3);
}

存储结构如下:

从上图可以看到,在方法区的instantKlass中有一个int a=1的数据存储。在堆内存中的两个对象的oop中,分别维护着int b=3.int b=2的实例数据。和oopDesc一样,instantKlass也维护着一些fields,用来保存类中定义的类数据,比如int a=1。

总结

每一个Java类,在被JVM加载的时候,JVM会给这个类创建一个instanceKlass,保存在方法区,用来在JVM层表示该Java类。当我们在Java代码中,使用new创建一个对象的时候,JVM会创建一个instanceOopDesc对象,这个对象中包含了两部分信息,对象头以及元数据。对象头中有一些运行时的数据,其中就包括和多线程相关的锁的信息。元数据其实就是维护的是指针,指向的是对象所属的类的instanceKlass。

3. 小结

本文介绍了函数式编程,面向过程,面向对象变成思想,并且通过虚拟机中实现来解释说明关键字,函数,对象的实现。

4. 参考

面向过程

https://blog.csdn.net/weixin_44769592/article/details/93393970

面向对象

https://baike.baidu.com/item/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/2262089?fr=aladdin

oop实现

https://www.cnblogs.com/Mateo-dengmin/p/15837645.html

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注