前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM|02内存模型

JVM|02内存模型

作者头像
微笑的小小刀
发布2019-09-03 18:17:45
4360
发布2019-09-03 18:17:45
举报
文章被收录于专栏:java技术大本营java技术大本营

JVM内存模型

概述

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

程序计数器

  1. 程序计数器是一个线程私有的,是一块很小的区间,可以被理解为程序记录行号器。
  2. 为什么说是线程私有的呢? 因为线程在多线程的情况下,需要进行来回的切换线程,所以就需要有一个程序计数器来记录下当前线程跳转之前执行到的地方的位置,所以这块区域只能是线程私有的。
  3. 如果线程执行的是java方法,那么程序计数器记录的将会是虚拟字节码指令的地址;如果是native方法,计数器将会为null,此时的线程不需要他来记录断点的地方,会由底层汇编语言记录;
  4. 程序计数器区是唯一的一个没有OutOfMemoryError的区域。

虚拟机栈

  1. 虚拟机栈也是一个线程私有的,内部存放的是一个个的栈帧,栈帧存放的就是一个个的方法,内部所存储的是方法的运行信息,包括局部变量表、操作数栈、动态连接、方法的返回地址信息。
  2. 栈的数据结构是先进后出,而我们平时说的栈其实是在指局部变量表,它的最小的局部变量表空间单位为Slot,虚拟机没有指明Slot的大小,但在jvm中,long和double类型数据明确规定为64位,这两个类型占2个Slot,其它基本类型固定占用1个Slot,而我们的方法内部的一些局部变量以及需要用到的全局变量都会放在方法的局部变量表中。
  3. 操作数栈是一块用来进行对局部变量表中的变量进行操作的区域,也是先进后出; 譬如我们要对i进行i++,步骤如下: 方法先将i的放入局部变量表中,后将i的值压入操作数栈中,后将1压入操作数栈中,执行反编译指令iadd,让栈顶两int型数值出栈并且相加,结果压进操作数栈中,然后将操作数栈顶元素pop出放回局部变量表中的i对应的值。
  4. 方法返回地址:指向一条字节码指令的地址
  5. 栈帧与局部变量表都是在编译期间确定的内存空间,运行期间不会改变。
  6. Java虚拟机栈可能出现两种类型的异常: 线程请求的栈深度大于虚拟机允许的栈深度,将抛出StackOverflowError。 虚拟机栈空间可以动态扩展,当动态扩展是无法申请到足够的空间时,抛出OutOfMemory异常。

本地方法栈

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

方法区

  1. 方法区是线程共享的,用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。
  2. 方法区内部还有一块运行常量池,用于存放编译期间生成的各种字面量和符号引用。

  1. 堆是一块线程共享的,多线程的情况下需要线程同步机制,它的目的是存放对象实例。 jdk1.8的堆内存模型如下:
  1. 年轻代Young Generation: 绝大部分的新建的对象都放在Eden Memory,如果Eden Memory溢出了,则要进行GC回收 后将未被GC回收的对象放入移动到 From Survivor 或 To Survivor 中。 此时 Minor GC 也会检查和移动 From Survivor 和 To Survivor中的对象,目的使 From Survivor,To Survivor其中一个置为空。在这个过程中会进行对象被移动次数的统计,设计一个移动次数的阈值 对于多次在survivor区中移动并且没有被GC的超过阈值的对象,会被移动到老年代;
  2. 老年代 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 执行的时间。
  3. 图中还有一块区域没有显示出来,就是元数据空间, Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中,这也是与1.7的永久代最大的区别所在。而元数据空间就是上文说到的方法区存放的东西。 至于为什么还有这样的区分:我的理解是方法区是jvm虚拟机规范的一种说法,具体的落地实践在jdk1.7是通过在堆中定义一个永久代来实现,jdk1.8是通过在堆外定义的一个元数据空间来实现。 为什么jdk1.7的永久区要被废除: 官网给出的解释是:
代码语言:javascript
复制
This is part of the JRockit and Hotspot convergence effort. JRockit
customers do not need to configure the permanent generation (since JRockit
does not have a permanent generation) and are accustomed to not
configuring the permanent generation.
移除永久代是为融合HotSpot JVM与 JRockit VM而做出的努力,因为JRockit没有永久代,
不需要配置永久代
现实使用中,由于永久代内存经常不够用或发生内存泄露,爆出异常
java.lang.OutOfMemoryError: PermGen。
基于此,将永久区废弃,而改用元空间,改为了使用本地内存空间。

JVM内存分析指令

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

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

1. -class 查看类加载统计
代码语言:javascript
复制

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

2. -compiler 查看编译统计
代码语言:javascript
复制
[root@hadoop101 ~]# jstat -compiler 3846
Compiled Failed Invalid   Time   FailedType FailedMethod
    2174      0       0     6.38          0

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

3. -gc 查看垃圾回收统计(十分常用)
代码语言:javascript
复制
[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.493
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
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
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
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

解释:(你会发现总有一个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 端口号

代码语言:javascript
复制
[root@hadoop101 ~]# jmap -heap 3846
Attaching 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% used
Eden Space:  # Eden 使用情况
   capacity = 12648448 (12.0625MB)
   used     = 4411808 (4.207427978515625MB)
   free     = 8236640 (7.855072021484375MB)
   34.880231946243526% used
From Space:  # From Surivior 使用情况
   capacity = 1572864 (1.5MB)
   used     = 33376 (0.031829833984375MB)
   free     = 1539488 (1.468170166015625MB)
   2.1219889322916665% used
To Space:  # To Survivor 使用情况
   capacity = 1572864 (1.5MB)
   used     = 0 (0.0MB)
   free     = 1572864 (1.5MB)
   0.0% used
tenured 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

代码语言:javascript
复制
[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=目标文件全路径 端口号

代码语言:javascript
复制
[root@hadoop101 ~]# jmap -dump:format=b,file=dump.dat 3846
Dumping heap to /root/dump.dat ...
Heap dump file created
[root@hadoop101 ~]# ll
total 37788
-rw-------. 1 root root     5418 Apr  6  2017 anaconda-ks.cfg
-rw-------. 1 root root 29300407 Aug 14 14:16 dump.dat
drwxr-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 浏览器访问端口号 文件

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 java技术大本营 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区
  • JVM内存分析指令
    • 通过jstat命令进行查看堆内存使用情况
      • 1. -class 查看类加载统计
      • 2. -compiler 查看编译统计
      • 3. -gc 查看垃圾回收统计(十分常用)
    • 通过jmap指令来分析内存溢出
      • 查看内存使用情况
      • 查看内存中对象数量及大小
      • 将内存使用情况dump到文件中
      • 通过jhat对dump文件进行分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档