Java内存模型以及垃圾回收算法

目录

1.内存区域划分【物理】

2.垃圾回收

3.Java中volatile的作用


1.内存区域划分【物理】

线程私有区:

1)程序计数器:可看作是当前线程所执行字节码的行号指示器

2)虚拟机栈【-Xss:设置栈的深度】

----虚拟机栈描述的是Java方法执行的内存模型,每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。声明周期与线程相同。

局部变量表:存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间完全确定的,在执行期间不会改变局部变量表大小。

虚拟机栈会产生两种异常:

a.如果线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverFlowError异常

b.虚拟机在动态扩展时无法申请到足够的内存,会抛出OOM

   3)本地方法栈

与虚拟机栈不同的是虚拟机栈为JVM执行的Java方法服务,而本地方法栈为虚拟机使用的Native方法使用

线程共享区:

  1. Java堆:数组元素和对象【-Xms:设置堆的最小值;-Xmx:设置堆的最大值;-Xmn:设置新生代内存大小】
  2. 方法区:已加载的类信息、常量与及静态变量
  3. 运行时常量池:方法区的一部分,符号引用与字面量

 

2.垃圾回收

如何判断对象是否存活?à唤醒阶段(Object类的finalize())à如何进行垃圾回收(GC算法)

2.1判断对象是否存活

(1)引用计数【无法解决对象循环引用问题】

----给对象增加一个计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是不能再被使用的,即对象已“死”。

(2)可达性分析

哪些对象可以作为GC Roots

(1)本地方法栈、虚拟机栈中中引用的对象;

(2)类中的静态变量与常量

**JDK1.2关于引用的扩充**[如下表所示]

强引用

程序中普遍存在的例,只要对象被任意一个强引用指向,无论是否发生内存溢出,都不能回收被强引用指向的对象

 

软引用

SoftReference描述软引用,若对象只被软引用指向,当内存够用时不回收此对象,当内存不够用时,会回收掉所有被软引用指向的对象,软引用对象为有用但非必需对象,(如缓存对象)

 

弱引用

弱引用也是用来描述非必需对象的。但是它的强度要弱于软引用,被弱引用关联的对象只能生存到下一次垃圾回收发生之前,当垃圾回收器开始进行工作时,无论当前内容是否够用,都会回收掉只被弱引用关联的对象。使用WeakeReference类来实现弱引用。

 

虚引用

虚引用也被称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。

 

 

:即使在可达性分析算法中不可达的对象,也并非“非死不可”的,这时候它们暂时处在“缓刑阶段”。要宣告一个对象的真正死亡,至少要经历两次标记过程:如果对象在进行可达性分析之后发现没有与GCRoots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者是已经被JVM调用过,虚拟机会将这两种情况都视为“没有必要执行”,此时的对象才是真正“死对象”

          如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个叫做F-Queue的队列之中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它(这里所说的执行指的是虚拟机会触发finalize()方法)fianalize()方法是对象逃脱死亡的最后一次次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象在finalize()中成功拯救自己(只需要重新与引用链上的任何一个对象建立起关联关系即可),那在第二次标记时它将会被移出“即将回收”的集合;如果对象这时候还是没有逃脱,基本上它就是真的被回收了。

2.2垃圾回收算法

(1)标记-清除算法【最基础的收集算法】

          a.标记:标记处所有需要回收的对象

          b.清除:在标记完成之后统一回收所有被被标记的对象

          不足:

a.效率问题:标记和清除这两个过程的效率都不高

b.空间问题:标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致程序运行中需要大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。

(2)复制算法:

        将可用内存划分为大小相等的两块,每次只使用其中的一块,当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。

        新生代中98%的对象都是“朝生夕死的”,所以并不需要按照1:1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者空间),每次使用Eden和其中一块Survivor(两个survivor区域一个称为From区,一个称为To区域)。当回收时,将Eden和Survivor中还存活的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间

        HotSpot实现的复制算法流程:

  1. 当Eden区满的时候,会触发第一次Minor GC,把还活着的对象拷贝到Survivor From区;当Eden区再次触发Minor GC,会扫描Eden区和From区域,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接赋值到To区域,并将Eden和From区域清空。
  2. 当后续Eden又发生Minor GC的时候,会对Eden和To区域进行垃圾回收,存活的对象复制到From区域,并将Eden和To区域清空。
  3. 部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenURingThreshold)决定,这个参数默认值是15,最终如果还是存活,就存入老年代。

(3)标记整理算法(老年代回收算法)

                 与“标记-清除”过程一致,但后续步骤不回收直接对可回收对象进行清理,而是让所有存活对象都想一端移动,然后直接清理掉。

当前JVM垃圾收集都采用的是“分代收集”算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代中,每次回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而在老年代中独享存活率较高。没有额外空间对它进行分配担保,就必须采用“标记-清除”或者“标记整理算法”

**了解Minor GC和Full GC么,这两种GC有什么不一样吗?

Minnor GC又称为新生代GC:指的是发生在新生代的垃圾收集。因为Java对象大多都具备招生夕灭的特性,因此Minor GC(复制算法)非常频繁,一般回收速度也比较快

Full GC又成为老年代GC或者Major GC:只发生在老年代的垃圾收集。出现了Major GC,经常会伴随至少一次的Minor GC(并非绝对,在Parallel Scavenge收集器中就有直接进行Full GC 的策略选择过程)Major GC的速度一般会比Minor GC慢10倍以上

3.Javavolatile的作用

  1. 保证被volatile变量修饰的内存可见性:当一条线程修改了这个变量的值,新增对于其他线程来说是可以立即得知的。
  2. 禁止指令重排;
  3. 当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经执行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行;
  4. 在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。

你可能感兴趣的:(java)