Java内存模型(Java Memory Model)是Java语言规范定义的一套规则,提供了一组规则和同步机制,以确保多线程程序在多线程环境下正确地处理内存访问的一致性和可见性问题。开发人员在编写多线程程序时,需要遵守Java内存模型的规则,并使用适当的同步机制来保证程序的正确性。
1、Java内存模型主要关注以下两个方面:
内存一致性(Memory Consistency):在多线程环境下,多个线程同时访问内存中的数据时,如何保证数据的一致性。
可见性(Visibility):在一个线程修改了共享变量的值后,其他线程如何能够立即看到该修改。
2、为了解决这些问题,Java内存模型提供了以下同步机制:
原子性(Atomicity):对于一些基本数据类型,如int、long等,Java内存模型提供了原子操作,保证这些操作在多线程环境下是原子性的,即不会被其他线程干扰。
互斥性(Mutual Exclusion):使用synchronized关键字或ReentrantLock等同步机制,将代码块或方法声明为互斥的,保证同一时间只有一个线程能够访问该代码块或方法,从而避免多个线程同时修改共享变量的问题。
可见性(Visibility):使用volatile关键字或synchronized关键字等同步机制,保证一个线程修改了共享变量的值后,其他线程能够立即看到该修改。
无序性(Memory Reordering):Java内存模型允许编译器和处理器对指令进行重排序,以提高执行效率。但是,这可能会导致多线程环境下的一些问题。因此,需要使用volatile关键字或synchronized关键字等同步机制来保证指令顺序执行。
1
Java 对象内存布局
一个 Java 对象在内存中包括对象头、实例数据和补齐填充3个部分:
对象头包括以下3个部分:
Mark Word:包含一系列的标记位,比如对象的哈希码、分代年龄、锁标志等等。在 32 位系统占 4 字节,在 64 位系统中占 8 字节。
Class Pointer:用来指向对象对应的类元数据的内存地址。在 32 位系统占 4 字节,在 64 位系统中占 8 字节。
Length:如果是数组对象,还有一个保存数组长度的空间,占4个字节,数组对象特有的。
实例数据:包括对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte 和 boolean 是 1 个字节,short 和 char 是 2 个字节,int 和 float 是 4 个字节,long 和 double 是 8个字节,对于引用类型来说,在 32 位系统上占用 4 个字节, 在 64 位系统上占用 8 个字节。
对齐填充:所有 Java 对象占用的字节数必须是 8 的倍数,也就是说如果一个 Java 对象占用的空间不是 8 的倍数,为了保证对象的大小为 8 字节的整数倍,那么就需要补齐填充为 8 的倍数。
2
Java 内存模型介绍
JVM 内存模型分为非堆区(Metaspace)和堆区,堆区分为两大块:一块是 Old 区,一块是 Young 区,Old:Young=2:1;Young区分为两大块:一块是 Eden 区,一块是 Survivor区(S0+S1),S0 和 S1 一样大,也可以叫 ServivorFrom、 ServivorTo,Eden:S0:S1=8:1:1。
一般对象和数组的创建会在堆中分配内存空间,新创建的 Java 对象存放在 Eden 区。
Eden 区:新创建的 Java 对象存放在 Eden 区(如果新创建的对象占用内存很大,则直接分配到老年代 Old区),当 Eden 区内存不够的时候就会触发 MinorGC,对新生代 Young 区进行一次垃圾回收。每经历一次Minor GC(复制算法回收对象)就会让对象的年龄加 1,当对象年龄为 15 时就会把新生代的对象放入老年代中(年龄默认是 15 岁,可以通过参数 -XX:MaxTenuringThreshold 来设置)。
比如有对象 Object1,Object2,Object3 等在 Eden 区,但是 Eden区的内存空间是有限的,比如有 500M,假如已经使用了 500M 或者达到设定内存的临界值,这时候就需要对 Eden 区的内存空间进行清理,即垃圾收集(Garbage Collect),这样的 GC 我们称之为 Minor GC,Minor GC 指的就是对 Young 区的 GC。经过 GC 之后,有些对象就会被清理掉,有些对象可能还存活着,对于存活着的对象需要将其复制到 Survivor 区,然后再清空 Eden 区中的这些对象。
Survivor 区:Survivor 区分为两块 S0 和 S1,也可以叫做 ServivorFrom 和 ServivorTo,在同一个时间点上,S0 和 S1 只能有一个区有数据,另外一个是空的。
假如一开始只有 Eden 区和 S0 中有对象,S1 中是空的,此时进行一次 GC 操作, S0 区中的对象的年龄就会加 1,若 S0 区中对象年龄达到设置的年龄阈值,此时对象会被移动到 Old 区,如果 Eden 区和 S0 区没有达到年龄阈值的对象会被复制到 S1 区,此时 Eden 区和 S0 区已经被清空了(没有被 GC 的对象要么到了Old 区,要么到了 S1 区), 这时候 S0 和 S1角色互换,之前的 S0 变成了 S1,之前的 S1 变成了 S0,也就是说无论如何都要保证名为 S1 的 Survivor 区域是空的(用于保留经过一次 Minor GC 后的存活对象),Minor GC 会一直重复这样的过程,直到 S1 区被填满,然后会将所有对象复制到老年代中。
Old 区:一般 Old 区都是年龄比较大的对象,或者超过了设置的年龄阈值的对象,在 Old 区也会有 GC 的操作,Old 区的 GC 我们称作为 Major GC。
我们经常说的 3 种 GC 方式:
Minor GC:新生代
Major GC:老年代
Full GC:新生代 + 老年代(因为 Major GC 一般伴随着 Minor GC,两个一起就是 Full GC)。
3
使用 jvisualvm 查看内存使用情况
在 jdk 安装目录 bin 文件夹里找到 jvisualvm.exe,点击可以打开 Java VisualVM,或者在命令行打开(需要配置环境变量),如下:
安装 Visual GC 插件
可以在 Java VisualVM 面板里,点击“工具 ---> 插件 ---> 可用插件”里找到 Visual GC 安装,或者添加自己下载的 Visual GC 插件,如下:
插件 Visual GC 下载:https://visualvm.github.io/uc/8u131/updates.html
下载后的文件:com-sun-tools-visualvm-modules-visualgc.nbm 大小:43.4kb。
安装之后,重启 Java VisualVM,出现 Visual GC 页面,安装成功。
在 Visual GC 页面,我们可以清晰的看到具体的内存使用情况,同时也验证了 Java 内存模型就是分为非堆区(Metaspace)和堆区(Old+Young(Old+Eden+S0+S1)),如下:
后面将为大家介绍垃圾回收算法。
标签:Java,Eden,对象,模型,GC,内存,S1 From: https://blog.51cto.com/javazyx/6991980