程序监控与调优---------Java篇

序言

        梳理下,针对java工程的监控与对应的调优. 主要分为两部分,第一部分是基础知识预备(这样才有思路去调优啊~),第二步是是监控工具的使用(针对不同的监控对象,会选择一个监控工具). 另外欢迎骚扰:[email protected]

预备知识

Java内存模型

先看下java内存模型,后面可以针对性的看到每个东西在该模型中的位置

 

栈和堆

       Java把内存分成两种,一种叫做栈内存,一种叫做堆内存. 堆中存的是对象。栈中存的是基本数据类型中对象的引用(另外栈中保存了对象的应用地址,可以说应用或者叫指针是放在栈里的.)

       在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

       堆内存用于存放由new创建的对象和数组在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

       引用变量是普通变量,定义时在栈中分配内存(即指针是在栈中),引用变量在程序运行到作用域外释放。而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针! 

另:

       在栈中,会为每一个线程创建一个栈。线程越多,栈的内存使用越大。对于每一个线程栈。当一个方法在线程中执行的时候,会在线程栈中创建一个栈帧(stack frame),用于存放该方法的上下文(局部变量表、操作数栈、方法返回地址等等)。每一个方法从调用到执行完毕的过程,就是对应着一个栈帧入栈出栈的过程。

程序计数器

如同其名称一样。程序计数器用于记录某个线程下次执行指令位置。程序计数器也是线程私有的。

JVM如何判断是否为垃圾空间

       JVM如何判断一个对堆空间是否为垃圾空间的方法如下,

引用计数

       在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行。那么很显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。不失一般性,如果一个对象没有任何引用与之关联,则说明该对象基本不太可能在其他地方被使用到,那么这个对象就成为可被回收的对象了。这种方式成为引用计数法。

       这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方式(Python采用的是引用计数法)。

可达分析(Java采用的是类似于树形结构的可达性分析法来判断对象是否还存在引用)

        为了解决这个问题,在Java中采取了 可达性分析法。该方法的基本思想是通过一系列的“gc Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过要注意的是被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。

常用的垃圾回收算法

        如同java判断是否为垃圾的方法有多重,关于垃圾回收的算法也有多重.并且他们可能在同一个厂家的jvm中的不同部分同时在使用

标记-清除(Mark-Sweep)

这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的.顾名思义分为两个阶段,

  1. 第一阶段标记,标记阶段的任务是标记出所有需要被回收的对象.
  2. 第二阶段清除,清除阶段就是回收被标记的对象所占用的空间(太简单了有没有~~~)

如下图,灰色的就是被标记的,然后被清空了.但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作

复制(Copying)

        为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来. 此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。

标记-整理(Mark-Compact)

为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:

分代收集(Generational Collecting)

       分代收集算法是目前大部分JVM的垃圾收集器采用的算法()。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为如下的3个划分.

  1. 老年代(Tenured Generation): 老年代的特点是每次垃圾收集时只有少量对象需要被回收
  2. 新生代(Young Generation):   新生代的特点是每次垃圾回收时都有大量的对象需要被回收
  3. 持久代(Permanent Generation): 其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。

新生代,老年代,持久带详解

先来个整体框架图便于理解,然后对照入座理解就行了

 

新生代

       所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。新生代又分为如下2类区域:

  1. Eden区:大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(一般是两个中的一个,同时Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
  2. Survivor区:当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“老老区(Tenured)(这里就用到了复制(Copying)算法)

老年代

   当新生代中的对象经过若干轮gc后还存活/或survisor在gc内存不够的时候(这就是个调优的方向哈,比如增大survivor的内存空间)。会把当前对象移动到老年代(这里其实还有个阈值默认的是15次,如果对象15次内还在survivior就会放到来年代.)。老年代一般gc策略为标记-整理(mark-compact)算法。

持久代

持久代一般可以不参与gc。应为其中保存的是一些代码/常量数据/类信息。JDK 1.8 中已经不存在持久带

关于垃圾回收的分类(针对堆内存)

Minor GC

当Eden区被对象填满时,就会执行Minor GC。并把所有存活下来的对象转移到其中一个survivor区。Minor GC同样会检查存活下来的对象,并把它们转移到另一个survivor区。这样在一段时间内,总会有一个空的survivor区。经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的(默认是15)。

 

Major GC

老年代的垃圾收集叫做Major GC,Major GC通常是跟full GC是等价的,收集整个GC堆。

分代GC

分代GC并不收集整个GC堆的模式,而是只专注分代收集

Young GC:只收集年轻代的GC

Old GC:只收集年老代的GC(只有CMS的concurrent collection是这个模式)

Mixed GC:收集整个young gen以及部分old gen的GC(只有G1有这个模式)

 

Full GC

Full GC定义是相对明确的,就是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。full gc 只会在两个情况下发生:1)system.gc被显示调用时,会执行full gc。2)老年代的堆内存满时,会执行full gc。

 

JVM参数调优方案

应该必变Full GC因为这样会很影响效率,还有就是避免更多的对象到老年代(特别注意新生代内不能不够就会放到老年代的问题.).

所以新生代应该尽可能的大,避免进入老年代.同时新生代的survivor不能太小.否则也会进入老年代.同时可以将对象的手机次数增加,以提高进入老年代的门槛.  反正就是个度的问题.网上其实有很多经验分享,但是目前先写这些,另欢迎骚扰[email protected]

堆的初始值与堆的最大可用值相等

初始值越小,垃圾回收的次数就越多

-Xms:堆初始值
-Xmx:堆最大可用值

配置新生代和老年代的调优参数配置

-XX:survivorRatio = 2 Eden是from区(s0)或者to区(s1)的两倍.默认的,Eden : from : to = 8 : 1 : 1
-XX:NewRatio = 2 设置老年区的内存大小为新生代的两倍



 

JConsole是什么

从Java 5开始 引入了 JConsole。JConsole 是一个内置 Java 性能分析器,可以从命令行或在 GUI shell 中运行。您可以轻松地使用 JConsole(或者,它更高端的 “近亲” VisualVM )来监控 Java 应用程序性能和跟踪 Java 中的代码。

启动JConsole

直接双击 Java1.8\jdk1.8.0_91\bin\jconsole.exe 后本地就会启动Jconsole.

图示如下:

JAVA程序的设置让JConsle连接

  1. 本地程序(相对于开启JConsole的计算机),无需设置任何参数就可以被本地开启的JConsole连接(Java SE 6开始无需设置,之前还是需要设置运行时参数 -Dcom.sun.management.jmxremote )
  2. 无认证连接 (下面的设置表示:连接的端口为8999、无需认证就可以被连接)
    -Dcom.sun.management.jmxremote=true          开启JVM远程监控
    
    -Djava.rmi.server.hostname=192.168.91.166    远程进程所在主机的IP。
    
    -Dcom.sun.management.jmxremote.port=8999     这个端口值可以任意设置,但在之后用Jconsole连接这个远程进程的时候,远程进程中的port一定要和此处的设置一致,并且一定不要和远程进程的服务端口区分开。
    
    -Dcom.sun.management.jmxremote.authenticate=false  false为不需要验证,true为需要验证。
    
    -Dcom.sun.management.jmxremote.ssl=false       false为禁用,true为启用。
    

JConsole连接远程机器的JAVA程序(就是将上面的配置文件放置在启动的sh里

  1. 写一个简单的一直运行的JAVA程序,运行在某台机器上如(192.168.0.181),并监听端口8080
  2. 在另外一台机器启动Jconsole(别入:192.168.0.182),然后连接181上的程序 
  3. 可以使用命令直接连接 如: jconsole.exe 192.168.0.181:8080
  4. 也可以在已经打开的JConsole界面操作 连接->新建连接->选择远程进程->输入远程主机IP和端口号->点击“连接”,如图:

概览

在连接指定的Java程序后,选择概览选项卡,会看到如下的图示:(对着图点击右键可以保存数据到CSV文件,以后可以使用其他工具来分析这些数据。)

内存

内存选项卡如下:(内存标签功能“执行GC”的按钮,可以单击执行垃圾收集。)

“详细信息”区域显示了当前内存信息:

  • 已用:目前使用的内存量,包括所有对象,可达和不可达占用的内存。
  • 已提交 :保证由Java虚拟机使用的内存量。 提交的内存量可能会随时间而改变。 Java虚拟机可能会释放系统内存,并已提交的内存量可能会少于最初启动时分配的内存量。 提交的内存量将始终大于或等于使用的内存量。
  • 最大值,可用于内存管理的最大内存量。 它的价值可能会发生变化,或者是不确定的。 如果Java虚拟机试图增加使用的内存要大于提交的内存,内存分配可能失败,即使使用量小于或等于最大值(例如,当系统上的虚拟内存不足)。
  • GC时间 :累计时间花在垃圾收集和调用的总数。 它可能有多个行,其中每一个代表一个垃圾收集器算法在Java虚拟机使用时间。(Ps MarkSweep:老年代GC,使用标记整理算法~  ,PS Scavenge: 是一种stop-the-world, 使用多个GC线程实现复制收集。如同上面复制收集一样,但是它是并行使用多个线程。)

线程

在左下角的“线程”列表列出了所有的活动线程。 如果你输入一个“过滤器”字段中的字符串,线程列表将只显示其名称中包含你输入字符串线程。 点击一个线程在线程列表的名称,显示该线程的信息的权利,包括线程的名称,状态、阻塞和等待的次数、堆栈跟踪。

图表显示活动线程的数量随着时间的推移。 两行显示。

  • 红色 :峰值线程数
  •  :活动线程数。

检测死锁线程

要检查如果您的应用程序已经陷入了僵局运行(例如,您的应用程序似乎是挂了),死锁的线程可以通过点击“检测死锁”按钮检测。 如果检测到任何死锁的线程,这些都显示在一个新的标签,旁边出现的“主题”标签,

类&VM概要

没什么说的

MBean

描述一个可管理的资源。是一个java对象,遵循以下一些规则:1.必须是公用的,非抽象的类 2.必须有至少一个公用的构造器 3.必须实现它自己的相应的MBean接口或者实现javax.management.DynamicMBean接口4.可选的,一个MBean可以实现javax.management.NotificationBroadcaster接口MBean的类型。 managed bean 在jmx里面的一个管理用的bean。(这个欢迎骚扰下[email protected] 否则待续吧)

发布了62 篇原创文章 · 获赞 50 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/cuiyaonan2000/article/details/102837194