1. 常遇到关于内存溢出的错误
java.lang包下
StackOverflowError 很少
OutOfMemoryError: heap space(堆空间) 比较常见
OutOfMemoryError: PermGen space 经常出现
2. Java虚拟机结构和属性
内存区域: 保存java类和对象的物理区域
堆: Java的内存区域叫做堆(heap)
堆被分成3个区域:
新域(young generation)、旧域(tenured generation)、永久域(perm generation)
标记为virtual的部分被保留下来,必要时才分配出去。
新域: 有Eden和两个救助空间survivor组成,新对象存放在Eden中
旧域: 对象在两个救助空间survivor之间移动,当它们足够"老",能够被移入到保存生存期较长对象的旧域
永久域: 在虚拟机的整个生存期都生存的对象
常用虚拟机配置选项属性
-Xmx Java Heap最大值,默认值为物理内存的1/4,最佳设值应该视物理内存大小及计算机内其他内存开销而定;
-Xms Java Heap初始值,Server端JVM最好将-Xms和-Xmx设为相同值,开发测试机JVM可以保留默认值;
-Xmn Java Heap Young区大小,不熟悉最好保留默认值;
-Xss 每个线程的Stack大小,不熟悉最好保留默认值;
具体实施查看 remark001_几招搞定JVM内存设置.txt
3. JVM垃圾回收机制,2种回收方法,7种垃圾收集器
2种回收方法
引用计数: 当应用程序引用某一对象的时候,JVM增加引用数,当引用超出范围时,JVM减少引用数,
当某对象的引用数为0时,便可以进行垃圾回收机制。(早期JVM的技术)
对象引用遍历(对象引用树) => 目前大多数JVM技术
对象引用遍历树,是将对象引用关系构建成一棵树,从一组根对象开始,沿着整个对象树上的每条链接,
递归确定可到达(reachable)的对象。如果某对象别的对象(至少一个)引用,则将它作为垃圾收集起来。
在对象遍历阶段,GC必须记住哪些对象可以到大,以便删除不可到达的对象,这称为标记(marking)对象。
7种垃圾收集器
标记-清除收集器(Serial收集器): 对遍历对象图进行遍历,并标记可到达的对象,然后扫描对象以寻找未标记对象并释放内存。
该收集器一般使用单线程工作并停止其它操作。
(你妈妈在打扫房间,会叫你出去等一会)
标记-清除-压缩收集器:
与Serial收集器有相同的标记阶段。在第二阶段,把标记对象复制到堆栈的新域中以便压缩堆栈。
这种收集器也会停止其它操作。
复制收集器: 它将堆栈分为2个域,称为半空间,它每次仅使用一个半空间。
GC运行时,JVM生成的新对象放在一个半空间中,将可到达对象复制到另一个半空间中,从而压缩堆栈。
这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。
增量收集器: 把堆栈分为多个域,每次仅从一个域收集垃圾。这会造成较小的应用程序中断。
分代收集器: 把堆栈分成两个或多个域,用以存放不同寿命的对象。JVM生成的新对象一般放在其中的某个域中。
过一段时间,继续存在的对象将获得使用期并转入更长寿命的域中。
分代收集器对不同域使用不同的算法以优化性能。
并发收集器: 同时发生,与应用程序同时运行。该收集器在某一时刻一般不得不停止其它操作以完成特定任务,
但是因为其它应用程序可进行后台操作,所以中断其它处理的实际时间大大降低。
并行收集器: 使用某种传统算法,并使用多线程并行执行它们的工作。在多CPU机器上使用多线程技术可以显著提高Java应用程序的可扩展性。
解释两个名词:并发和并行。
这两个名词都是并发编程中的概念,在谈论垃圾收集器的上下文语境中,他们可以解释为:
并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),
用户程序继续运行,而垃圾收集程序运行于另一个CPU上。
4. JVM内存区域配置: 堆区域、新域与旧域、永久区域(非堆内存)、新域子空间
Sun的JVM使用的是分代收集器,它把堆分成3个主要域: 新域与旧域、永久区域(也叫非堆内存分配).
JVM生成的所有新对象放在新域、一旦对象经历了一定数量的垃圾收集循环后,便获得使用期进入旧域。
在永久域中JVM则存储类和方法对象。
Java虚拟机的配置选项有专门用于对这些域进行配置的属性
配置堆区域
堆实质上就是新域+旧域的和,它代表这2个区域的内存大小。
设置堆的初始大小和最大大小内存 128MB
java -Xms128m (其中s为start )
java -Xmx128m (其中x为max )
通常可以将初始化大小设置为最大大小,这样可以避免程序动态增加堆的大小。
配置新域和旧域
设置新域大小64MB
java -Xms256m -Xmx256m -Xmn64m (Xmn 其中n为new)
设置新域的初始值和最大值 64MB
(-XX:NewSize 设置新域初始值 )
(-XX:MaxNewsize 设置新域最大值 )
java -Xms256m -Xmx256m -XX:NewSize 64m -XX:MaxNewsize=64m
设置了新域的大小,旧域大小即为堆的大小减去新域的大小,不需要对旧域进行设置,JVM也没有提供对旧域设置属性。
配置永久域(非堆内存)
永久域默认大小为4MB,运行程序时,JVM会调整永久域大小以满足需要。
为了避免调整,可使用-XX:PermSize设置初始值,使用-XX:MaxPermSize设置最大值
java -Xms512m -Xmx512m -Xmn128m -XX:PermSize=32m -XX:MaxPermSize=64m
配置新域子空间
默认状态,JVM在新域使用复制收集器,对旧域使用标记-清除-压缩收集器。
在新域
JVM内存区域配置的实战案例: remark002_eclipse内存溢出崩溃_服务器JVM崩溃.txt
5. JVM性能调优
调优配置参考
JVM的堆大小决定了JVM花费在收集垃圾上的时间和频度。
应用中建立和释放对象的速度决定了垃圾收集的频度。
编程中,注意使用对象的缓存而不是建立对象。
对生存时间越长,需要收集时间也越长,收集也会变慢。
一次完全的垃圾收集应该不超过3-5秒。
如果系统花费很多时间收集垃圾,应该减少堆大小。
一般来说,你使用物理内存的80%为堆大小。
对于1GB内存,单CPU的机器来说,如下的一组参考配置
-Xms800m -Xmx800m // 堆初始值和堆最大值一样
-Xmn200m // 新域的内存
-XX:PermSize=128m // 永久域初始值
-XX:MaxPermSize=128m // 永久域最大值
-XX:NewSize=200m // 新域的初始值
-XX:MaxNewSize=200m // 新域最大值
-XX:NewRatio=3 // 设置该值后可不设置NewSize
-XX:SurvivorRatio=4 // 设置救助区域大小
-XX:userParNewGC // 可用来设置并行收集
-XX:ParallelGCThreads // 可用来增加并行度
-XX:UseParallelGC // 设置后可以使用并行清除收集器
-XX:UseAdaptiveSizePolicy // 上面一个联合使用效果更好,利用它可以自动优化新域大小和救助空间比值
我们经常遇到java.lang.OutOfMemoryError. Java heap space的错误
根源是JVM虚拟机默认Heap大小是64MB,可通过设置最小值和最大值来解决
(1) windows中更改系统环境变量
加上 JAVA_OPTS=-Xm800m -Xmx800m
(2) tomcat的话,可以在\bin\catalina.bat或catalina.sh加上
set JAVA_OPTS=-Xm800m -Xmx800m
JVM调优实战
heap内存设置
进入JAVA_HOME/demo/jfc/SwingSet2目录
java -jar -Xmn4m -Xms16m -Xmx16m SwingSet2.jar
出现如下错误: Exception in thread "Image Fetcher 1" java.lang.OutOfMemoryError: Java heap space
除了异常信息,会发现程序的响应速度变慢。
说明Heap size偏小,GC占用了更多时间,应该分配到的执行时间较少。
设置成如下,则安然无恙:
java -jar -Xmn4m -Xms16m -Xmx32m SwingSet2.jar
Heap最大不要超过可用物理内存的80%,一般要将-Xms和-Xmx选项设置为相同,
而-Xmn为1/4的-Xmx(heap最大值)