JVM之内存

2020-04-16  本文已影响0人  囧略囧

1、JVM内存区域

image.png

方法区

  所有线程共享
所有对象实例及数组都需要在堆上分配(随着栈上分配、标量替换等,也不那么绝对了)
  可以处于物理上不连续的内存空间,当堆中没有内存用于实例分配,堆也无法再扩展时,抛出OutOfMemoryError异常。

虚拟机栈

  为虚拟机执行Java方法(也就是字节码)服务
  线程私有

本地方法栈

  为虚拟机执行Native方法服务
  线程私有

程序计数器

  当前线程所执行的字节码行号指示器

是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域

2、对象的创建

采用哪种方式由Java堆是否规整决定
Java堆是否规整由垃圾收集器是否带压缩整理功能决定

对象创建非常频繁,存在并发问题:

2、对象的访问

内存溢出:(out of memory)通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。

内存泄漏:(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

如果是建立过多线程导致的内存溢出,在不能减少线程数或更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

3、 GC算法

程序计数器、虚拟机栈、本地方法栈不需要过多考虑回收的问题

仅需关注Java堆和方法区

3.1对象存活还是死亡

3.1.1引用类型

https://www.jianshu.com/p/825cca41d962

3.1.2对象死亡

对象真正死亡至少要经历两次标记过程

  1. 对象在可达性分析后没有与GC Roots存在引用链,会被第一次标记。

2.有必要执行finalize()方法的对象会被放入F-Queue队列中,稍后被虚拟机建立的低优先级的Finalize线程执行。(虚拟机仅保证调用finalize方法,但不保证等待它执行结束,避免执行缓慢、死循环等)。
虚拟机会对F-Queue队列中的对象进行第二次标记,如果对象在finalize方法中与引用链上的任何一个对象建立了关联,则被移出待回收集合。

如果这之后对象仍然在待回收集合,则对象已死亡,等待垃圾回收。

任何对象的finalize方法仅会被调用一次,之后再也不会被调用

方法区(永久代)的垃圾回收主要回收:废弃常量+无用的类
废弃常量与堆中的对象类似,没有被引用则清出常量池
无用的类必须满足:

4、 GC算法

4.1标记-清除算法

标记全部需回收对象,在标记完成后统一回收所有被标记的对象。

4.2复制算法(用于新生代)

将可用内存分为大小相等的两块,每次只使用一块,GC时将存活的对象复制到另一块。全部清除原来那半。
*空间问题:
内存浪费,代价太高

由于新生代中98%的对象都是“朝生夕死”,并不需要1:1划分内存空间。
采用1块较大的Eden空间和2块较小的Survivor空间。
每次使用1块Eden和1块Survivor空间,清理时将存活对象复制到1块Survivor空间。全部清除原来的1块Eden和1块Survivor空间。

当Survivor空间不够用时,需要老年代进行分配担保。

4.3标记-整理算法(用于老年代)

标记全部需回收对象,将所有存活对象向一端移动。然后直接清理掉端边界以外的内存。

4.4分代收集算法

新生代采用复制算法,老年代采用标记-清除或标记-整理算法。

5、 垃圾收集器

5.1Serial收集器

新生代单线程复制算法,暂停所有工作线程
老年代单线程标记-整理算法,暂停所有工作线程

单CPU环境下,简单而高效

5.2 ParNew收集器

新生代多线程复制算法,暂停所有工作线程
老年代单线程标记-整理算法,暂停所有工作线程

仅Serial和ParNew收集器可以和CMS配合工作
因为Parallel Scavenge和G1都没有采用传统的GC收集器代码框架

5.3 Parallel Scavenge收集器(新生代收集器)

新生代多线程复制算法

Parallel Scavenge收集器的目的是达到一个可控制的吞吐量。(CPU运行用户代码时间/总时间)

可设置两个参数控制吞吐量:

需注意,GC停顿时间牺牲了吞吐量新生代空间
收集300M的新生代肯定比500M要快,但之前10秒收集一次每次100ms,现在5秒一次,一次70ms,吞吐量也相应下降。

5.4 Serial Old收集器(老年代收集器)

5.5 Parallel Old收集器(老年代收集器)

在注重吞吐量和CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+ Parallel Old的组合。

5.6 CMS收集器

*是一种以获取最短回收停顿时间为目标的收集器

1.初始标记,stop the world
2.并发标记
3.重新标记,stop the world
4.并发清除

5.7 G1收集器

1.初始标记
2.并发标记
3.最终标记
4.筛选回收

G1将Java堆划分为多个大小相等的区域。新生代老年代不再物理隔离。
在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

6、 内存分配

6.1 对象优先在Eden区分配

Eden区没有足够空间时,出发Minor GC
(新生代Minor GC
老年代Major GC / Full GC,一般比Minor GC慢10倍以上)

6.2 大对象直接进入老年代

常见的很长的字符串和数组

6.3 长期存活的对象进入老年代

对象在Eden区出生并经历一次Minor GC后仍能被Survivor容纳的话,记为1岁,移动代Survivor中。
每经历一次Minor GC就增加1岁。
当年龄增加到一定程度(默认15岁),就会被晋升到老年代中。

6.4 动态对象年龄判断

如果在Survivor空间中相同年龄所有对象大小总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象就直接进入老年代,无需等到要求的年龄。

6.5 空间分配担保

在Minor GC前,检查老年代最大可用连续空间是否大于新生代空间总和。若大于则Minor GC可以安全进行。否则,则查看是否允许担保失败。若允许担保失败,则查看老年代最大可用连续空间是否大于历次新生代晋升到老年代的平均大小。如果大于则尝试进行Minor GC(虽然有风险),否则进行Full GC。

SafePoint

由于 Full GC(或Minor GC) 会影响性能,所以我们要在一个合适的时间点发起 GC,这个时间点被称为 Safe Point,这个时间点的选定既不能太少以让 GC 时间太长导致程序过长时间卡顿,也不能过于频繁以至于过分增大运行时的负荷。一般当线程在这个时间点上状态是可以确定的,如确定 GC Root 的信息等,可以使 JVM 开始安全地 GC。Safe Point 主要指的是以下特定位置:

循环的末尾
方法返回前
调用方法的 call 之后
抛出异常的位置 另外需要注意的是由于新生代的特点(大部分对象经过 Minor GC后会消亡), Minor GC 用的是复制算法,而在老生代由于对象比较多,占用的空间较大,使用复制算法会有较大开销(复制算法在对象存活率较高时要进行多次复制操作,同时浪费一半空间)所以根据老生代特点,在老年代进行的 GC 一般采用的是标记整理法来进行回收。

image.png
上一篇下一篇

猜你喜欢

热点阅读