一,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。

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情况

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可能有些不一样,累死了,欢迎加小编V:k15197783918