JVM学习(3)-- JVM内存模型

目录

JVM内存模型

内存模型与运行时数据区

图解

对象创建所在的区域

survivor区详解

old区详解

对象生命周期图解

常见问题

如何理解Minor/Major/Full GC

为什么需要Survivor区?只有Eden不行吗?

为什么需要两个Survivor区

新生代中Eden:S1:S2为什么是8:1:1?

使用工具查看内存模型

方法取内存溢出


JVM内存模型

内存模型与运行时数据区

运行时数据区其重点存储数据的是堆和方法区,所以内存的设计也着重于这两方面展开(注:这两块区域是线程共享的)

图解

一块是非堆区,一块是堆区。
堆区分为两大块,一个是 Old 区,一个是 Young 区。
Young 区分为两大块,一个是 Survivor 区( S0+S1 ),一块是 Eden 区。 Eden:S0:S1=8:1:1S0 S1 一样大,也可以叫 From To

对象创建所在的区域

一般情况下,新创建的对象都会被分配到Eden区,一些特殊的大的对象会直接分配到Old区。

比如有对象 A B C 等创建在 Eden 区,但是 Eden 区的内存空间肯定有限,比如有 100M ,假如已 经使用了 100M 或者达到一个设定的临界值,这时候就需要对 Eden 内存空间进行清理,即垃圾收 (Garbage Collect) ,这样的 GC 我们称之为 Minor GC Minor GC 指得是 Young 区的 GC 经过 GC 之后,有些对象就会被清理掉,有些对象可能还存活着,对于存活着的对象需要将其复制 Survivor 区,然后再清空 Eden 区中的这些对象。
 

survivor区详解

   由图解可以看出,Survivor区分为两块S0和 S1 ,也可以叫做 From To 在同一个时间点上, S0 S1 只能有一个区有数据,另外一个是空的。
接着上面的 GC 来说,比如一开始只有 Eden 区和 From 中有对象, To 中是空的。 此时进行一次 GC 操作, From 区中对象的年龄就会 +1 ,我们知道 Eden 区中所有存活的对象会被复 制到 To 区, From 区中还能存活的对象会有两个去处。 若对象年龄达到之前设置好的年龄阈值,此时对象会被移动到 Old 区,没有达到阈值的对象会被复 制到 To 区。 此时 Eden 区和 From 区已经被清空 ( GC 的对象肯定没了,没有被 GC 的对象都有了各自的去处 ) 这时候 From To 交换角色,之前的 From 变成了 To ,之前的 To 变成了 From 也就是说无论如何都要保证名为 To Survivor 区域是空的。 Minor GC 会一直重复这样的过程,直到 To 区被填满,然后会将所有对象复制到老年代中

old区详解

从上面的分析可以看出,一般 Old 区都是年龄比较大的对象,或者相对超过了某个阈值的对象。 Old 区也会有 GC 的操作, Old 区的 GC 我们称作为 Major GC

对象生命周期图解

常见问题

如何理解Minor/Major/Full GC

MinorGC: 新生代
MajorGC: 老年代
FullGC: 新生代 + 老年代

为什么需要Survivor?只有Eden不行吗?

如果没有 Survivor,Eden 区每进行一次 MinorGC, 并且没有年龄限制的条件下,存活的对象就会被送到老年 代。 这样一来,老年代很快被填满 , 触发 MajorGC( 因为 MajorGC 一般伴随着 MinorGC, 也可以看做触发了 FullGC) 老年代的内存空间远大于新生代 , 进行一次 FullGC 消耗的时间比 MinorGC 长得多。 执行时间长有什么坏处 ? 频发的 FullGC 消耗的时间很长 , 会影响大型程序的执行和响应速度。
 
假如增加老年代空间,更多存活对象才能填满老年代。虽然降低 FullGC 频率,但是随着老年代空间加大 , 旦发生 FullGC, 执行所需要的时间更长。 假如减少老年代空间,虽然 FullGC 所需时间减少,但是老年代很快被存活对象填满 ,FullGC 频率增加。
 
所以 Survivor 的存在意义 , 就是减少被送到老年代的对象 , 进而减少 FullGC 的发生 ,Survivor 的预筛选保 , 只有经历 16 MinorGC 还能在新生代中存活的对象 , 才会被送到老年代。

为什么需要两个Survivor区

假设现在只有一个 Survivor , 我们来模拟一下流程 :
刚刚新建的对象在 Eden , 一旦 Eden 满了 , 触发一次 MinorGC,Eden 中的存活对象就会被移动到 Survivor 区。这样继续循环下去 , 下一次 Eden 满了的时候 , 问题来了 , 此时进行 MinorGC,Eden Survivor 各有一些 存活对象 , 如果此时把 Eden 区的存活对象硬放到 Survivor , 很明显这两部分对象所占有的内存是不连续的 , 也就导致了内存碎片化。 两个survivor区永远有一个 Survivorspace 是空的 , 另一个非空的 Survivorspace 无碎片。

新生代中Eden:S1:S2为什么是8:1:1

新生代中的可用内存:复制算法用来担保的内存为 9 1可用内存中 Eden S1 区为 8 1即新生代中 Eden:S1:S2=8 1 1

使用工具查看内存模型

使用jvisualvmJDK自带,还有很多自带工具,可以看这一篇工具

 

方法取内存溢出

StackSpace 用来做方法的递归调用时压入 StackFrame( 栈帧 ) 。所以当递归调用太深的时候,就有可能耗 StackSpace ,爆出 StackOverflow 的错误。
-Xss128k :设置每个线程的堆栈大小。 JDK5 以后每个线程堆栈大小为 1M ,以前每个线程堆栈大小为 256K 根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对 一个进程内的线程数还是有限制的,不能无限生成。
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢 出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的 错误。
  

猜你喜欢

转载自blog.csdn.net/baidu_41922630/article/details/104841768