Java内存结构
顾名思义,主要是关于Java的运行环境JVM的内存功能划分的概念,一般提到Java内存结构,主要就是说JVM的内存结构。随着JDK版本的迭代更新,JVM内部的结构也在有着不同程度的更新,如JDK7中的Oracle HotSpot 开始移除永久代,JDK8中,永久代被元空间(MetaSpace)所取代等等。所以说Java的内存结构,主要也是说的这方面的概念。它与Java具体的程序运行分析无关,主要是说明了JVM的各个组成部分以及各组成部分的具体作用是什么。
Java的内存结构可以分为堆内内存和堆外内存。简单来说,堆内内存是Java程序运行时的主要使用空间,它受Java虚拟机的内存管理所控制,对于Java开发人员来说,它是透明的,开发人员无法准确控制。但是可以通过一些手段进行调控,从而使程序性能的性能得以提升。堆外内存是指JVM以外的区域,它不受虚拟机的内存管理所控制,而是有操作系统直接管理。
堆内内存
这块主要是JVM的内部组成结构,如方法区、堆区、栈区、垃圾回收系统等等。不同的区有不同功能,具体功能这里不再赘述,其中占据最大空间的就是堆区,提到堆区,就不得不提堆区的划分:新生代、老年代(这里将永久代也划分为一种特殊的老年代)。不同的区存储不同“年龄”的对象,JVM的垃圾回收机制,可以根据不同的对象区采用不同的垃圾回收策略。
堆外内存
JVM的内存管理虽然带来了使用的方便,同时也带来了降低程序性能的问题,这个也是Java一直为大众所诟病的问题。因为各种算法就算再精确,也不可能做到及时有效地回收无用的内存,而且在垃圾回收的时候,甚至会造成其他工作的暂停(Stop the World),这样会严重影响系统的整体性能。另外,对于堆内的内容在flush到远程时,它会先复制到直接内存(即堆外内存),然后再发送,这对性能也有一定的影响。
Java引入堆外内存,它就解决了上面的两个痛点,首先是垃圾回收问题,堆外内存的垃圾回收不是JVM来完成的,需要开发者自己来管理,具体什么时间进行回收,由开发人员自己决定,这样避免的垃圾回收的时机不准的问题;但是它同样带来了挑战,它对开发人员的水平有一定要求,而且一旦出现问题,一般很难排查;然后是flush到远程时的情况,这里因为它本身使用的就是直接内存,省去了复制的步骤,所以堆外内存的操作一般效率比较高。
其他内容不再赘述,具体可参考: JVM内存结构总结
Java内存模型
概念
Java的内存模型就是描述Java程序中各个变量(实例域、静态域和数组元素)之间的关系,以及在系统中对变量的存储和取出操作的具体底层细节。它主要针对的是Java在运行期间,数据在内存中的流转过程的具体实现细节。不涉及具体的物理结构,是一种逻辑上的运行规则。
线程工作内存
而Java的内存结构一般都直接并发相关联。一般Java在工作时,内部线程的工作空间有两部分:线程的本地工作空间(也是线程的数据缓存空间)和公共共享空间(主内存)。线程间的通讯是通过将数据刷到主内存中来完成的。主内存区域是线程间共享的区域,任何线程都可以访问,而本地工作空间是线程私有空间,线程之间不可互相访问。
SR-133内存模型
SR -133内存模型,这是新的Java内存模型标准(JMM)。它主要的目的就是为了阐述基于内存操作时,进程(线程)之间的可见性原则 。之前老版本的Java内存模型存在很大的争议性,也有很多严重的问题,所以JDK5以后引入了SR-133内存模型。
重排序
同时提到内存模型就不得不了解重排序概念。它是编译器以及处理器内部的优化过程,也就是说开发人员写出的代码与真正编译后执行的指令顺序可能是不一样的,但是它能保证在单线程下程序的执行结果是不会发生变化的。这些对Java开发人员是透明的,但是如果不了解它,出了问题就很难深究其具体原因。
Volatile和Synchronized
另外提到并发,就需要了解Java在并发环境下保证执行语句的原子性和可见性方式:volatile(不确保原子性,确保可见性)和synchronized(可以确保原子性和可见性)。
其它内容就不再赘述,具体可参考:Java内存模型
Java对象模型
概念
Java的对象模型说的是Java中的对象在内存中的存储结构。它描述的是内存中Java对象的内部组成结构,相比较于前面两个“宏观”概念,它属于“微观”视角。在内存中,一个Java对象包含三部分:对象头、实例数据和对齐填充。 其中对象头是一个很关键的部分,因为对象头中包含锁状态标志、线程持有的锁等标志 。下面基于HotSpot虚拟机来分析Java对象模型。
OOP-Klass Model
HotSpot是基于C++实现的。这里的OOP指的是普通对象指针(Ordinary Object Pointer)。OOP-Klass结构中包含的内容很多,大都涉及到C++的具体实现。这里只需记住一个结果:在Java程序运行过程中,每创建一个新的对象,在JVM内部就会相应地创建一个对应类型的OOP对象。oop在C++中定义的类型为oopDesc类型,oopDesc是所有OOPS类的共同基类,例如:instanceOopDesc,arrayOopDesc等等,它们的基类都是oopDesc。不同的子类有不同的使用场景。如:instanceOopDesc表示对象实例,即当我们new一个对象的时候,JVM会创建一个instanceOopDesc对象来表示这个Java对象;同理,当我们new一个Java数组的时候,JVM会创建一个arrayOopDesc对象来表示这个数组对象。还有其他各种类型:
//定义了oops共同基类
typedef class oopDesc* oop;
//表示一个Java类型实例
typedef class instanceOopDesc* instanceOop;
//表示一个Java方法
typedef class methodOopDesc* methodOop;
//表示一个Java方法中的不变信息
typedef class constMethodOopDesc* constMethodOop;
//记录性能信息的数据结构
typedef class methodDataOopDesc* methodDataOop;
//定义了数组OOPS的抽象基类
typedef class arrayOopDesc* arrayOop;
//表示持有一个OOPS数组
typedef class objArrayOopDesc* objArrayOop;
//表示容纳基本类型的数组
typedef class typeArrayOopDesc* typeArrayOop;
//表示在Class文件中描述的常量池
typedef class constantPoolOopDesc* constantPoolOop;
//常量池告诉缓存
typedef class constantPoolCacheOopDesc* constantPoolCacheOop;
//描述一个与Java类对等的C++类
typedef class klassOopDesc* klassOop;
//表示对象头
typedef class markOopDesc* markOop;
在HotSpot中,oopDesc、instanceOopDesc和arrayOopDesc分别是定义在不同.hpp文件中的(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,里面并没有定义其他的数据结构。而从oopDesc源码中可以发现,它里面实际可以分成三块:markOop _mark、共用体 _metadata以及各种field字段。
_mark:“标记”的意思,它里面存储了相关对象的锁信息,GC分代信息等。
field:都是各种在oop中定义的字段内存地址,都有各自的使用方式,这里不做深入分析。
_metadata:它里面有两个内容,_klass(表示普通指针)、_compressed_klass(是压缩类指针)。
这里主要分析klass
Klass
和oopDesc所处的地位一样,Klass类是其他Klass类型的基类。它有两个功能:
- 实现语言层面的Java类(在Klass基类中已经实现)
- 实现Java对象的分发功能(由Klass的子类提供虚函数实现)
其实由此可以推断出,实际上在Java中的类在底层实现上也是一种对象,也就是Klass所对应的实例。例如:instanceKlass类型,它实际上就是在Java中,每加载一个类,都会创建一个instanceKlass对象,所以在底层实现上也可以说类也是一种“对象”,只不过是一种特殊的对象。
基于Java类也是一种“对象”的观点,自然可以想到:既然也是对象,那么在JVM中,它的存储方式实际上与对象实例的存储方式是相类似的,它也有对应的oop,只不过是klassOop,而且如果是oop结构,那么肯定也会存在相对应的一个klass来描述(klassKlass )。klassKlass也是klass子类的一种。具体结构可以参考下图: 基于这种设计下,JVM对内存的分配和回收,都可以采用统一的方式来管理。oop-klass-klassKlass关系如图: 这里有一个很好的例子:
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);
}
具体存储结构如下图:
具体对象模型分析可以参考博客:深入理解多线程(二)--- Java对象模型,深入理解多线程(三)--- Java的对象头
总结:
在JVM加载类的时候,会创建一个instanceKlass,因为JVM中类加载信息存储在方法区中,所以instanceKlass会在方法区中创建。当我们在Java中new一个对象的时候,无论是普通对象还是数组,都是对象的一种,它会在JVM中创建一个对应的instanceOopDesc对象,来表示Java中的对象,它里面主要结构有三部分:_mark部分(用于存储对象的锁信息,GC分代信息等)、_metadata(共用体,存储一个普通klass指针,指向对象所属类,_compressed_kass,压缩类的指针)。而且在JVM中,实际上类在底层实现上也是一种对象,它也有与Java对象在底层实现上相类似的结构,也会有一个klass来描述所属类型,所以不难想象出,这里的klass实际上就形成了一种链式结构。