2019独角兽企业重金招聘Python工程师标准>>>
有时候碰到性能问题,比如一个java application出现out of memory,出现内存泄漏的情况,再去修改bug可能会变得异常复杂,利用工具去分析整个java application 内存占用情况,然后再去走查代码。
首先先看一下,java内存分配的基本模型,由于JVM内存划分比较复杂,这里只是简单的说一下java内存划分
java 堆(heap):
Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的
唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”
所以Java 堆中还可以细分为:新生代和老年代;
新生代:所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
老年代(old Generation):在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象
持久代(Prem Generation):用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。
再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。如果从内存分配
的角度看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local
Allocation Buffer,TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,
存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。
java 栈区:
JVM中运行的每个线程都拥有自己的线程栈,线程栈包含了当前线程执行的方法调用相关信息,我们也把它称作调用栈。随着代码的不断执行,调用栈会不断变化。
一般认为 方法,局部变量,对象的引用 都是存在栈区内的。
java方法区:
方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,它用于存
储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
很多人称之为永生代(Permanent Generation);
出现out of memory 接着会出现 java heap space,这种情况是堆空间满了,
可能出现的情况,比如一个方法 进栈了,里面有一段逻辑,最常见的就是打个比方
public void method(){
List<Model> list=...;
.... 执行代码的逻辑
}
这个list 里面有N多对象,但是执行下面的逻辑时内存溢出,这是因为方法在没有出栈之前,java heap中的对象太多(方法在出栈之后GC 对 没有引用的 对象进行垃圾回收)。
还有 就是有一些全局的变量或者集合 ,或者存在方法区的 内存 没有进行及时清理,导致内存没有释放,这种称之为真泄漏。
2.1 利用jmap 进行内存分析,
jmap -heap pid //打印heap空间的概要,这里可以粗略的检验heap空间的使用情况。
Heap Configuration:指java应用启动时设置的JVM参数。像最大使用内存大小,年老代,年青代,持久代大小等。
Heap Usage:当时的heap实际使用情况。包括新生代、老生代和持久代。
其中新生代包括:Eden区的大小、已使用大小、空闲大小及使用率。Survive区的From和To同样。
有这个可以很简单的查看本进程的内存使用情况。
可以用于分析堆内存分区大小是否合理,新生代和老生代的大小分配是否合适等。
也许进程占用的总内存比较多,但我们在这里可以看到真正用到的并没有多少,很多都是"Free"。内存使用的堆积大多在老年代,内存池露始于此,所以要格外关心“Old Generation”。
jmap -histo PID //这里会生成一个类的统计报表,此表非常简单,如显示什么类有多少个实例,共占了多少字节等。
2.2 利用jmap 生成dump 文件,然后再利用mat工具进行分析。
jmap -dump:live,format=b,file=‘文件名 ’2657 //2657 是进程的PID 号
生成转储文件之后,file -open(该转储文件)
上面的饼状图显示了内存分配的比例,通过dominator_tree可以看到详细信息。