Java基础知识点总结系列(八)——Java内存管理与垃圾回收

一、基础知识储备

1. Java内存划分

Java内存包括五个部分:堆内存,方法区、栈内存、程序计数器、本地方法栈

1.1 堆内存 Heap(线程共享)

堆内存是Java内存区域最大的一块,所以也是GC垃圾回收的重点关照对象。Java中所有的对象实例都存储在堆内存上(即所有new的对象都在此区域分配内存),当堆内存空间不足时,抛出OutOfMemoryError 。
在分带收集算法中,Java堆区会被划分为新生代,老年代,持久代。其中新生代又被分为Eden区和Survior区,Survior区又包括FromSpace区(Survior0区)和ToSpace区(Survior1区),Eden区和两个Survior区大小比例为8:1:1。

1.2 方法区 Method Area(线程共享)

用于存放类的所有信息,属性、字段、方法等。运行时常量池也在此区域,运行时常量池:用于存储编译时生成的各种字面变量。空间不足时抛出OutOfMemoryError.

1.3 虚拟机栈 VM Stack(线程私有)

每个线程对应一个栈,线程执行时调用的方法都会生成一个栈帧,方法中局部变量都会存储在栈帧中,方法从调用到执行完毕并返回就是入栈和出栈的过程。空间不足时抛出OutOfMemoryError和StackOverFlow,具体来说,当程序新创建一个线程,JVM为其创建一个栈时,如果当前虚拟机栈空间不足,则抛出OutOfMemory异常;如果创建栈之后,由于线程本身调用层级过多(比如递归调用某个函数)进而引起该线程中栈帧总数超过-Xss设定的最大值,则引发StackOverFlow异常。

1.4 本地方法栈 Native Method Stack(线程私有)

与虚拟机栈类似,区别在于其只适用于Native方法。

1.5 程序计数器(线程私有)

与每个线程关联,每创建一个线程,系统都将为其创建一个程序计数器,用来存储当前线程中下一条指令的位置,以便在重新切回当前线程时能继续执行。

2. Java中对象的生命周期

二、Java垃圾回收机制

1. 垃圾判定机制

JVM在判定一个对象是否为垃圾时,主要有两种方法,分别是引用计数法和根路径搜索法。

1.1. 引用计数法

每个对象在创建时,同步保存一个指向该对象的引用数,当一个对象引用数为0时,即将一个对象判定为垃圾。(图中对象D的引用计数即为0,会被判定为垃圾对象)
在这里插入图片描述

缺点:无法解决循环引用的问题(如下图所示,对象BCD存在相互引用,故引用计数均不为0,所以BCD在引用计数法下都不会被判定为垃圾对象,但实际情况下BCD都是垃圾对象)

在这里插入图片描述

1.2. 根路径搜索法

将一些对象设置为根对象(GC Roots),从根对象出发,沿着根对象的引用路线遍历对象,如果一个对象没有任何从根对象出发的引用链能到达它,则此对象会被判定为垃圾。根路径搜索法不会出现循环引用无法回收的问题,所以也是现代垃圾回收算法所采用的主流方式。
GC Roots包括:
① 虚拟机栈中引用的对象;
② 方法区静态变量引用的对象;
③ 方法区常量区引用的对象;
④ 本地方法栈引用的对象;

2. 垃圾回收算法

2.1. 标记清除算法

算法分为两个阶段,在标记阶段,JVM会对当前所有对象的引用链进行检查,将所有能从GC Roots到达的对象进行标记;在清除阶段,JVM会对所有未被标记的对象进行清除。注意在标记阶段会停止程序执行,因为如果不停止程序执行,当有一个新的对象在这个阶段被创建时,即便存在引用链,由于没有被标记,也会在清除阶段被清除。

缺点:① 由于需要递归,所以执行效率较低; ②由于清除的对象位置随机,所以会存在大量内存碎片;③ 标记阶段会将整个程序挂起(不挂起的话,程序执行时新创建的对象由于未被标记,故在回收阶段都会被回收);
2.2. 标记压缩/整理算法

是对标记清除算法的一个优化,阶段一的标记与标记清除算法相同;在阶段二,将所有被标记的对象整理到堆的一端,然后直接清理掉除了端内存的其他区域。

优缺点:解决了标记清除算法本身内存碎片的问题,但在标记阶段依然会将整个程序挂起(原因同上)
2.3. 复制算法

将内存区域划分为大小相等的两块,每次使用其中的一块,当其中一块满了后,将所有存活的对象复制到另一块,同时清空当前这块内存。

优缺点:效率很高,运行时不需要挂起整个程序;但当当前区域全部都是存活的对象时,就相当于只是一次位置交换,导致存在一半的空间浪费。
2.4. 分代收集算法

分代收集是根据不同代采用不同的垃圾回收算法:
在新生代,因为对象存活平均周期短,所以采用复制算法小效率高;
在年老代,采用标记/压缩算法;
具体过程为:
绝大部分对象都在Eden区创建(需要大量连续空间的对象会直接在老年代创建,比如大型数组),当Eden区满了后,会执行一次Minor GC,同时将当前存活的对象全部复制到Survior0区(From区),当Survior0区满了之后(可能经过不止一次Minor GC),会将Eden区和Survior0区所有存活对象都复制到Survior1区(To区),同时清空Eden区和Survior0区,接着会交换Survior0区和Survior1区,也就是JVM会一直保证Survior1区是空的。对象每在Survior区经历过一次Minor GC年龄便会增长一岁,默认当对象年龄增长到15岁时就会进入Tenured Generation(老年代)。当Tenured Generation满了之后会进行Full GC。当Survoir区中相同年龄的对象所占空间超过Survior总空间的一半时,大于这个年龄的对象直接进入Tenured Generation。

3.垃圾回收器

3.1 串行垃圾回收器

即采用单线程模式对垃圾进行判定和回收,适用于单CPU、单线程小型应用。

3.2 并行垃圾回收器

采用多线程判定并回收垃圾,是JVM默认采用的垃圾回收器。

4. GC调优

4.1 GC调优的思路

由于老年代采用了标记整理算法,而标记整理算法在执行过程中(标记阶段)会将整个程序挂起,所以会比较影响程序执行的性能;而新生代由于采用了复制算法,在算法执行过程中并不会挂起整个程序,相对来说对程序执行影响很小。所以GC调优的整体思路就是通过配置堆内存及代码尽可能减少Full GC(老年代满之后的GC)的调用。

4.2 GC调优的措施

① 不用的对象显式的置为null;
显式置为null的对象会被直接判定为垃圾,从而节省GC算法标记的时间。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值