JVM内存区域与垃圾回收


一、JAVA内存区域与内存溢出

1.一、概述

Java中JVM提供了内存管理机制,Java虚拟机在执行Java程序的过程当中会把内分分为不一样的数据区,如图:
JVM内存区域.pnghtml

1.二、程序计数器

程序计数器是当前线程所执行的字节码的行号指示器,做用就是根据计数器的值获取下一条要执行的字节码指令。当执行的是java方法,则记录的是正在执行的虚拟机字节码指令的地址,若是是Native方法,则这个计数器的值为空。不存在任务OutOfMemoryError。java

1.三、虚拟机栈

每一个普通Java方法(除去Native方法)在执行的时候都会同时建立栈帧,用于存储局部变量表、操做栈、动态连接、方法出口等信息,每一个方法被调用直到完成的过程对应着栈帧在JVM栈中的入栈与出栈。其中局部变量表所须要的内存空间在编译器间完成分配。linux

跟虚拟机栈相关联的异常有两种:算法

  • StackOverflowError

线程请求的栈深度大于虚拟机容许的最大深度。shell

  • OutOfMemoryError

虚拟机栈扩展时没法申请到足够的内存。服务器

1.四、本地方法栈

用于虚拟机执行Native方法,其余和本地方法栈相同。也会有StackOverflowError和OutOfMemoryError。多线程

1.五、堆

虚拟机启动后建立堆,用于存放对象实例。堆时垃圾回收器的主要工做区域,主要分为新生代和老年代,新生代又能够细分为Eden空间、From Survivor空间、To Survivor空间。java程序启动时,可用-Xmx与-Xms控制堆的大小。若是堆中没有内存完成实例分配而且堆也没法扩展时会抛出OutOfMemoryError。并发

1.六、方法区

方法区主要存储类的元数据,如虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码,JDK1.8以前以永久代实现,JDK1.8以后使用元空间,并且元空间使用的是系统内存。若是没法申请内存时,会抛出OutOfMemoryError。工具

二、垃圾回收

2.一、如何判断对象已经死亡?

2.1.一、引用计数法

在对象中添加一个引用计数器,当有个地方引用时,计数器值+1,当引用失效时,计数器值-1。计数器为0的对象就是死亡的,可是这里有个问题:对象循环引用,两个对象互相引用着对方,致使它们的引用计数器不为0,因而没法通知GC回收。测试

2.1.二、GC Roots搜索

Java中采用的是GC ROOT搜索,思路就是经过一系列名为“GC Roots”的对象做为起点,从这些节点开始向下搜索,搜索中所走过的路径称为引用链,当GC Roots对某一对象不可达时,则证实此对象不可用。

GC Roots包括如下几种:

  • 栈帧中的本地变量表中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈Native方法引用的对象

2.二、垃圾收集算法

2.2.一、标记-清除算法

标记-清除算法分为两个阶段:

  • 标记:首先标记出全部须要回收的对象。
  • 清除:统一回收被标记的对象。

标记-清除.png

这个算法有连个缺点:

  1. 效率不高
  2. 会产生不连续的内存碎片
2.2.二、复制算法

复制算法的效率很高,其将可用内存按照容量划分为大小相等的两块,每次只使用其中的一块,当一块用完时,就将还存活的对象复制到另外一块上面,而后清除掉使用过的内存空间。

复制算法.png

这种算法很适合回收新生代,在新生代中分为Eden空间、From Survivor空间、To Survivor空间,通常分配的内存比例为8:1:1,当回收时,将Eden与From Survivor中还存活的对象一次性拷贝到To Survivor中,以后清理掉Eden与From Servivor空间,当To Survivor空间不够时,须要依赖老年代。

2.2.三、标记-整理算法

在老年代,对象的存活率比较高,因此标记-整理算法被提出来了,首先标记出要回收的对象,而后将全部存活的对象都向一端移动,而后直接清理掉死亡的对象:

标记-整理.jpg

2.2.四、分代收集算法

分代回收的思想就是根据对象的存活周期,将不一样的内存划分为几块,根据每块内存的特色采用适当的收集算法,好比新生代采用复制算法,老年代采用标记-整理算法。

2.三、垃圾收集器

下图展现了7种不一样分代的收集器,若是两个收集器之间存在连线,则表示能够搭配使用。

垃圾回收器.jpg

经过如下命令能够查看垃圾回收器信息:

java -XX:+PrintCommandLineFlags -version

个人测试服务器结果:

-XX:InitialHeapSize=524503488 -XX:MaxHeapSize=8392055808 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC 
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)

能够看到使用的是:ParallelGC。

JVM参数对应关系:

JVM参数对应关系.png

下面简单介绍7中垃圾回收器:

2.3.一、Serial收集器

新生代单线程收集器,简单高效。

2.3.二、ParNew收集器

Serial收集器的多线程版本,除了在垃圾回收时使用多线程,其他都和Serial收集器相同。

2.3.三、Parallel Scavenge收集器

Parallel Scavenge收集器是并行的采用复制算法的新生代收集器,着重于系统的吞吐量,适合后台运算而不须要太多用户交互的任务。

2.3.四、Serial Old收集器

Serial Old是单线程的老年代垃圾收集器,使用标记-整理算法。具备简单高效的特色。

2.3.五、Parallel Old收集器

Parallel Old收集器是Parallel Scanenge的老年代版本,多线程垃圾回收,也是用标记-整理算法。

2.3.六、CMS收集器

CMS注重服务的响应时间,是基于标记-清除算法实现。具备并发收集、低停顿的特色。

2.3.七、G1收集器

Garbage First,基于标记-整理算法,其将整个Java堆(包括新生代和老年代)划分为多个大小固定的独立区域,而且跟踪这些区域里的垃圾堆积程度,在后台维护一个优先列表,每次根据容许的收集时间,优先回收垃圾最多的区域。

三、CPU占用太高问题排查

3.一、 linux查看进程信息

top

linux查看进程信息.png

3.二、查看进程占用cpu最多的线程

ps -mp 23967 -o THREAD,tid,time

查看进程占用cpu最多的线程.png

3.三、线程ID转16进制

printf "%x\n" 23968

线程ID转16进制.png

3.四、查看线程信息

jstack  23967  |grep -A  10  5da0

查看线程信息.png

jstack 23967  |grep 5da0 -A 30

查看线程信息-2.png

3.五、 查看进程的对象信息

jmap -histo:live 23967 | more

查看进程的对象信息.png

3.六、查看进程的GC状况

jstat -gcutil 23967 1000 100

查看进程的GC状况.png

参考

利用jmap和MAT等工具查看JVM运行时堆内存

tencent.jpg