history 端口映射 java开发环境变量 MongoDB mysql安装 npm安装 Flutter css swing dictionary ansible compilation Semantic UI angularjs教程 less使用 java商城源码 jquery遍历对象 jq延时 jquery事件绑定 jquery获取最后一个子元素 python中str函数 python入门指南 random函数用法 python获取字典的值 python自定义异常 python打开文件夹 java基础语言 java查看版本 java删除数组中的元素 java中的接口 java怎么配置 linux安装 一键刷入recovery customerrors 球中的小鬼 microkms 忧思华光玉 微信python退出程序 c语言表白代码 mac画图软件
当前位置: 首页 > 学习教程  > 编程语言

JVM相关以及调优分析的常用命令和工具

2020/8/31 12:40:13 文章标签:

JVM的结构包含类加载器,运行时数据区,执行引擎和本地方法接口。

1.类加载器

负责加载class文件,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由ExecutionEngine决定。
JVM自带的加载器有:
	 启动类加载器(Bootstrap classLoader)由C++编写,加载jre/lib;
	 扩展类加载器(Extension classLoader)由Java编写,加载jre/lib/ext;
	 应用程序类加载器(System classLoader)由Java编写,加载classpath;
加载器的双亲委派机制:
	  一个类加载器收到类加载请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功
	  返回; 只有父类加载器无法完成此加载任务时,才自己去加载。 好处:确保一个类只被加载一次

2.Native Interface本地方法接口

Java语言本身不能对操作系统底层进行访问和操作,但是可以通过Native Interface接口调用
其他语言来实现对底层的访问,这也是JAVA跨平台的来源

3.执行引擎

字节码解释器,将字节码编译成机器语言后执行

4.运行时数据区

包含:方法区栈/堆/本地方法栈/程序计数器
  a.方法区: 
    方法区是线程共享的,通常用来保存装载的类的元结构信息。比如:运行时常量池+静态变量+常量+字段+方法字节码+在类/实例/
    接口初始化用到的特殊方法等。
  b.栈: 
    栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束,栈内存也就释放,
    对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。用于存放局部变量、实例
    方法、引用类型变量、方法出口等信息。
  c.堆: 
    Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。这个区域是用来存放对象实例的,几乎所有对象实例都会在
    这里分配内存。
   d.程序计数器:
    线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行
    引 擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记
  e.本地方法栈:
    保存被native修饰的方法,即非java方法。

堆的生命周期
新生代

新生代是对象的诞生、成长、消亡的区域,一个对象在这里产生,应用,最后被垃圾回收器收集,销毁。新生代又分为两部分: 
伊甸区(Eden space)和幸存者区(Survivor pace) ,所有的对象都是在伊甸区被new出来的。
幸存区有两个: 0区(Survivor 0 space)和1区(Survivor 1 space)。当伊甸园的空间用完时,程序又需要创建对象,JVM
的垃圾回收器将对伊甸园区进行垃圾回收(Minor GC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。然后将伊甸园中的剩
余对象移动到幸存0区.若幸存0区也满了,再对该区进行垃圾回收,然后移动到1区。那如果1区也满了呢?再移动到老年代。

老年代

若老年代也满了,那么这个时候将产生MajorGC(FullGC),进行老年代的内存清理。若老年代执行了Full GC之后发现依然无法
进行对象的保存,就会产生OOM异常“OutOfMemoryError”。
如果出现java.lang.OutOfMemoryError: Java heap space异常,说明Java虚拟机的堆内存不够。原因有二:
(1)Java虚拟机的堆内存设置不够,可以通过参数-Xms、-Xmx来调整。
(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)。

元空间(1.8之前叫永久代,之后移除了永久代)

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于: 元空间并不在虚拟机中,而
是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
 -XX:MetaspaceSize:初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空
 间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
 -XX:MaxMetaspaceSize:最大空间,默认是没有限制的。

GC垃圾回收

根据发生的区域来分类:
  Minor GC:从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。
  Major GC:是清理老年代。
  Full GC(Minor GC+Major GC) :是清理整个堆空间—包括年轻代和老年代。
回收算法:

 标记-清除算法:
    先标记可回收的内存,后清除,缺点是:效率比较低;会出现大量不连续的内存碎片。
    
 复制算法:
    将可用的内存分成两份,每次使用其中一块,当这块回收之后把未回收的复制到另一块内存中,然后把使用的清除。
    
 标记整理算法:
     是在标记-清除算法基础上,不直接清理,而是使存活对象往一端游走,然后清除一端边界以外的内存,这样既可以避免不连续
     空间出现,还可以避免对象存活率较高时的持续复制。
      
 分代收集算法:
     分代收集算法就是目前虚拟机使用的回收算法,它解决了标记整理不适用于老年代的问题,将内存分为各个年代,在不同年代使
     用不同的算法,从而使用最合适的算法,新生代存活率低,可以使用复制算法。而老年代对象存活率高,没有额外空间对它进行
     分配担保,所以只能使用标记清除或者标记整理算法。

收集器(根据类型可分为串行/并行/并发收集器)

  Serial收集器:新生代串行收集器,采用“复制”算法,适合单核处理器,在垃圾回收时,必须暂停其他所有线程。
  Serial Old收集器:老年代串行收集器,采用“标记-整理”算法,可与Serial收集器协同工作
  ParNew收集器:Serial收集器的多线程版本,并行收集器,除了使用多条线程进行垃圾回收之外,其他与Serial一样,
  即也需要停止所有用户线程,采用复制算法等。
  Parallel Scavenge:新生代垃圾并行收集器,并行的复制算法收集器,特点:可控制吞吐量,运行时暂停所有线程
  Parallel Old:Parallel Scavenge的老年代版本并行收集器,只能和Parallel Scavenge配合使用,使用“标记-整理”算法。
  Concurrent Mark Sweep(CMS):并发垃圾收集器,且采用标记-清除算法。
  Garbage-First收集器(G1):G1既可以作用于新生代又可以作用于老年代。
垃圾收集器默认情况:
  jdk1.7 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
  jdk1.8 默认垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)
  jdk1.9 默认垃圾收集器G1

#################################JVM优化方向#################################

GC 优化的两个目标:
     将进入老年代的对象数量降到最低(减少Full GC的频率);
     
     对象在 Eden 区被创建,随后被转移到 Survivor 区,在此之后剩余的对象会被转入老年代。也有一些对象由于占用内存过
     大,在 Eden 区被创建后会直接被传入老年代。老年代 GC 相对来说会比新生代 GC 更耗时,因此,减少进入老年代的对象数
     量可以显著降低 Full GC 的频率;
     
     减少 Full GC 的执行时间Full GC 的执行时间比 Minor GC 要长很多,因此,如果在 Full GC 上花费过多的时间
    (超过 1s),将可能出现超时错误。可以通过减小老年代内存大小使Full GC的时间降低,但是减小老年代的内存大小又会增加
    Full GC的频率,两者需要摸索出合适的平衡值。
  优化方向建议:
    JVM调优没有固定的参数参考,需要根据不同的服务器和系统需求来调整,GC优化是到最后不得已才采用的手段。
    一般来说堆越大越好,能够降低GC的频率,但增加堆内存,会造成单次GC需要遍历处理的对象更多,耗时增加;也会受服务器硬
    件的限制无法无限大,所以需要根据实际找到平衡值。
   通常堆参数-Xms和-Xmx可以设置相等,防止垃圾收集器在最小、最大之间收缩堆而产生额外的消耗,耗费性能。
   新生代/老年代大小比例设置合适:新生代过小,发生Minor GC频繁,且大对象容易直接进入老年代;新生代过大,老年代变小,
   容易Full GC频繁,Minor GC耗时大幅度增加。具体设置多大合适没有值,需要根据实际优化,SUN官方给出的建议是新生代占
   堆的3/8比较合适。

JVM优化参数

堆配置:
-Xms:初始堆大小
-Xms:最大堆大小
-Xss: 每个线程的堆栈大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。
-XX:MaxMetaSpaceSize=n:最大元空间大小
-XX:+CollectGen0First : FullGC时是否先YGC
收集器设置:
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
-XX:ParallelGCThreads 并行收集器的线程数
打印GC:
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename

典型设置: 
机器配置:128G内存   32核CPU
java -Xmx72g -Xms72 -Xmn4g -Xss256m
   -Xmx72g:设置JVM最大可用内存为72g.
   -Xms72g:设置JVM促使内存为72g.此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存.
   -Xmn4g:设置年轻代大小为rG.

整个堆大小=年轻代大小 + 年老代大小 + 持久代大小.持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小.此值对系
统性能影响较大,Sun官方推荐配置为整个堆的3/8.(注:这里默认的使用的JVM是Sun的Hotspot,而其中使用GC算法就是分代算法)

常见的JVM异常

A. OutOfMemory(OOM)

OutOfMemory ,即内存溢出,是一个常见的 JVM 问题。什么情况下会抛出OOM呢?满足以下2个条件:
1、JVM98%的时间都花费在内存回收
2、每次回收的内存小于2%

那么分析 OOM 的思路是什么呢?首先,要知道有三种 OutOfMemoryError:

OutOfMemoryError:Java heap space - 堆空间溢出
OutOfMemoryError:PermGen space - 方法区和运行时常量池溢出
OutOfMemoryError:unable to create new native thread - 线程无法创建
B. OutOfMemoryError:PermGen space 表示方法区和运行时常量池溢出。

原因:
Perm 区主要用于存放 Class 和 Meta 信息的,Class 在被 Loader 时就会被放到 PermGen space,这个区域称为年老代。GC 在主程序运行期间不会对年老区进行清理,默认是 64M 。
当程序程序中使用了大量的 jar 或 class,使 java 虚拟机装载类的空间不够,超过 64M 就会报这部分内存溢出了,需要加大内存分配,一般 128m 足够。

解决方案:
(1)扩大永久代空间
       JDK7 以前使用 -XX:PermSize 和 -XX:MaxPermSize 来控制永久代大小。
       JDK8 以后把原本放在永久代的字符串常量池移出, 放在 Java 堆中(元空间 Metaspace)中,元数据并不在虚拟机中,使
       用的是本地的内存。使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 控制元空间大小。
(2)清理应用程序中 WEB-INF/lib 下的 jar,用不上的 jar 删除掉,多个应用公共的 jar 移动到 Tomcat 的 lib 目录,
     减少重复加载

C. OutOfMemoryError:Java heap space 表示堆空间溢出。
原因:JVM 分配给堆内存的空间已经用满了。
问题定位
  使用 jmap 或 -XX:+HeapDumpOnOutOfMemoryError 获取堆快照。 
  使用内存分析工具(visualvm、jhat、jProfile 等)对堆快照文件进行分析。 
  分析图,重点是确认内存中的对象是否是必要的,分清究竟是是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow).

内存泄漏:没用了的内存没用及时释放导致最后占满内存。
内存泄漏常见几个情况:
1)静态集合类:声明为静态(static)的 HashMap、Vector 等集合,通俗来讲 A 中有 B,当前只把 B 设置为空,A 没有设置
为空,回收时 B 无法回收-因被 A 引用。
2)监听器:监听器被注册后释放对象时没有删除监听器
3)物理连接:DataSource.getConnection()建立链接,必须通过 close()关闭链接。
4)有死循环或不必要地重复创建大量对象
查找:FGC 次数越多,FGCT 所需时间越多-可非常有可能存在内存泄漏。

jvm分析

1.jstat
   命令 jstat -gcutil pid 5s
  
回显字段说明
S0C:第一个幸存区的大小
S1C:第二个幸存区的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园区的大小
EU:伊甸园区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间 

#进程运行时间
ps -eo pid,tty,user,comm,lstart,etime | grep pid

gullgc的频率=时间/FGC

2.jstack
  top -p pid  -H  获取cpu最高的进程

  jstack pid|grep -A 10 0x31d8 --转换进制
   查看栈的相关信息
  
3.jmap(Memory Map)和jhat(Java Heap Analysis Tool)
  
  jmap -heap pid查看进程堆内存使用情况
  jmap -histo[:live] pid查看堆内存中的对象数目、大小统计直方图,如果带上live则只统计活对象
  jmap -dump:format=b,file=dumpFileName 用jmap把进程内存使用情况dump到文件中,再用jhat分析查看。
  
  jhat -port 8888 /tmp/dump.dat  分析进程内存使用 
  浏览器中输入主机地址:port查看
  
JDK自带的分析工具bin目录下  jvisualvm、jconsole

本文链接: http://www.dtmao.cc/news_show_150026.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?