基本上来说,JVM是一个虚拟运行环境,对于字节码来说就像是一个机器一样,可以执行任务,并通过底层实现执行内存相关的操作。
JVM使Java程序做到了“一次编写,到处运行”。
JVM解放了程序员,使程序员不必再关系对象的生命周期,使程序员不必再关心应该在何时释放内存。
可以将JVM当做是一种专为Java而生的特殊的操作系统,它的工作是管理运行Java应用程序的运行时环境。
out-of-memory
系统无法为新线程和新对象分配空间
“堆”满了,垃圾回收不成功
影响:业务宕机
各个年代:
新生代(Young Generation)
伊甸园空间(Eden )、幸存者空间(Survivor )
最新被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可到达,所以很多对象被创建在新生代,然后消失。
老年代(Old Generation)
对象没有变得不可达,并且从新生代中存活下来,会被拷贝到这里。
持久代(Permanent Generation)
也被称为方法区(method area)。他用来保存类常量以及字符串常量。
先理解养鱼专业户的“渔池” ?
每个空间的执行顺序如下:
绝大多数刚刚被创建的对象会存放在伊甸园空间。
在伊甸园空间执行了第一次GC之后,存活的对象被移动到其中一个幸存者空间。
此后,在伊甸园空间执行GC之后,存活的对象会被堆积在同一个幸存者空间。
当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。之后会清空已经饱和的那个幸存者空间。
在以上的步骤中重复几次依然存活的对象,就会被移动到老年代。
垃圾回收(GC)是旨在释放不可达Java对象所占用的内存的过程,是Java virtual machine(JVM)中动态内存管理系统的核心组成部分。在一个典型的垃圾回收周期中,所有仍被引用的对象,即可达对象,会被保留。没有被引用的Java对象所占用的内存会被释放并回收,以便分配给新创建的对象。
发生在新生代的对象的消失:minor GC(局部垃圾回收)
发生在老年代的对象的消失:major GC(完全垃圾回收)
当你在启动Java应用程序时指定了启动参数_-Xmx_(例如,java -Xmx2g MyApp),则相应大小的内存会被分配给Java进程。这块内存即所谓的*Java堆*(或简称为*堆*)。这块专用的内存地址空间用于存储Java应用程序所创建的对象。随着Java应用程序的运行,会不断的创建新对象并为之分配内存,Java堆(即地址空间)会逐渐被填满。
32位与64位的区别
32位JVM最大堆为1.5G
64位JVM最大堆为操作系统中内存可使用空间大小
minor GC
major GC
目标
快速释放不可达对象所占用的内存,防止应用程序出现OOM错误。
回收内存时,对应用程序的性能(指延迟和吞吐量)的影响要尽可能小
原则
JVM堆不是越大越好(堆越大GC时间越长)
不是每个应用程序的JVM配置均可以一样(应用规模、结构和响应要求不一样)
调优的操作是循序渐进的(摸着石头过河)
JVM监控工具
命令行:
jstat (关注GC的次数和GC所耗时间)
jmap (将内存堆印象保存为文件)
HeapAnalyzer(分析jmap保存的文件)
图形化
Jconsole
VisualVM
jstat –gc $vmid$ 1000 (vmid是java应用的进程id号,1000:每隔一秒展示GC监控数据)
从上图中可计算得到以下值:
年青代GC发生了1989次(YGCT),共耗费了41.001秒(YGC),故年青代每次GC的时间为: 41.001/ 1989= 0.020秒
完全GC发生了1次(FGCT),共耗费了0.971秒(FGC),故完全GC每次GC的时间为:0.971/1=0.971秒
一般性能较好的判断:年青代GC耗时在50~100毫秒以内,完全GC小于1秒以内为很好的性能。(因为GC均会影响系统访问)
jmap -dump:format=b,file=testjvm.dump $vmid$
(vmid是java应用的进程id号,file:java堆内存数据保存为文件的名称)
以上的命令可以将当前JAVA内存堆保存为文件,便于进一步进行分析。
特别注意:
进行此工作的时候,应用访问可能会中断。所以不应该在业务运行状态下操作此命令。
HeapAnalyzer
命令行:java -Xmx800m -jar ha395.jar
(注意:-Xmx800m 是指定工具所需的java堆大小,此值要大于分析文件的尺寸大小,如果启动过程中发现控制台有 java.lang.OutOfMemoryError出现,可以适当加大上面的数字800,给予更多的空间。ha395.jar中的文件名称按实际下载解压后的文件名为准)
HeapAnalyzer输出的内容:以下表内容即显示当前系统中出现泄漏的对象(flex/messaging/messages/AsyncMessage)及占用内存的大小。
Jconsole
命令行:jconsole.exe server_IP:7080
(注意:Server_IP为运行java程序的服务器IP,7080为专门为jconsole远程连接而配置开通的JMX端口。
以下为Windows平台下配置方法:
set JAVA_OPTS=%JAVA_OPTS% -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=7080 -Dcom.sun.management.jmxremote
)
visualVM
JDK1.6以上版本免费提供,服务器上相应配置与Jconsole连接时一样
(注意:Server_IP为运行java程序的服务器IP,7080为专门为jconsole远程连接而配置开通的JMX端口。
提示:如visualVM在JAVA程序本机运行,可以直接运行监控到,不需对JMX端口进行设置)
通过以下jconsole监控截图,我们发现应用服务器的堆内存一直在上升。虽然有定时的GC操作(内存回收),但整体趋势仍然是一个斜坡上行的过程。
通过以下jconsole监控截图,我们发现应用服务器的堆内存已经占用完,年青代和老年代均没有可进行再分配的空间了,此时执行GC也不能成功。当前应用的状态就是无法访问了。
再通过其GC时间可看到,Copy(即年青代回收)平均每次为1*60/18646=0.0032秒,没有问题;FullGC为4680秒(1小时18分钟)/2304=2.03秒。
由此可以看出,FullGC占用了大量时间和次数,但仍然无法将内存清理出来。证明内存中的某些对象未被成功释放,需要继续分析。
通过heapdump分析工具,发现系统中已经有非常多的flex/messaging/messages/AsyncMessage实例占用了大部分的内存。
由此即定位了是哪个实例出现问题,可以联系开发人员进行配合检查了。
通过开发人员分析检查,确认flex的消息对象默认没有设置其失效时间,建立的对象将永远不会失效,故就会造成对象不停的建立,直到内存溢出。
故修改其配置文件,设置相关对象失效时间,达到JAVA可以回收其占用的内存空间,如下:
以下配置为针对一个特定的应用环境进行的配置:
-Xmx4096m 最大JAVA堆4G
-Xms4096m 初始化JAVA堆4G,一般服务器上均设置为最大一样
-Xmn1536M 年青代堆设置值1.5G
-XX:SurvivorRatio=10 年青代中Eden区与Survivor区的比例
-XX:PermSize=250M 持久代的尺寸固化为250M
-XX:MaxPermSize=250M 与上一个设置一致
-XX:+UseParallelOldGC 老年代使用并行回收GC方法,这样可使收集过程中应用仍然提供访问
-XX:MaxTenuringThreshold=30 Survivor区中对象要经过30次复制后仍然有效时,才会移动到老年代
-XX:CMSInitiatingOccupancyFraction=50 老年代占用当达到50%以启动FullGC
根据操作系统内存使用状况,配置合理的JVM堆大小。一般32位系统不能超过1.5G,64位系统可根据应用规模及系统剩余空间调整。并且将Xms和Xmx设为一样的值;
年青代堆设置一般设置为整个JVM堆的3/8左右;
持久代堆设置一般根据应用程序加载的类、反射的类和调用的方法等情况调整,达到应用长期使用而不报告:java.lang.OutOfMemoryError: PermGen space
特殊配置:
-XX:CMSInitiatingOccupancyFraction=50 老年代占用达50%时启动FullGC
-XX:MaxTenuringThreshold=30 Survivor区中对象要经过30次复制后仍然有效时,才会移动到老年代。一般用于应用中很多对象必须长时间存活的情况,尽量让在这些对象在青年代中被消灭
-XX:+UseParallelOldGC 老年代使用并行回收GC方法,这样可使收集过程中应用仍然提供访问。一般用于应用业务响应要求严格,但此设置效率比较低,并且只能用于多CPU服务器。