JVM理解及zabbix监控tomcat

背景

由于我们的tomcat出现过内存溢出的情况,由此分析一下内存溢出的原因 tomcat内存溢出有三种情况: 1.堆内存(Heap)溢出 2.持久代空间(Perm Gen)溢出 3.无法创建新线程 由此又引出Heap和Perm Gen是什么的问题 所以深入的学习理解一下JVM的知识,不过JVM需要学习的东西太多了,我只是学习了皮毛,在此记录一下,以后还要继续学习

JVM

JVM是Java Virtual Machine(Java虚拟机)的缩写,Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。 JVM体系分为三部分:类装载器子系统、运行时数据区、执行引擎

类装载器

每一个Java虚拟机都由一个类加载器子系统(class loader subsystem),负责加载程序中的类型(类和接口),并赋予唯一的名字。每一个Java虚拟机都有一个执行引擎(execution engine)负责执行被加载类中包含的指令。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。

执行引擎

主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 。

运行时数据区

我们重点学习的是运行时数据区的内容 运行时数据区又包括:方法区、堆、Java虚拟机栈、程序计数寄存器和本地方法栈 方法区和堆由所有线程共享,也就是每一个进程会有独立的内存空间进行活动,之后的每个线程都共享这一块内存。 Java虚拟机栈、程序计数寄存器是由线程独享的,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。每条线程包含一个栈区,只保存基础数据类型的对象和自定义对象的引用,每个栈区相互独立,互不干扰。

堆内存(Heap)

这就是我们需要关注的地方了,tomcat内存溢出的原因之一 堆内存分为三部分:Eden Space、Survivor Space、Tenured Gen Eden Space和Survivor Space统一称作年轻代(young Generation) Tenured Gen被称为老年代 Eden Space:对象被创建的时候首先放到这个区域,内存大小相对较小,GC频繁,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域。 Survivor Space:用于保存在eden space内存区域中经过垃圾回收后没有被回收的对象。Survivor有两个,分别为To Survivor、 From Survivor,这个两个区域的空间大小是一样的。执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全部释放),另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),然后To Survivor 和 From Survivor的标记会互换,始终保证一个survivor是空的。 年轻代中执行的垃圾回收被称之为Minor GC(因为是对年轻代进行垃圾回收,所以又被称为Young GC),每一次Young GC后留下来的对象age加1。年轻代的空间越小,GC的频率就越高。 Tenured Gen:用于存放年轻代中经过多次垃圾回收仍然存活的对象,也有可能是年轻代分配不了内存的大对象会直接进入老年代。经过多次垃圾回收都没有被回收的对象,这些对象的年代已经足够old了,就会放入到老年代。当老年代被放满的之后,虚拟机会进行垃圾回收,称之为Major GC。由于Major GC除并发GC外均需对整个堆进行扫描和回收,因此又称为Full GC。GC相对不频繁。 所以Heap区也就是堆内存的大小=年轻代+老年代=Eden Space+Survivor Space+Tenured Gen 这三部分的大小 然后我们来说一说堆内存溢出了怎么办 JVM初始分配的堆内存由-Xms指定,这个参数在tomcat下的bin/catalina.sh文件中定义,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。Heap Size最大不要超过物理内存的80% 例如:JAVA_OPTS='$JAVA_OPTS -server -Xms128m -Xmx128m',一般为2的n次幂($JAVA_OPTS是保留原先设置) 还可以指定Young Generation的大小-Xmn,也就是Eden Space+Survivor Space的大小,一般为-Xmx的3、4分之一 如果堆内存空间还是不够的话,就只能加物理内存了 ### 非堆内存 Java 虚拟机管理堆之外的内存都属于非堆内存,主要分析了Code Cache和Perm Gen两部分 ** Code Cache**:代码缓存区,它主要用于存放JIT所编译的代码。CodeCache代码缓冲区的大小在client模式下默认最大是32m,在server模式下默认是48m,这个值也是可以设置的,它所对应的JVM参数为ReservedCodeCacheSize 和 InitialCodeCacheSize,可以通过如下的方式来为Java程序设置 -XX:ReservedCodeCacheSize=128m CodeCache缓存区是可能被充满的,当CodeCache满时,后台会收到CodeCache is full的警告信息 Perm Gen:Permanent Generation space,是指内存的永久保存区域,因而称之为永久代。这个内存区域用于存放类定义、字节码、常量等很少变更的信息,Class在被加载的时候被放入这个区域。默认大小为物理内存的1/64。这个区域一般被认为是运行时数据区中的方法区 Perm Gen这个空间不足就是造成我们tomcat内存溢出的第二个原因了 GC不会在主程序运行期间对Perm Gen进行清理,但如果程序加载的类太多了,或者使用了大量的第三方jar包,其大小超过了jvm默认的大小,就有可能会造成内存溢出。 JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由-XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。 造成tomcat内存溢出的第三个原因:无法创建新的线程 (这种现象比较少见,也比较奇怪,主要是和jvm与系统内存的比例有关。   这种怪事是因为JVM已经被系统分配了大量的内存(比如1.5G),并且它至少要占用可用内存的一半。有人发现,在线程个数很多的情况下,你分配给JVM的内存越多,那么,上述错误发生的可能性就越大。   每一个32位的进程最多可以使用2G的可用内存,因为另外2G被操作系统保留。这里假设使用1.5G给JVM,那么还余下500M可用内存。这500M内存中的一部分必须用于系统dll的加载,那么真正剩下的也许只有400M,现在关键的地方出现了:当你使用Java创建一个线程,在JVM的内存里也会创建一个Thread对象,但是同时也会在操作系统里创建一个真正的物理线程(参考JVM规范),操作系统会在余下的400兆内存里创建这个物理线程,而不是在JVM的1500M的内存堆里创建。在jdk1.4里头,默认的栈大小是256KB,但是在jdk1.5里头,默认的栈大小为1M每线程,因此,在余下400M的可用内存里边我们最多也只能创建400个可用线程。)这是我从网上看到的,我的理解就是创建线程的时候不光在JVM里创建,还要在操作系统里创建一个真正的线程,然后这个真正的线程是占用物理内存的,线程很多的情况下,物理内存就不够用了。

类加载

我们在用zabbix监控tomcat的时候不光要监控jvm内存的使用情况,刚才说到加载的类太多会造成Perm Gen这个空间不足,所以还要监控java类加载的情况;tomcat线程数太多也会造成问题,还要监控tomcat线程情况 java文件在代码编译后,就会生成JVM(Java虚拟机)能够识别的字节码文件(.class)。而JVM把Class文件中的类描述数据从文件加载到内存,也就是方法区,并对数据进行校验、转换解析、初始化,使这些数据最终成为可以被JVM直接使用的Java类型,这个说来简单但实际复杂的过程叫做JVM的类加载机制。类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称链接。类用到才被加载。

类卸载

类的生命周期

当Sample类被加载、连接和初始化后,它的生命周期就开始了。当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,Sample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。由此可见,一个类何时结束生命周期,取决于代表它的Class对象何时结束生命周期 Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。Java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。由用户自定义的类加载器加载的类是可以被卸载的。

tomcat线程

tomcat每一个进来的请求都需要一个线程,直到该请求结束。tomcat服务器每个实例就是一个进程,默认一个大线程池用于运行所有的webapp。在tomcat的conf/server.xml文件中可以定义maxThreads,就是实际可同时处理的请求数,默认为200;还有acceptCount,当同时连接的人数达到maxThreads时,还可以接收排队的连接,超过这个连接的则直接返回拒绝连接。

zabbix监控tomcat

服务端配置

Zabbix2.0起添加了支持用于监控JMX应用程序的服务进程,称为“Zabbix-Java-gateway”,它是用java写的一个程序。 1.安装Zabbix-Java-gateway,把他安装到/etc/zabbix目录下 2.修改Java-gateway的配置文件并启动它(zabbix_java_gateway.conf)

LISTEN_IP="0.0.0.0"       #监听地址
LISTEN_PORT=10052      #监听端口
START_POLLERS=5        # 开启的工作线程数(必须大于等于后面zabbix_server.conf文件的StartJavaPollers参数)

启动

service zabbix-java-gateway start

3.修改zabbix_server的配置文件并重启(zabbix_server.conf)

JavaGateway=127.0.0.1                     # JavaGateway 服务器地址,zabbix_server与zabbix_java_gateway在同一台主机
JavaGatewayPort=10052                    #端口
StartJavaPollers=5
#重启zabbix_server
/etc/init.d/zabbix_server restart

客户端配置

然后配置被监控端,开启JMX 1.下载catalina-jmx-remote.jar wget http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.56/bin/extras/catalina-jmx-remote.jar #我的tomcat版本是7.0.56 将下载后后的jar包放到被监控的tomcat实例的lib目录下。 2.修改tomcat的server.xml文件,添加下面这句,定义JMX端口 <Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener" rmiRegistryPortPlatform="13373" rmiServerPortPlatform="13374" /> 我一开始从网上查到的端口信息要加到下面的catalina.sh文件那句话里,但是后来测试的时候 jmx始终报红,可是防火墙也已经开放端口了,后来我又查到,除了定义好的端口之外,JMX还会随机开放一个端口用来获取数据,所以两个端口就定义到server.xml文件中,在防火墙开放这两个端口就可以了 3.修改tomcat/bin/下的catalina.sh,添加如下内容: CATALINA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=ip地址" hostname为要获取数据的被监控端ip 4.重启tomcat 5.注意添加防火墙规则,添加server.xml中定义的端口 6.测试是否可以获取数据 命令行下测试需要cmdline-jmxclient-0.10.3.jar这个包,在服务端测试是否可以拿到数据 java -jar cmdline-jmxclient-0.10.3.jar controlRole:tomcat ip:端口 java.lang:type=Memory NonHeapMemoryUsage 注意cmdline-jmxclient-0.10.3.jar所在的路径,我是在root目录下测试,jar包也在root目录下,如果能拿到数据,那么配置就没问题了

zabbix管理页面

浏览器连接到zabbix服务器页面 1.新建模板 zabbix自带有监控JMX模板,但是很多都没有用,然后就自己新建了一个模板,监控堆内存,非堆内存,tomcat线程等等 配置tomcat线程的键值需要访问tomcat的端口号,于是这个不是加载模板里的,是一个一个复制的 2.创建主机 由于键值不能重复,所以每一个tomcat都作为一个主机,加到对应的机器组里 3.连接模板 新建主机的时候就连接模板 4.新建触发器 设置了内存超过85%就报警 5.新建图形 新建图形,添加监控项 6.测试 如果监控项显示不支持的话,看一下键值是否正确,到服务器上测试是否能获取到数据,也有可能是加载时间较慢,耐心等待一下就有了,然后出来图形就可以监控了

本站公众号
   欢迎关注本站公众号,获取更多程序园信息
开发小院