一,JVM内存模型概括
还有一个寄存器,线程运行于其上面
1.程序计数器
记录线程的执行位置,线程私有内存,唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域
2.线程栈(VM stack)
栈的默认大小是1M
-Xss2m 这样设置成2M
异常 :Fatal: Stack size too small
异常的引起一般是线程数目太多
3.本地方法栈(native stack)
即为一些Native方法分配的stack
异常:java.lang.OutOfMemoryError: unable to create new native thread
一般也是由线程太多引起,增加栈空间,同上方法
4.堆(heap),程序可用堆
截图自JConsole
每个线程的栈都是该线程私有的,堆则是所有线程共享的
这里说的堆,主要指程序能控制的,包括
The New Generational Heap,默认4M,此区域一般为JVM内存的1/15大小
此代分为Eden space区,Survivo space区可以看成emptySurvivo区,Survivor区,
当new 一个对象时,首先是在Eden space区,当Eden space区满时,Survivor space区进行垃圾回收(此处复制算法),当对象在Survivo space区经过几次回收Tenured Generation
复制算法,每次算法开始都得停止当前所有的线程,然后把Survivor区的所有活跃的对象复制到emptySurvivo区,然后对Survivor区空间进行清除变成emptySurvivo,以前的emptySurvivo成为了Survivor区。(互换)
Tenured Generation,
此处储存年老代的对象,此处的垃圾回收采用The Mark and Sweep 算法
GC标记算法/清理算法(The Mark and Sweep algorithms)进行回收,从引用进行标记,然后按照引用的程度或无引用到的对象进行回收,然后再对清除了的内存进行合并.
关于GC,因为GC主要就是对堆的回收,当然还有常量池和永久代,所以此处总结下GC
GC策略介绍
对于GC在 HotSpot VM 常用的有三种:
1.serial collector,单线程收集器,回收时都需要暂停当前线程,长时间等待,
配置
Client下默认方式
强制加上 -XX:+UseSerialGC
2.parallel collector( throughput collector ),并行收集器,或叫多线程的收集,
年轻代:暂停应用程序,多个垃圾收集线程并行的复制收集。
年老代:暂停应用程序, 多个垃圾收集线程并行的复制收集。
server下默认方式,具体配置
设置并行收集的线程数目,如20个线程,-XX:ParallelGCThreads=20
配置年轻代为并行收集 -XX:+UseParallelGC.
配置年老代垃圾收集方式为并行收集(JDK6.0开始支持)-XX:+UseParallelOldGC
设置暂停时间,设置每次年轻代垃圾回收的最长时间如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值,如 -XX:MaxGCPauseMillis= 100,
设置吞吐量,吞吐量为垃圾回收时间与非垃圾回收时间的比值 -XX:GCTimeRatio 来调整GC的时间
3.concurrent collector(concurrent low pause collector),并发收集器,
年轻代:同样是暂停应用程序,多个垃圾收集线程并行的复制收集。
年老代:和并行的区别在这,只是在初始标记(initial mark)和二次标记(remark)时需要stop-the-world。但收集时时间很长,所以不能等年轻代满后再开始清理.
使用启动并发收集器 -XX:+UseConcMarkSweepGC
XX:CMSInitiatingOccupancyFraction=指定还有多少剩余堆时开始执行并发收集
- 根据官方文档,他们俩个需要在多CPU的情况下,才能发挥作用。在一个CPU的情况下,会不如默认的serial collector,因为线程管理需要耗费CPU资源。而在两个CPU的情况下,也提高不大。只是在更多CPU的情况下,才会有所提高。当然 concurrent low pause collector有一种模式可以在CPU较少的机器上,提供尽可能少的停顿的模式,见CMS GC Incremental mode。
- 当要使用throughput collector时,在java opt里加上-XX:+UseParallelGC,启动throughput collector收集。也可加上-XX:ParallelGCThreads=<desired number>来改变线程数。还有两个参数 -XX:MaxGCPauseMillis=<nnn>和 -XX:GCTimeRatio=<nnn>,MaxGCPauseMillis=<nnn>用来控制最大暂停时间,而-XX: GCTimeRatio可以提高GC说占CPU的比,以最大话的减小heap。
- 此段引自 HotSpot VM GC 的种类
GC的触发条件(基于serial collector,不同的GC策略略有不同,基本都差不多)
对于new generation来说,当eden区的对象空大于Survivor0(假设为from)的free空间时,会发生minor gc,即对整个Survivor0区,Survivor1区进行一个复制算法垃圾回收,并且部分对象转到Old Generation
当new generation满了(即eden区+Survivor0区大于Old Generation的free空间时),将发生major gc,即对New Generation和Old Generation两代都进行垃圾回收
当然还一种程序调用system.gc(),但此方法也不一定会调用,只是建议
所以得出,如果JVM的设置内存过大,发生GC的回收频率将越小,但是回收的时间越长(特别对new generation进行复制算法的复制时是需要停止当前所有线程的),所以并不是说JVM的内存设置的越大越好,得根据实际情况进行优化。
异常 java.lang.OutOfMemoryError: Java heap space
一般是由于垃圾回收后,old generation里空间也不够用了
堆内存的相关参数设置
默认值(基于-server)
-server时最大堆内存是物理内存的1/4,但小于1G. JDK 1.5以前是64M
(官方: Smaller of 1/4th of the physical memory or 1GB.Before J2SE 5.0, the default maximum heap size was 64MB.)
-client 小一倍
参数设置
-Xms128m
表示JVM Heap(堆内存)最小尺寸128MB,初始分配
-Xmx512m
表示JVM Heap(堆内存)最大允许的尺寸256MB,按需分配
new Generation与Old Generation的比例,默认为1:2,即为2
-XX:NewRatio= 参数可以设置(也可以-XX:NewSize和-XX:MaxNewsize设置新域的初始值和最大值)
Eden与Survivor的比例,默认为32
-XX:SurvivorRation=参数可以设置
当对象默认经过1次New Generation 就转入Old Generation(这个不同文章上不同,待我确定)
-XX:MaxTenuringThreshold=参数可以设置 (默认0)
用-XX:+PrintTenuringDistributio可以查看值
5.方法区,永久代(Perm Space)
其实也可与看成堆内存的一部分,看成永久代(Perm Space),但GC也会回收,但很少回收,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,比如一个int x,在不同的环境中是不同的,此信息就存在方法区
异常:java.lang.OutOfMemoryError: PermGen space
引起,一般是太多的类信息,比如应用spring很多反射信息,可以适度设置大点
方法区或永生代相关设置
-XX:PermSize=64MB 最小尺寸,初始分配
-XX:MaxPermSize=256MB 最大允许分配尺寸,按需分配
XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled 设置垃圾不回收
默认大小
-server选项下默认MaxPermSize为64m
-client选项下默认MaxPermSize为32m
6.常量池
Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool table),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。
GC的作用主要是用来卸载类和回收常量池,当然有部分方法区,即使永久代(Perm Space)也会一定的回收
7.native内存区“Code Cache”(non-heap)
这个没画出来,但通过工具可以看到,用于存放编译和保存本地代码(native code)的内存
二,对TOMCAT监控调优
1,监控TOMCAT内存
JConsole,JDK自带
直接在命令行输入JConsole,打开JConsole,如堆那个图,就可以监控了
远程监控,需要在启动程序时,配置 com.sun.management.jmxremote 即可
比如tomcat,
打开%tomcat_home%/bin/catalina.bat (linux下是catalina.sh),在JAVA_OPTS=%JAVA_OPTS%后加上参数
-Dcom.sun.management.jmxremote.port=1090 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
然后打开JConsole
然后连接即可
如打开堆图,可以找到上面说的各个部分
Eden Space (heap): 内存最初从这个线程池分配给大部分对象。
Survivor Space(heap):用于保存从Eden Space copy的对象,复制算法,分两部分Survivor
Tenured Generation (heap):用于保持已经在 survivor space内存池中存在了一段时间的对象。
Perm Generation (non-heap): 方法区,保存虚拟机自己的静态(refective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的
Code Cache(non-heap):HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)
2,打印Tomcat的日志
在前面继续加上参数 -Xloggc:%TOMCAT_HOME%\logs\tomcat_gc.log , 把LOG日志文件输出到tomcat_gc.log
打开日志如下
0.755: [GC 11747K->1664K(62848K), 0.0039655 secs] 0.759: [Full GC 1664K->1604K(62848K), 0.0241215 secs]
如果不满意,可以进行调优,参数如文中上面参数所示,直接设置于JAVA_OTPS即可
3,利用JSTA查看GC情况
详细 附上
jstat 使用
其他工具
JProfiler :商业软件,需要付费。功能强大。详细说明参考
VisualVM :JDK自带,功能强大,与JProfiler类似,官网地址下载http://visualvm.java.net/download.html(当然JDK7现在都自带了,在jdk1.7.0_10\bin里)
JConsole和VisualVM 在JDK都自带了 o(∩_∩)o 哈哈 感觉JProfiler这些收费的挺尴尬
OK,垃圾回收和调优原理打完收工,大概清楚 ,调优了就有方向了,具体参数可以查文档等
本文是基于HotSpot VM,其它JVM可能有些不一样,累死了,欢迎交流QQ107966750