JVM内存模型及垃圾收集算法
1.根据Java虚拟机规范,JVM将内存划分为:
- New(年轻代),Tenured(年老代),永久代(Perm)
- 其中New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小
- 年轻代(New):年轻代用来存放JVM刚分配的Java对象
- 年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
- 永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。
New(年轻代)又分为几个部分:
- Eden:Eden用来存放JVM刚分配的对象
- Survivor1,Survivro2两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured(年老代)。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。
垃圾回收算法
- 垃圾回收算法可以分为三类,都基于标记-清除(复制)算法:
- Serial算法(单线程) 并行算法 并发算法 JVM会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于1个核,会对年轻代选择并行算法
- 并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不停止应用执行。所以,并发算法适用于交互性高的一些程序。经过观察,并发算法会减少年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低。
垃圾回收动作何时执行?
- 当年轻代内存满时,会引发一次普通GC,该GC仅回收年轻代。需要强调的时,年轻代满是指Eden代满,Survivor满不会引发GC
- 当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代
- 当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载
何时会抛出OutOfMemoryException
- 并不是内存被耗空的时候才抛出,JVM98%的时间都花费在内存回收 每次回收的内存小于2% 满足这两个条件将触发OutOfMemoryException
- 这将会留给系统一个微小的间隙以做一些Down之前的操作,比如手动打印Heap Dump。
内存泄漏及解决方法
系统崩溃前的一些现象:
每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s
FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
年老代的内存越来越大并且每次FullGC后年老代没有内存被释放
之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值。
生成堆的dump文件
通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。
1
2
3
4列出当前所有运行的 java 进程:jcmd -l
列出当前运行的 java 进程可以执行的操作:jcmd PID help
#生成dump
jmap -dump:live,format=b,file=e:/heap.hprof 10444分析dump文件
下面要考虑的是如何打开这个3G的堆信息文件,显然一般的Window系统没有这么大的内存,必须借助高配置的Linux。当然我们可以借助X-Window把Linux上的图形导入到Window。我们考虑用下面几种工具打开该文件:
1 | Visual VM |
为什么崩溃前垃圾回收的时间越来越长?
根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判断内存泄漏的依据
为什么Full GC的次数越来越多?
因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收
为什么年老代占用的内存越来越大?
因为年轻代的内存无法被回收,越来越多地被Copy到年老代
评论