本博客仅供自己参考和学习,如有不足之处请担待。
JVM本质上就是一个操作系统,主要干了件事,对Class字节码编译,内存管理。
一、Class编译,通过Classloader,隐式装载(不详细说明)
①加载 ②验证 ③解析 ④初始化
二、内存布局
当我们把程序放到虚拟机中运行的时候,JVM不会吧我们用到的所有数据全部一股脑的,全部塞到内存中,他会按照数据的性质进行分类。这就形成了运行时的数据区域,主要有由执行引擎处理的方法区和堆。以及由JNI(本地接口)处理的虚拟机栈,本地方法栈和程序计数器。
程序计数器,可以理解为我们在运行程序的时候,代码所在的行号的提示,这就是程序计数器最大的作用。( 特别是在**if,where,for等等这些控制跳转语句**的时候)
java虚拟机栈:每个方法在调用时就会创建一个栈帧,每个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机中入栈到出栈的过程。
堆: 是java虚拟机所管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,对象实例在这里被分配内存。是垃圾回收器(GC)管理的主要区域;
方法区;存储已被虚拟机加载的类信息,常量,静态变量;即编译后的代码等数据;运行时常量池(runtime constant pool),也是常量池的一部分。
直接内存:并不是虚拟机运行时数据区域的一部分,也不在java虚拟机规范中定义的内存区域。这个直接内存往往用在java IO通信上,并不是所有java程序都用上,但是这个直接内存,可以被虚拟机所管理,而且我们有参数,可以调节直接内存的大小;
这里垃圾回收算法我就不写了。主要讲一下实战中的JVM性能调优;
三、内存溢出和内存泄漏
1、内存溢出(OOM out of memory)
原因:程序在申请内存时,JVM中没有足够的内存空间
2、内存溢出的几种方式:
①、栈溢出: StackOverFlowError,常见于死递归,栈内存默认1M
②、堆溢出: OutOfMemory:Java heap space / GC overhead limited exceeded
如果垃圾回收占用98%资源,但回收效率不到2%,就会发生OOM的GC overhead limited exceeded情况
③、方法区溢出(这里又叫永久代、垃圾回收效率很低)
通常占用这块区域的有动态语言,Cglib/JSP等一些运行时需要编译成class字节码的,OSGI(使用多种类型的加载器)
④、直接内存溢出
2、内存泄漏
原因: 程序在申请空间后,无法释放已申请的内存,导致内存中始终被占用,如果过多的内存泄漏容易导致OOM,内存泄漏往往是代码有问题导致的。以下是内存泄漏的几种原因;
①、长生命周期对象持有短生命周期对象的引用。导致短生命周期对象无法GC
②、连接未关闭。 像数据库连接池,网络连接,IO等。。通常使用try_finally
③、变量作用域不合理。定义变量超过使用范围
④、内部类持有外部类。
⑤、hashcode值改变
与内存泄漏的不同之处:
内存溢出:是实实在在的内存不够用导致的
内存泄漏:应该释放的对象没有释放,导致可使用内存小
解决办法
内存溢出:检查代码+虚拟机参数设置
内存泄漏: 一定是代码导致的
MAT内存泄漏分析工具:可对内存泄漏提出猜想
几个重要的参数:
shallow Heap :浅堆: 对象本身所占有的内存大小
Retailed Heap: 深堆:就是一个对象回收后,可以真实回收的大小。深堆包含那些只被它引用对象。
只要该对象被回收,该对象所单引用的对象也能被回收,引用的对象可达性没有了。其中深堆里面有两个参数,outgoing:谁引用了它,income: 它引用了谁。
四、JDK为我们提供的内存分析工具
命令分析工具:
Jps : 虚拟机进程状态工具
Jstat:虚拟机统计信息工具
JInfo::java配置信息工具
Jmap:java内存映射工具
jhat: 虚拟机转储快照分析工具
Jstack :java堆栈跟踪工具
可视化工具:
Jconsole java监视与管理控制台
VisualVM : 多合一故障处理工具
怎么使用不再详细讲述,很多其他的博客都有;
五、JVM调优和深度了解性能优化
1、 原则:大多数Java应用不需要GC调优,需要调优的,不是参数问题,是代码问题。在项目中,分析GC情况,从而优化代码,比调整GC参数更重要。GC优化是最后的手段。简单来说就是调节系统稳定性,优化代码。
2、GC调优的目的:从MinorGC和Full GC时间和次数考虑,使用-XX:+PrintGCDetails打印GC日志;
达到GC的最佳指标
①、GC的时间足够短(Minor GC执行不到50ms,Full GC执行不到1s)
②、GC的次数足够小( Minor GC执行不频繁,大于10秒/次,FullGC能达到10分钟/次)
GC调优的最重要的三个选项:
第一位:选择合适的GC回收器
第二位:选择合适的堆大小
第三位:选择年轻代在堆中的比重
3、调优步骤(通过IDEA中VM参数添加JVM参数)
①、监视GC状况。在IDEA添加:-XX:+printGCDetails,打印日志。
使用各种JVM工具,查看当前日志,分析当前JVM参数设置,并且分析当前堆内存快照和gc日志,根据实际的各区域内存划分和GC执行时间,觉得是否进行优化;
②、分析日志情况,判断是否需要调优。
如果各项参数设置合理,系统没有超时日志出现,GC频率不高,GC耗时不高,那么没有必要进行GC优化;如果GC时间超过1-3秒,或者频繁GC,则必须优化;
注:如果满足下面的指标,则一般不需要进行GC:
Minor GC执行时间不到50ms;
Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s;
Full GC执行频率不算频繁,不低于10分钟1次;
③、调整垃圾回收类型,内存分配参数。
如果内存分配过大或过小,或者采用的GC收集器比较慢,则应该优先调整这些参数,并且先找1台或几台机器进行beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择;
④、GC调优是一个不断分析的过程。根据系统运行情况具体问题具体分析
通过不断的试验和试错,分析并找到最合适的参数
⑤、全面应用参数;
如果找到了最合适的参数,则将这些参数应用到所有服务器,并进行后续跟踪。
注: i、JVM中添加设置-XX:+printGCDetails,设置需要加一个加号。添加参数不用加号,而是用等号赋值。如-XX:MataSpaceSize=64M。如果添加参数是一个缩写形式:如-Xms500(堆初始空间调整),不需要等号。
ii、在阅读GC日志的时候,主要关注FullGC和MinorGC的时间和次数。
4、实战中调优(后期有时间再加)
5、推荐配置
- 年轻代大小选择
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择).在此种情况下,年轻代收集发生的频率也是最小的.同时,减少到达年老代的对象.
- 吞吐量优先的应用:尽可能的设置大,可能到达Gbit的程度.因为对响应时间没有要求,垃圾收集可以并行进行,一般适合8CPU以上的应用.
- 避免设置过小.当新生代设置过小时会导致:1.YGC次数更加频繁 2.可能导致YGC对象直接进入旧生代,如果此时旧生代满了,会触发FGC.
- 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数.如果堆设置小了,可以会造成内存碎 片,高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间.最优化的方案,一般需要参考以下数据获得:
- 并发垃圾收集信息、持久代并发收集次数、传统GC信息、花在年轻代和年老代回收上的时间比例。
吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代.原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象