Java常见问题分析

一、JVM简介
1.JVM内存模型
实际占用内存大小:-XX:MaxPermSize + -Xmx + -Xss + -XX:MaxDirectMemorySize
如图一:

主要分为:非堆内存+堆内存+栈内存+堆外内存
JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的
在JVM中堆之外的内存称为非堆内存(Non-heap memory)。
Java虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在Java虚拟机启动时创建的。
堆外内存:DirectMemory是java nio引入的,直接以native的方式分配内存,不受jvm管理。这种方式是为了提高网络和文件IO的效率,避免多余的内存拷贝而出现的。
栈:每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。

2.堆内存分三代
共划分为:年轻代(Young Generation)、年老代(old generation tenured)和持久代(Permanent Generation)。
持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大。年轻代和年老代的划分是对垃圾收集影响比较大的。
年轻代:[Eden/Survisor/Survisor]
    所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
    年轻代分三个区。一个Eden区,两个Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
年老代:
    在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
持久代:
    用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。
新生代和老年代都是堆内存空间
堆的内存模型:
[Eden|from|to]-[old]
\__young____/--\old/
默认的Edem:from:to=8:1:1(可以通过参数–XX:SurvivorRatio来设定),即:Eden=8/10的新生代(young)空间大小,from=to=1/10 的新生代空间大小。
新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young )

3.GC
Scavenge GC
    一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
    新生代通常存活时间较短,因此基于复制算法来进行回收,所谓复制算法就是扫描出存活的对象,并复制到一块新的完全未使用的空间中,对应于新生代,就是在Eden和其中一个Survivor,复制到另一个之间Survivor空间中,然后清理掉原来就是在Eden和其中一个Survivor中的对象。新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到 survivor,最后到老年代。
Full GC
    对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个对进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
    旧生代与新生代不同,对象存活的时间比较长,比较稳定,因此采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,然后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。
年老代(Tenured)被写满
持久代(Perm)被写满
System.gc()被显示调用
上一次GC之后Heap的各域分配策略动态变化

4.JVM提供的GC方式
JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
1)串行GC
    在整个扫描和复制过程采用单线程的方式来进行,适用于单CPU、新生代空间较小及对暂停时间要求不是非常高的应用上,是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定
2)并行回收GC
    在整个扫描和复制过程采用多线程的方式来进行,适用于多CPU、对暂停时间要求较短的应用上,是server级别默认采用的GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数
3)并行GC
    与旧生代的并发GC配合使用
如图二:

二、java常见问题处理
1.进程异常退出
=======================================
可能的原因:
1)系统OOM Killer //grep kill /var/log/messages,查看kill时对应的内存占用total-vm,anon-rss,file-rss
2)人为的kill  //history |grep -i kill
3)代码代用system.exit() //反查代码
4)JVM自身bug //DirectMemory 的默认大小是64M,而JDK6之前和JDK6的某些版本的SUN JVM,存在一个BUG,在用-Xmx设定堆空间大小的时候,也设置了DirectMemory的大小。加入设置了-Xmx2048m,那么jvm最终可分配的内存大小为4G多一些,是预期的两倍。
解决方式是设置jvm参数-XX:MaxDirectMemorySize=128m,指定DirectMemory的大小。
5)内存问题    //内存不足,比如申请一个大的对象的时间。不能及时gc
6)native stack溢出导致 //不受jvm控制,但是被java占用的
致命错误出现的时候,JVM生成了hs_err_pid<pid>.log这样的文件,其中往往包含了虚拟机崩溃原因的重要信息
默认创建在工作目录:可以结合find -name hs_err_pid*
hs_err_pid<pid>.log文件内容

1)触发致命错误的操作异常或者信号
2)版本和配置信息
3)触发致命异常的线程详细信息和线程栈
4)当前运行的线程列表和它们的状态
5)堆的总括信息
6)加载的本地库
7)命令行参数
8)环境变量
9)OS的CPU信息

2.OOM
=======================================
1)Java heap space/GC overhead limit exceeded
    dump分析:启动参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=  或者jmap -dump:format=b,file=文件名 [pid]
    使用mat工具分析heapdump
    占用内存较大代码优化
    如果内存占用不多:可能是创建了一个大对象导致,根据日志分析创建大对象的时间/jstack分析是否存在死循环
2)PermGen space
    调大PermSize
    是否动态加载Groovy脚本
    是否有动态生成类逻辑,比如使用cglib大量动态生成类
3)Direct buffer memory
    默认占用-Xmx相同的内存 //-XX:MaxDirectMemorySize=1G调整
    网络通信使用Netty但是未限流
    分析代码中是否使用DirectyBuffer未合理控制
4)java.lang.StackOverflowError
    调小-Xss使每个线程栈的内存占用减小 //设置每个线程的堆栈大小
    调小-Xmx,给栈更多的内存空间
    分析代码中是否存在不合理的递归
5)request bytes for Out of swap space
    地址空间不够 //64bitos
    物理内存不够:jmap -histo:live pid ,如果内存明显减少,说明是directbuffer问题,通过-XX:MaxDirectMemorySize设定
    btrace Inflater/Deflater
6)unable to create new native thread
    ulimit -a //vim /etc/security/limits.conf添加
    * soft noproc 11000
    * hard noproc 11000
    * soft nofile 5000  //修改限制
    * hard nofile 5000  //修改限制
/proc/sys/kernel/pid_max 操作系统线程数限制
/proc/sys/vm/max_map_count 单进程mmap的限制会影响
/proc/sys/kernel/thread-max
/proc/sys/vm/max_map_count
max_user_process(ulimit -u)

7)Map failed
如图三:

3.CPU过高
=======================================
基本命令:top,vmstat,mpstat,sar,tsar
us高:用户进程消耗的CPU时间多
    原因:full gc,CMS gc,代码死循环,整体消耗CPU多等
    方案:查看gc.log ;jstat -gcutil [pid] //https://github.com/oldratlee/useful-scripts/blob/master/show-busy-javathreads.sh
sy高:内核消耗的CPU时间多
    原因:锁竞争激烈,线程主动切换频繁
    方案:jstack查看是否有锁,或者是否是线程切换频繁。//修改为无锁结构,线程切换频繁改为通知机制
        btrace ConditionObject.awaitNanos 是否存在很小值,最好是ms级别
wa高:等待IO的CPU时间多,随机IO太多或者磁盘性能问题
    原因:io读写频繁
    方案:iostat,iotop,lsof//增加缓存,同步改为异步,随机写入改为顺序写

4.应用无响应
=======================================
CPU高
OOM
死锁     jstack -l 查看对应死锁的线程 //去掉死锁,
线程池满    //增大线程池,减少耗时

5.环境变量异常
=======================================
时区错误/变量错误/编码方式错误
解决方案:
    jinfo 查看具体的启动参数

6.调用超时    
=======================================
服务端慢/服务端或调用端gc/服务端或调用端CPU高/大对象序列化慢/网络问题,丢包

=======================================
三、案例分析
案例一:"PermGen space"
java.lang.OutOfMemoryError: PermGen space
Exception in thread "http-bio-17788-exec-75"
明显可以看出是老年代的内存溢出,说明在容器下的静态文件过多,比如编译的字节码,jsp编译成servlet,或者jar包。
解决此问题,修改jvm的参数 permsize即可,permsize初始默认为64m。

-vmargs -Xms128M -Xmx512M -XX:PermSize=64M -XX:MaxPermSize=128M
-vmargs 说明后面是VM的参数,所以后面的其实都是JVM的参数了
-Xms128m JVM初始分配的堆内存
-Xmx512m JVM最大允许分配的堆内存,按需分配
-XX:PermSize=64M JVM初始分配的非堆内存
-XX:MaxPermSize=128M JVM最大允许分配的非堆内存,按需分配

http://makaidong.com/gsycwh/1/147114_9379890.html
由衷感谢:海水同学支持。

原文地址:http://blog.51cto.com/hmtk520/2067043

时间: 2024-10-13 22:09:40

Java常见问题分析的相关文章

JAVA学习(六):JAVA中的继承及其常见问题分析

JAVA中的继承及其常见问题分析 1.JAVA中继承的定义 JAVA中,类的继承是通过扩展其他类而形成新类来实现的,原来的类称为父类(Super Class)或基类,新的类称为原来类的子类或派生类.在子类中,不仅包含了父类的属性和方法,还可以增加新的属性和方法,从而使得父类的基本特征可被所有子类对象共享. 注:类的继承并不改变类成员的访问权限,也就是说,如果父类的成员是公有的.被保护的或默认的,它的子类仍具有相应的这些特性. /**********************************

Web开发常规调试方法与常见问题分析

一.Web项目基本原理 现在的web项目大都已经前后端独立开发与部署. 前后端独立开发,一般是前端与后端通过web接口(常见的有RESTful与websocket)文档进行交流.前端开发人员先更具业务需求与实际原型进行ui的编程实现与事件的设计,并通过web接口进行业务数据的增删改查.后端开发人员根据定义的接口文档,实现业务数据的计算与增删改查,并对持久化的数据(一般保存在数据库里)进行操作. 前后端独立部署,前端项目均为html5/css/javascript/图片等静态资源,只需要web服务

java代码分析及分析工具

java代码分析及分析工具 一个项目从搭建开始,开发的初期往往思路比较清晰,代码也比较清晰.随着时间的推移,业务越来越复杂.代码也就面临着耦合,冗余,甚至杂乱,到最后谁都不敢碰. 作为一个互联网电子商务网站的业务支撑系统,业务复杂不言而喻.从09年开始一直沿用到现在,中间代码经过了多少人的手,留下了多少的坑,已经记不清楚了,谁也说不清了. 代码的维护成本越来越高.代码已经急需做调整和改善.最近项目组专门设立了一个小组,利用业余时间做代码分析的工作,目标对核心代码进行分析并进行设计重构. 代码分析

HDFS API的java代码分析与实例

HDFS API的java代码分析与实例 1.HDFS常用的方法,我已经写好,我们看一下 // Create()方法,直接在HDFS中写入一个新的文件,path为写入路径,text为写入的文本内容 public static void  Create(String path,String text) throws IOException {             Configuration conf=new Configuration();                  conf.set(

J2SE快速进阶——Java内存分析

程序的执行过程 要在Java中分析内存,我们先来了解一下程序的执行过程: 正如上图所示,大致分为3个步骤: 1.最开始,我们的程序是存在于硬盘中的,当启动运行时,程序会被加载(load)到内存中去,这里的内存可以看做我们的内存条: 2.此时,内存中除了存在刚加载的程序的代码,还存在操作系统本身的代码(好吧,此句可以当做废话→_→),操作系统会找到程序中的Main方法开始执行程序: 3.第三步就是本文的重点,系统在程序执行过程中对内存的管理.在Java中,内存大致会被分为四块--heap(栈).s

学java教程之java内存分析

学编程吧学java教程之java内存分析发布了,欢迎大家通过xuebiancheng8.com来访问 java的内存模型是java中非常重要的知识,也是面试的时候重点. java虚拟机的内存模型中和我们打交道多的分为这么几个区域 堆区,栈区,方法区. 其中方法区又分为常量池,静态区和方法区. 这几部分分别是干嘛的呢,堆区是用来存放new出来的对象的,堆区是应用程序共享的区域. 栈区又叫方法栈,程序在运行的时候,代码要在方法栈中运行,运行的代码需要放在方法栈中来执行,然后寄存器一行一行加载执行.

Java AsyncTask 分析内部实现

sdk3.0前,使用内部的线程池,多线程并发执行.线程池大小等于5,最大达128 sdk3.0后,使用默认的serial线程池,执行完一个线程,再顺序执行下一个线程.sdk4.3时 线程池大小等于5,最大达128 sdk4.4后线程池大小等于 cpu count + 1,最大值为cpu count * 2 + 1 sdk3.0后有两种线程池的实现,默认为 Serial 线程池 public static final Executor SERIAL_EXECUTOR = new SerialExe

Java性能优化指南系列(二):Java 性能分析工具

进行JAVA程序性能分析的时候,我们一般都会使用各种不同的工具.它们大部分都是可视化的,使得我们可以直观地看到应用程序的内部和运行环境到底执行了什么操作,所以性能分析(性能调优)是依赖于工具的.在第2章,我强调了基于数据驱动的性能测试是非常重要的,我们必须测试应用的性能并理解每个指标的含义.性能分析和数据驱动非常类似,为了提升应用程序的性能,我们必须获取应用运行的相关数据.如何获取这些数据并理解它们是本章的主题.[本章重点介绍JDK中提供的性能分析工具] 操作系统工具及其分析 程序分析的起点并不

Java常见问题之初始化过程

假设有个名为Dog的类 对于静态字段: 1. 当首次创建类型为Dog的对象时,或者Dog类的静态字段/静态方法首次被访问时,Java解释器会查找类路径,以定位Dog.class文件. 2. 载入Dog.class,此时初始化所有静态字段,如果没有对静态字段进行显示初始化,则默认将所有基本类型字段都设置成标准初值,而引用字段被设置成null. 对于非静态字段: 1. 当创建类型为Dog的对象时,首先将在堆上为该对象分配存储空间,且存储空间会被清零,这就将所有基本类型字段都设置成了标准初值,而引用字