目录
JVM内存模型
内存模型与运行时数据区
运行时数据区其重点存储数据的是堆和方法区,所以内存的设计也着重于这两方面展开(注:这两块区域是线程共享的)
图解
一块是非堆区,一块是堆区。
堆区分为两大块,一个是
Old
区,一个是
Young
区。
Young
区分为两大块,一个是
Survivor
区(
S0+S1
),一块是
Eden
区。
Eden:S0:S1=8:1:1,
S0
和
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
。
根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对
一个进程内的线程数还是有限制的,不能无限生成。
线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在该线程内有递归、大的循环时出现溢
出的可能性更大,如果该值设置过大,就有影响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的
错误。