我是靠谱客的博主 痴情香烟,这篇文章主要介绍jvm 优化(一):内存结构前言目录正文1.jdk7和jdk8内存结构区别2.各垃圾回收算法对比3.各垃圾收集器对比4.基础jvm命令参考:,现在分享给大家,希望可以做个参考。

原文链接:https://blog.csdn.net/weixin_40533111/article/details/85678805 作者四月天五月雨_,转载请注明出处,谢谢

前言

本文主要写jdk7和jdk8的内存结构区别和常规操作,后几篇会写调优工具和优化方式.

目录

1.jdk7和jdk8内存结构区别
2.各垃圾回收算法对比
3.各垃圾收集器对比
4.基础jvm命令

正文

1.jdk7和jdk8内存结构区别

在jvm结构中,对内存划分了:

  1. 程序计数器
  2. 2.Java虚拟机栈2.Java虚拟机栈
  3. 本地方法栈
  4. Java堆
  5. 方法区
    在实际运行中,堆占据操作的大部分内存,常见的OOM和一些调优手段也是针对堆进行的.

1.1 jdk7堆内存分布如下:

在这里插入图片描述

Young(年轻代):

年轻代分为三部分:Eden+Survivor * 2,两个survivor一样大小,刚创建的对象在Eden区,当Eden区放不下新对象时,触发垃圾回收,主流的虚拟机(如hotspot)在年轻代的回收策略默认为复制算法,把Eden区经过一轮仍存活的对象复制到另一个空闲的survivor区,如果放不下,这个对象则直接放到老年代,jvm提供参数:XX:PretenureSizeThreshold来设置大对象阈值,不经过回收,超过阈值的直接进入老年代

Tenured(老年代):

Tenured区主要保存生命周期长的对象,当一些对象在Young复制转移多次(默认15次)以后,对象就会被转移到Tenured区,或者一些系统级对象,也可进入老年代

Perm(永久代):

永久代主要保存class,method,filed对象,随着工程的启动就确定了

Virtual(可调尺度)

virtual就是堆初始大小-Xms 和堆最大大小-Xmx的之间的可调跨度,生产中为了避免堆内存频繁扩大,可以设置-Xms等于-Xmx.

1.2 jdk8堆内存分布如下:

在这里插入图片描述
相比jdk1.7,可以明显看出,在jdk1.8中没有了永久代的概念,在之前,永久代是Hotspot虚拟机所属的,后来sun公司被Oracle收购,为了融合另一款优秀的虚拟机JRockit,牺牲了永久代,用元空间代替,因为在其他的虚拟机中没永久代的概念.

1.3 堆中各区域大小对比

默认情况下:
年轻代:老年代=1:2
年轻代=Eden+survivor1+survivor2
Eden:survivor1:survivor2=8:1:1
即新生代的可用极限内存为容量的90%,当然,一般情况下远达不到这个值,就触发gc了

手动设置:
可以通过参数 –XX:NewRatio 来指定

2.各垃圾回收算法对比

简述流程和优缺点.

2.1 标记-清除算法

流程: 顾名思义,执行时分两步,1.先标记需要回收的对象,2.完成标记后开始回收.
优点: 这个算法比较特殊,这是一个基础算法,为后续其他算法提供了方向,它的优点就是它是其他回收算法的垫脚石.
缺点: 作为最基础的算法,两个主要缺点:1.效率不高,先标记,在清除,太笼统,不够细化. 2.空间问题,对象活着的时候分布在堆中各区域,直接清除后,会产生碎片,造成内存空间不连续,导致后来再分配大对象时,连续空间不够,从而放不下,从而触发新的回收动作,性能不足
在这里插入图片描述

2.2 复制算法

流程: 将年轻代划分为三个区域:Eden+survivor1+survivor2,其中两个survivor区大小严格一致,比例为8:1:1,即新对象的可用空间为新生代的90%,当空间不够时,触发垃圾回收,将Eden区和survivor1区仍然存货的对象,复制到survivor2(每执行一次回收,就复制到另一块survivor区),如果survivor区放不下,对象直接晋升到老年代.
优点: 解决了效率问题,内存复制,速度是很可观的,
但是: 当年轻代对象的存活率较高时,一次次的垃圾回收,大量对象在survivor1和survivor2间复制来复制去,内存并内有显著减少,反而浪费了时间,效率不高
缺点: 空间问题,jdk8整个堆分为新生代+老年代,比例为:1:2,上面提到:年轻代最高可用内存为90%,即堆内存整体可用内存为:29/30,有1/30是无用状态,但权衡考虑,这是一种很好的回收策略了
在这里插入图片描述

2.3 标记-整理算法

流程: 思路和标记-清除算法基本一致,只是在清除前多了一步整理—垃圾回收时,先标记应回收的对象,把存活的对象移动到内存的一端,此刻再执行清除,清除另一端的内存,可以获取连续空间
优点: 增加了空间利用率
缺点: 动作多了,效率自然低了
在这里插入图片描述

2.4 分代收集算法

这并不是一种新算法,是一种组合方式,现在的商用虚拟机,垃圾收集都是采用这种.
流程:根据对象存活周期,将堆内存划分为几块,年轻代(Eden+survivor*2),老年代,然后根据各个年代的对象存活特点,选择相应的算法,比如年轻代,很多对象朝生夕死,显然复制算法更适合,而老年代多是活的时间较长的老对象,用标记-清除,或者标记-整理算法都行
优点:对内存的管理更细化,效率和空间利用率更高,就像中国房地产,现在房价严重虚高,高的吓银,为避免泡沫更大,政府出台限售政策,但并不是全国都限售,而是因地制宜,因城施策.我们的愿景是国.富.民.强,安.居.乐.业,…回归正题–_--

3.各垃圾收集器对比

垃圾收集器就是算法的实现,java虚拟机规范中对垃圾收集器的实现没有任何规定,因此各厂商,不同版本间的虚拟机提供的垃圾收集器可能存在差异,使用者可以根据自身的业务特点,选择不同的垃圾收集器组合,以达到最佳的性能

3.1 serial收集器

这是最基本,历史(JDK1.3.1之前的唯一选择)最长的垃圾收集器,单线程的收集器
流程: 触发垃圾回收时,serial会执行,同时停止虚拟机内其他所有线程,即传说中的"stop-the-world"出现了,直至垃圾回收结束,其他线程恢复
优点: 简单高效
缺点: 由于STW的出现,会让应用暂时不可用,不过对于client模式下的,单核CPU来说,它很适合,一二百M的新生代,回收时间100ms左右,更大的内存配比没试过,(待–)

3.2 parNew收集器

流程: parNew就是多线程版的serial收集器,其余行为包括控制参数都和serial一样,是虚拟机运行在server模式下的默认选择,并且唯一能与CMS搭配使用的收集器.
优点: 略高效,就是多线程的serial
缺点: 同serial

3.3 parallel Scavenge收集器

流程: 常规收集器都在努力缩短STW时间,parallel Scavenge的关注点是提高吞吐量,更大的利用CPU资源
优点: 提高了整体吞吐量
缺点: 单次回收过程,停顿时间长,适合用在后台的计算而不需要太多交互的场景,而常规收集器更适合用在有用户交互的场景中,单次STW时间短,用户体验好

3.4 Serial Old 收集器

它是serial收集器的老年代版本,也是单线程,使用"标记-整理算法",给client模式下的虚拟机使用(32位,过时了),
优点: 可作为CMS的后备收集器使用
缺点: 同年轻代版本serial

3.5Parallel Old 收集器

是Parallel Scavenge的老年代版本,关注吞吐量,在jdk1.6提供,在注重吞吐量优先的场景:可使用Parallel Scavenge收集器加Parallel Old收集器,以达到最高吞吐量

3.6 CMS收集器

CMS(Concurrent Mark-Sweep),是一种一种以获得最短停顿时间为目标的收集器.
**流程:**初始标记->并发标记->重新标记->并发清除
CMS是基于"标记-清除"算法,初始标记很快,并发标记就是在用户线程运行中时GC Roots跟踪,比较耗时,重新标记就是修正其他线程在这个过程中产生的对象,清除就是清除,因为是基于"标记-清除"算法,就会产生碎片,注意:初始标记和重新标记仍会产生STW

**优点:**并发收集,低停顿
**缺点:**1.CMS收集器对CPU资源敏感,其实所有的面向并发设计的程序,都对CPU敏感,特别是当CPU核数较少时,还得一直保持一部分资源执行垃圾回收,明显拖慢程序,核数越多,越不明显
2.无法处理"浮动垃圾",因为CMS在清理的时候,用户线程还在执行,有可能出现"Concurrent Model Failure"而导致一次新的Full GC,耗费时间,因此需要预留一些空间在老年代,以防在清理垃圾时又产生大量对象,jdk1.6中,CMS启动阈值设置为92%,如上面介绍,当失败时,可用备用的收集器serial Old

3.7 G1收集器

从实现方式来看,CMS和之前的收集器时传统收集器–称为一代收集器,CMS是一代收集器中的最优实现.
而G1收集器是二代收集器,有着完全不同的实现
优点:
1.并行与并发:在多核情况下,充分利用CPU,缩短STW时间
2.分代收集:保留这一定义,能够用不同方式去处理各种生命周期的对象,以达到更好的效果
3.空间整合:G1从整体来看是基于"标记-整理"算法,从局部来看是基于"复制算法",总之不会产生碎片
4.可预测停顿:G1和CMS收集器都专注于降低STW时间,但G1可以通过参数控制最大停顿时间

**缺点:**吞吐量时候弱项,不过对于用户交互的应用,已经很好了
作为二代收集器,
核心特点是:
G1将内存化整为零,它将整个堆空间划分为若干个大小相等的独立区域(Region),虽然保留了新生代和老年代概念,但他们在物理上没隔离了,G1之所以支持可预测停顿,就是基于这个拆分内存的设计,G1跟踪每个Region里面的垃圾堆积的价值的大小,在后台维护一张优先列表,每次根据允许的时间停顿,来回收对应的Region,这样可以保证G1收集器在有限的时间获得更好的效率.
这个化整为零的思路,看起来容易,但细节实现很难,世界顶级团队sun实验室从发第一篇论文到第一版G1成型,花了近10年时间.
在jdk11中,G1已经是默认垃圾收集器了
在这里插入图片描述

4.基础jvm命令

在使用调优工具之前,先熟悉下jdk本身提供的命令,市场上一些工具也是依赖jdk接口和底层命令.

4.1. jps命令:

JVM Process Status Tool,显示所有的HotSpot虚拟机进程

option:

-l 显示全类名或
-m 输出JVM启动时传递给main()的参数
eg:
[root@localhost ~]# jps -lm 76851 org.apache.rocketmq.namesrv.NamesrvStartup 76898 org.apache.rocketmq.broker.BrokerStartup -c /usr/local/rocketmq/conf/2m-2s-async/broker-a.properties 31256 sun.tools.jps.Jps -lm 89227 org.apache.catalina.startup.Bootstrap start

4.2 jstat命令:

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令的格式如下:

jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]

常用option:

class
compiler
gc

4.2.1 查看class加载统计

复制代码
1
2
3
4
5
[root@localhost ~]# jstat -class 89227 Loaded Bytes Unloaded Bytes Time 3670 7860.6 13 16.6 5.48

说明:

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

4.2.3 查看编译统计

复制代码
1
2
3
4
[root@localhost ~]# jstat -compiler 89227 Compiled Failed Invalid Time FailedType FailedMethod 2496 0 0 9.66 0

说明:

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

4.2.4 垃圾回收统计

在这里插入图片描述

说明:

  • 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:垃圾回收消耗总时间

4.3 jmap的使用以及内存溢出分析

jmap(JVM Memory Map)命令用于生成heap dump文件,然后使用工具对这个文件分析,如果不使用这个命令,还阔以使用-XX:+HeapDumpOnOutOfMemoryError参数来让虚拟机出现OOM的时候·自动生成dump文件。 jmap能生成dump文件,Java堆和永久代的详细信息,如当前使用率、当前使用的是哪种收集器等。

命令格式:

jmap [option] LVMID

option:
dump : 生成堆转储快照
finalizerinfo : 显示在F-Queue队列等待Finalizer线程执行finalizer方法的对象
heap : 显示Java堆详细信息
histo : 显示堆中对象的统计信息
permstat : to print permanent generation statistics
F : 当-dump没有响应时,强制生成dump快照

4.3.1 查看堆信息

jmap -heap 89227

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[root@localhost ~]# jmap -heap 89227 Attaching to process ID 28920, please wait... Debugger attached successfully. Server compiler detected. JVM version is 24.71-b01 using thread-local object allocation. Parallel GC with 4 thread(s)//GC 方式 Heap Configuration: //堆内存初始化配置 MinHeapFreeRatio = 0 //对应jvm启动参数-XX:MinHeapFreeRatio设置JVM堆最小空闲比率(default 40) MaxHeapFreeRatio = 100 //对应jvm启动参数 -XX:MaxHeapFreeRatio设置JVM堆最大空闲比率(default 70) MaxHeapSize = 2082471936 (1986.0MB) //对应jvm启动参数-XX:MaxHeapSize=设置JVM堆的最大大小 NewSize = 1310720 (1.25MB)//对应jvm启动参数-XX:NewSize=设置JVM堆的‘新生代’的默认大小 MaxNewSize = 17592186044415 MB//对应jvm启动参数-XX:MaxNewSize=设置JVM堆的‘新生代’的最大大小 OldSize = 5439488 (5.1875MB)//对应jvm启动参数-XX:OldSize=<value>:设置JVM堆的‘老生代’的大小 NewRatio = 2 //对应jvm启动参数-XX:NewRatio=:‘新生代’和‘老生代’的大小比率 SurvivorRatio = 8 //对应jvm启动参数-XX:SurvivorRatio=设置年轻代中Eden区与Survivor区的大小比值 PermSize = 21757952 (20.75MB) //对应jvm启动参数-XX:PermSize=<value>:设置JVM堆的‘永生代’的初始大小 MaxPermSize = 85983232 (82.0MB)//对应jvm启动参数-XX:MaxPermSize=<value>:设置JVM堆的‘永生代’的最大大小 G1HeapRegionSize = 0 (0.0MB) Heap Usage://堆内存使用情况 PS Young Generation Eden Space://Eden区内存分布 capacity = 33030144 (31.5MB)//Eden区总容量 used = 1524040 (1.4534378051757812MB) //Eden区已使用 free = 31506104 (30.04656219482422MB) //Eden区剩余容量 4.614088270399305% used //Eden区使用比率 From Space: //其中一个Survivor区的内存分布 capacity = 5242880 (5.0MB) used = 0 (0.0MB) free = 5242880 (5.0MB) 0.0% used To Space: //另一个Survivor区的内存分布 capacity = 5242880 (5.0MB) used = 0 (0.0MB) free = 5242880 (5.0MB) 0.0% used PS Old Generation //当前的Old区内存分布 capacity = 86507520 (82.5MB) used = 0 (0.0MB) free = 86507520 (82.5MB) 0.0% used PS Perm Generation//当前的 “永生代” 内存分布 capacity = 22020096 (21.0MB) used = 2496528 (2.3808746337890625MB) free = 19523568 (18.619125366210938MB) 11.337498256138392% used 670 interned Strings occupying 43720 bytes.

4.3.2 查看活跃对象

jmap -histo:live 89227 | more

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@localhost ~]# jmap -histo:live 76851 | more num #instances #bytes class name ---------------------------------------------- 1: 3412 1964056 [Ljava.lang.Object; 2: 13770 1097136 [C 3: 3114 633880 [B 4: 858 562848 io.netty.util.internal.shaded.org.jctools.queues.MpscArrayQueue 5: 3265 374208 java.lang.Class 6: 13654 327696 java.lang.String 7: 2121 124920 [I 8: 2553 81696 java.util.HashMap$Node 9: 2383 76256 java.util.concurrent.ConcurrentHashMap$Node 10: 2 65568 [Lcom.alibaba.fastjson.util.IdentityHashMap$Entry; 11: 71 52720 [Lio.netty.util.Recycler$DefaultHandle; 12: 1300 52000 java.util.LinkedHashMap$Entry 13: 296 49248 [Ljava.util.HashMap$Node; 14: 1143 36576 java.util.Hashtable$Entry 15: 403 33072 [Ljava.lang.String; 16: 10 32960 [Ljava.nio.channels.SelectionKey; 17: 767 30680 java.math.BigInteger 18: 904 28928 sun.security.util.DerInputBuffer 19: 904 28928 sun.security.util.DerValue 20: 1791 28656 java.lang.Object 21: 46 25424 [Ljava.util.concurrent.ConcurrentHashMap$Node; 22: 792 25344 io.netty.buffer.PoolThreadCache$SubPageMemoryRegionCache 23: 629 25160 java.lang.ref.SoftReference 24: 547 21880 java.util.TreeMap$Entry 25: 904 21696 sun.security.util.DerInputStream 26: 891 21384 [Lsun.security.x509.AVA; 27: 891 21384 sun.security.x509.AVA 28: 891 21384 sun.security.x509.RDN

4.3.3 生成dump文件

有些时候我们需要将jvm当前内存中的情况dump到文件中,然后对它进行分析,jmap支持dump到文件。

#用法:
jmap -dump:format=b,file=dumpFileName

复制代码
1
2
3
4
5
[root@localhost bin]# jmap -dump:live,format=b,file=tomcatTest1.hprof 103345 Dumping heap to /var/local/demo1/apache-tomcat-8.5.37/bin/tomcatTest1.hprof ... Heap dump file created

位置如果,当然不应该存这里,仅仅是demo示例
在这里插入图片描述

4.3.4 使用jhat对dump文件查看

dump文件本身是二进制文件,借助工具查看,jhat实际上也是用jvm运行,可以配置启动参数,
语法:

复制代码
1
2
jhat file

option:
-port port-number 设置 jhat HTTP server 的端口号. 默认值 7000.>
-J< flag > 因为 jhat 命令实际上会启动一个JVM来执行, 通过 -J 可以在启动JVM时传入一些启动参数. 例如, -J-Xmx512m 则指定运行 jhat 的Java虚拟机使用的最大堆内存为 512 MB. 如果需要使用多个JVM启动参数,则传入多个 -Jxxxxxx.

更多参数可jhat -help 查看

在这里插入图片描述

页面可访问查看:
在这里插入图片描述

后面几篇持续介绍.

参考:

1.深入理解java虚拟机-周志明
2.码出高效-java开发手册

最后

以上就是痴情香烟最近收集整理的关于jvm 优化(一):内存结构前言目录正文1.jdk7和jdk8内存结构区别2.各垃圾回收算法对比3.各垃圾收集器对比4.基础jvm命令参考:的全部内容,更多相关jvm内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(76)

评论列表共有 0 条评论

立即
投稿
返回
顶部