加入收藏 | 设为首页 | 会员中心 | 我要投稿 云计算网_泰州站长网 (http://www.0523zz.com/)- 视觉智能、AI应用、CDN、行业物联网、智能数字人!
当前位置: 首页 > 站长资讯 > 外闻 > 正文

值得阅读的内存泄露分析总结和Tomcat调优

发布时间:2019-03-02 14:31:08 所属栏目:外闻 来源:今日头条
导读:副标题#e# 写在最前面,运行环境:tomcat8,jdk1.8,windows server 2008内存16G,软件LoadRunner11,MAT和JProfile9.1。 问题描述:前段时间遇到一个很奇怪的问题,开发的WEB应用,经常会毫无症状的宕掉,然后抓了线程栈看下,发现之前写的数据库链接池出
副标题[/!--empirenews.page--]

写在最前面,运行环境:tomcat8,jdk1.8,windows server 2008内存16G,软件LoadRunner11,MAT和JProfile9.1。

问题描述:前段时间遇到一个很奇怪的问题,开发的WEB应用,经常会毫无症状的宕掉,然后抓了线程栈看下,发现之前写的数据库链接池出现了阻塞的问题,后面分析代码发现同步锁那个地方有一些问题,出现异常可能导致锁不释放,造成堵塞,然后其他线程全block住了,然后应用卡住了,最后就挂了。后面换成了开源的DBPC连接池,获取数据库链接卡住的问题就解决了。但是又发现了一个新问题,用LoadRunner做压力测试时发现tomcat占用的内存持续上升,压了一段时间停了再继续压,tomcat占用内存不会释放,继续往上涨,第一反应就是怀疑存在内存泄露,于是继续往下研究。

值得阅读的内存泄露分析总结和Tomcat调优

由于之前毫无分析内存泄露的经验,对JVM的内存分配和回收机制也不算了解,纯小白一个,所以只能看《深入理解Java虚拟机》和从网上查各种资料。

Part1.JVM内存组成介绍

这里先介绍一下JVM的内存组成,如下图所示:

JVM 将内存区域划分为 MethodArea(Non-Heap)(方法区),Heap(堆),Program Counter Register(程序计数器), VM

Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack (本地方法栈),其中Method Area和Heap是线程共享的,VMStack,Native

Method Stack 和Program Counter Register是非线程共享的。

那我们的程序是怎么在这些内存上运行的呢,概括地说来,JVM初始运行的时候都会分配好Method Area(方法区)和Heap(堆),而JVM 每遇到一个线程(当前情景下WEB应用前台的一个数据请求发送到后台对应就是启动了一个线程),就为其分配一个Program Counter Register(程序计数器),VM

Stack(虚拟机栈)和Native Method Stack (本地方法栈),当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

1.程序计数器

2.VM Strack

先来了解下JAVA指令的构成:

JAVA指令由 操作码 (方法本身)和 操作数 (方法内部变量) 组成,其实底层都是体系结构和组成原理里面学的东西。

1)方法本身是指令的操作码部分,保存在Stack中;

2)方法内部变量(局部变量)作为指令的操作数部分,跟在指令的操作码之后,保存在Stack中(实际上是简单类型(int,byte,short 等)保存在Stack中,对象类型在Stack中保存地址(相当于指针里面的地址),在Heap 中保存值);

虚拟机栈也叫栈内存,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束,该栈就 Over,所以不存在垃圾回收。也有一些资料翻译成JAVA方法栈,大概是因为它所描述的是java方法执行的内存模型,每个方法执行的同时创建帧栈(Strack Frame)用于存储局部变量表(包含了对应的方法参数和局部变量),操作栈(Operand Stack,记录出栈、入栈的操作),动态链接、方法出口等信息,每个方法被调用直到执行完毕的过程,对应这帧栈在虚拟机栈的入栈和出栈的过程。

局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象的引用(reference类型,不等同于对象本身,根据不同的虚拟机实现,可能是一个指向对象起始地址的引用指针,也可能是一个代表对象的句柄或者其他与对象相关的位置)和 returnAdress类型(指向下一条字节码指令的地址)。局部变量表所需的内存空间在编译期间完成分配,在方法在运行之前,该局部变量表所需要的内存空间是固定的,运行期间也不会改变。

栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法 A 被调用时就产生了一个栈帧 F1,并被压入到栈中,A 方法又调用了 B 方法,于是产生栈帧 F2 也被压入栈,执行完毕后,先弹出 F2栈帧,再弹出 F1 栈帧,遵循“先进后出”原则。如下图所示:

3.Heap

Heap(堆)是JVM的内存数据区。Heap 的管理很复杂,是被所有线程共享的内存区域,在JVM启动时候创建,专门用来保存对象的实例。在Heap 中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(以帧栈的形式保存在Stack中),在Heap 中分配一定的内存保存对象实例。而对象实例在Heap 中分配好以后,需要在Stack中保存一个4字节的Heap 内存地址,用来定位该对象实例在Heap 中的位置,便于找到该对象实例,是垃圾回收的主要场所。java堆处于物理不连续的内存空间中,只要逻辑上连续即可。下面我们还会着重介绍一下这块区域。

4.Method Area

Object Class Data(加载类的类定义数据) 是存储在方法区的。除此之外,常量、静态变量、JIT(即时编译器)编译后的代码也都在方法区。正因为方法区所存储的数据与堆有一种类比关系,所以它还被称为 Non-Heap。方法区也可以是内存不连续的区域组成的,并且可设置为固定大小,也可以设置为可扩展的,这点与堆一样。

垃圾回收在这个区域会比较少出现,这个区域内存回收的目的主要针对常量池的回收和类的卸载。

5.运行时常量池(Runtime Constant Pool)

方法区内部有一个非常重要的区域,叫做运行时常量池(Runtime Constant Pool,简称 RCP)。在字节码文件(Class文件)中,除了有类的版本、字段、方法、接口等先关信息描述外,还有常量池(Constant Pool Table)信息,用于存储编译器产生的字面量和符号引用。这部分内容在类被加载后,都会存储到方法区中的RCP。值得注意的是,运行时产生的新常量也可以被放入常量池中,比如 String 类中的 intern() 方法产生的常量。

常量池就是这个类型用到的常量的一个有序集合。包括直接常量(基本类型,String)和对其他类型、方法、字段的符号引用.例如:

类和接口的全限定名;

字段的名称和描述符;

方法和名称和描述符。

池中的数据和数组一样通过索引访问。由于常量池包含了一个类型所有的对其他类型、方法、字段的符号引用,所以常量池在Java的动态链接中起了核心作用。

6.NativeMethod Stack

与VM Strack相似,VM Strack为JVM提供执行JAVA方法的服务,Native

Method Stack则为JVM提供使用native 方法的服务。

7.直接内存区

(编辑:云计算网_泰州站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读