JVM|02内存模型

Posted java技术大本营

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM|02内存模型相关的知识,希望对你有一定的参考价值。

JVM内存模型

概述

Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
根据java虚拟机的规范,我们可以将JVM的内存分为五大块


程序计数器

  1. 程序计数器是一个线程私有的,是一块很小的区间,可以被理解为程序记录行号器。

  2. 为什么说是线程私有的呢?
    因为线程在多线程的情况下,需要进行来回的切换线程,所以就需要有一个程序计数器来记录下当前线程跳转之前执行到的地方的位置,所以这块区域只能是线程私有的。

  3. 程序计数器区是唯一的一个没有OutOfMemoryError的区域。

虚拟机栈

  1. 栈的数据结构是先进后出,而我们平时说的栈其实是在指局部变量表,它的最小的局部变量表空间单位为Slot,虚拟机没有指明Slot的大小,但在jvm中,long和double类型数据明确规定为64位,这两个类型占2个Slot,其它基本类型固定占用1个Slot,而我们的方法内部的一些局部变量以及需要用到的全局变量都会放在方法的局部变量表中。

  2. 操作数栈是一块用来进行对局部变量表中的变量进行操作的区域,也是先进后出;
    譬如我们要对i进行i++,步骤如下:
    方法先将i的放入局部变量表中,后将i的值压入操作数栈中,后将1压入操作数栈中,执行反编译指令iadd,让栈顶两int型数值出栈并且相加,结果压进操作数栈中,然后将操作数栈顶元素pop出放回局部变量表中的i对应的值。

  3. 栈帧与局部变量表都是在编译期间确定的内存空间,运行期间不会改变。

  4. Java虚拟机栈可能出现两种类型的异常:
    线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。
    虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。

本地方法栈

  1. 本地方法栈的调用与虚拟机栈十分相似,但是本地方法栈是在调用native方法才会使用的,底层调用的是系统的C或者C++的方法。

方法区

  1. 方法区是线程共享的,用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。

  2. 方法区内部还有一块运行常量池,用于存放编译期间生成的各种字面量和符号引用。

  1. 堆是一块线程共享的,多线程的情况下需要线程同步机制,它的目的是存放对象实例。
    jdk1.8的堆内存模型如下:

  2. 年轻代Young Generation:
    绝大部分的新建的对象都放在Eden Memory,如果Eden Memory溢出了,则要进行GC回收
    后将未被GC回收的对象放入移动到 From Survivor 或 To Survivor 中。
    此时 Minor GC 也会检查和移动 From Survivor 和 To Survivor中的对象,目的使 From Survivor,To Survivor其中一个置为空。在这个过程中会进行对象被移动次数的统计,设计一个移动次数的阈值
    对于多次在survivor区中移动并且没有被GC的超过阈值的对象,会被移动到老年代;

  3. 老年代 Old Generation
    与 Young Generation相同,当 Old Generation区满了之后将执行 GC 操作,该操作称为:Major GC,耗用的时间也相对较长。
    Young Generation和 Old Generation都可以主动触发 stop-the-world 事件,挂起所有任务,执行 GC 操作。被挂起的任务只有在 GC 执行完毕后,才会恢复执行。
    多数情况下, GC 性能调优(GC tuning)就是指降低 stop-the-world 时 GC 执行的时间。

  4. 图中还有一块区域没有显示出来,就是元数据空间,
    Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。而元数据空间就是上文说到的方法区存放的东西。
    至于为什么还有这样的区分:我的理解是方法区是jvm虚拟机规范的一种说法,具体的落地实践在jdk1.7是通过在堆中定义一个永久代来实现,jdk1.8是通过在堆外定义的一个元数据空间来实现。
    为什么jdk1.7的永久区要被废除:
    官网给出的解释是:

 
   
   
 
This is part of the JRockit and Hotspot convergence effort. JRockitcustomers do not need to configure the permanent generation (since JRockitdoes not have a permanent generation) and are accustomed to notconfiguring the permanent generation.移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代现实使用中,由于永久代内存经常不够用或发生内存泄露,爆出异常java.lang.OutOfMemoryError: PermGen。基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。

JVM内存分析指令

通过jstat命令进行查看堆内存使用情况

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:
jstat [-命令选项] [端口号] [间隔时间/毫秒] [查询次数]
常用的有三个命令选项

1. -class 查看类加载统计

 
   
   
 
[root@hadoop101 ~]# jps3846 Bootstrap6714 Jps[root@hadoop101 ~]# jstat -class 3846Loaded Bytes Unloaded Bytes Time  3612 7741.5 0 0.0 7.58


解释:
Loaded:加载class的数量
Bytes:所占用空间大小
Unloaded:未加载数量
Bytes:未加载占用空间
Time:时间

2. -compiler 查看编译统计

[root@hadoop101 ~]# jstat -compiler 3846Compiled Failed Invalid Time FailedType FailedMethod 2174 0 0 6.38 0

解释:
Compiled:编译数量。
Failed:失败数量
Invalid:不可用数量
Time:时间
FailedType:失败类型
FailedMethod:失败的方法

3. -gc 查看垃圾回收统计(十分常用)

[root@hadoop101 ~]# jstat -gc 3846 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 1536.0 1536.0 0.0 32.6 12352.0 3144.9 30820.0 20681.0 25344.0 24556.2 2816.0 2588.3 15 0.417 1 0.075 0.493# 间隔1秒 收集五次[root@hadoop101 ~]# jstat -gc 3846 1000 5 S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 1536.0 1536.0 0.0 32.6 12352.0 3144.9 30820.0 20681.0 25344.0 24556.2 2816.0 2588.3 15 0.417 1 0.075 0.4931536.0 1536.0 0.0 32.6 12352.0 3144.9 30820.0 20681.0 25344.0 24556.2 2816.0 2588.3 15 0.417 1 0.075 0.4931536.0 1536.0 0.0 32.6 12352.0 3144.9 30820.0 20681.0 25344.0 24556.2 2816.0 2588.3 15 0.417 1 0.075 0.4931536.0 1536.0 0.0 32.6 12352.0 3144.9 30820.0 20681.0 25344.0 24556.2 2816.0 2588.3 15 0.417 1 0.075 0.4931536.0 1536.0 0.0 32.6 12352.0 3144.9 30820.0 20681.0 25344.0 24556.2 2816.0 2588.3 15 0.417 1 0.075 0.493

解释:(你会发现总有一个survivor是空的,这也印证了我上面说到的年轻代的存储方式)
S0C:第一个Survivor区的大小(KB)
S1C:第二个Survivor区的大小(KB)
S0U:第一个Survivor区的使用大小(KB)
S1U:第二个Survivor区的使用大小(KB)
EC:Eden区的大小(KB)
EU:Eden区的使用大小(KB)
OC:Old区大小(KB)
OU:Old使用大小(KB)
MC:方法区大小(KB)
MU:方法区使用大小(KB)
CCSC:压缩类空间大小(KB)
CCSU:压缩类空间使用大小(KB)
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

通过jmap指令来分析内存溢出

前面通过jstat可以对jvm堆的内存进行统计分析,而jmap可以获取到更加详细的内容,
如:内存使用情况的汇总、对内存溢出的定位与分析。

查看内存使用情况

指令格式:jmap -heap 端口号

[root@hadoop101 ~]# jmap -heap 3846Attaching to process ID 3846, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.111-b14
using thread-local object allocation.Mark Sweep Compact GC
Heap Configuration: # 堆内存的配置信息 MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 482344960 (460.0MB) NewSize = 10485760 (10.0MB) MaxNewSize = 160759808 (153.3125MB) OldSize = 20971520 (20.0MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)
Heap Usage: # 堆内存的使用情况New Generation (Eden + 1 Survivor Space): # 年轻代 capacity = 14221312 (13.5625MB) used = 4445184 (4.2392578125MB) free = 9776128 (9.3232421875MB) 31.257200460829495% usedEden Space: # Eden 使用情况 capacity = 12648448 (12.0625MB) used = 4411808 (4.207427978515625MB) free = 8236640 (7.855072021484375MB) 34.880231946243526% usedFrom Space: # From Surivior 使用情况 capacity = 1572864 (1.5MB) used = 33376 (0.031829833984375MB) free = 1539488 (1.468170166015625MB) 2.1219889322916665% usedTo Space: # To Survivor 使用情况 capacity = 1572864 (1.5MB) used = 0 (0.0MB) free = 1572864 (1.5MB) 0.0% usedtenured generation: # 老年代 capacity = 31559680 (30.09765625MB) used = 21177312 (20.196258544921875MB) free = 10382368 (9.901397705078125MB) 67.10242942894224% used
13885 interned Strings occupying 1883624 bytes.

查看内存中对象数量及大小

查看所有对象,包括活跃以及非活跃的
jmap ‐histo 端口号 | more
查看活跃对象
jmap ‐histo:live 端口号 | more


[root@hadoop101 ~]# jmap -histo:live 3846 | more
num #instances #bytes class name---------------------------------------------- 1: 36197 6581296 [C 2: 3052 1689256 [I 3: 985 1004736 [B 4: 34556 829344 java.lang.String 5: 15257 488224 java.util.HashMap$Node 6: 4042 466008 java.lang.Class 7: 4453 391864 java.lang.reflect.Method 8: 4214 265160 [Ljava.lang.Object; 9: 6799 217568 java.util.concurrent.ConcurrentHashMap$Node 10: 977 175600 [Ljava.util.HashMap$Node; 11: 89 104880 [Ljava.util.concurrent.ConcurrentHashMap$Node; 12: 5975 95600 java.lang.Object 13: 1177 84016 [Ljava.lang.String; 14: 1615 77520 java.util.HashMap 15: 2602 54072 [Ljava.lang.Class; 16: 89 49424 [Ljava.util.WeakHashMap$Entry; 17: 608 48640 java.lang.reflect.Constructor 18: 1430 45760 java.util.Hashtable$Entry 19: 1096 43840 java.lang.ref.SoftReference 20: 1047 41880 java.util.LinkedHashMap$Entry 21: 1027 41080 java.util.TreeMap$Entry 22: 837 40176 org.apache.tomcat.util.modeler.AttributeInfo 23: 9 37008 [Ljava.nio.ByteBuffer; 24: 52 35264 [S 25: 125 34416 [[C 26: 974 31168 java.lang.ref.WeakReference 27: 1180 28320 java.util.ArrayList

解释:
B byte
C char
D double
F float
I int
J long
Z boolean
[ 数组,如[I表示int[]
[L+类名 其他对象

将内存使用情况dump到文件中

有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对它进行分析,jmap也
是支持dump到文件中的
语法:jmap ‐dump:format=b,file=目标文件全路径 端口号

[root@hadoop101 ~]# jmap -dump:format=b,file=dump.dat 3846Dumping heap to /root/dump.dat ...Heap dump file created[root@hadoop101 ~]# lltotal 37788-rw-------. 1 root root 5418 Apr 6 2017 anaconda-ks.cfg-rw-------. 1 root root 29300407 Aug 14 14:16 dump.datdrwxr-xr-x. 2 root root 6 Aug 14 08:54 jvm-rw-r--r--. 1 root root 571 Aug 14 10:32 JvmTest.class-rw-r--r--. 1 root root 250 Aug 14 08:56 JvmTest.java-rw-r--r--. 1 root root 9366128 Apr 4 2017 node-v6.10.2-linux-x64.tar.xz-rw-------. 1 root root 5098 Apr 6 2017 original-ks.cfg[root@hadoop101 ~]#

通过jhat对dump文件进行分析

我们将jvm的内存dump到文件中,这个文件是一个二进制的文件,不方便查看,这时我们可以借助于jhat工具进行查看。
语法: jhat ‐port 浏览器访问端口号 文件

[root@hadoop101 ~]# jhat -port 8081 dump.datReading from dump.dat...Dump file created Wed Aug 14 14:16:57 UTC 2019Snapshot read, resolving...Resolving 271656 objects...Chasing references, expect 54 dots......................................................Eliminating duplicate references......................................................Snapshot resolved.Started HTTP server on port 8081Server is ready.

我们可以通过服务器ip:8081查看
在最后还有一个OQL的查询功能,语法类似SQL,可以快速查询到想要看的数据

以上是关于JVM|02内存模型的主要内容,如果未能解决你的问题,请参考以下文章

详解Jvm内存结构

详解Jvm内存结构

不止面试02-JVM内存模型面试题详解

02-JVM内存模型:虚拟机栈与本地方法栈

jvm内存模型

02 java内存模型