美文网首页
虚拟机JVM运行时区域及垃圾回收

虚拟机JVM运行时区域及垃圾回收

作者: Yanci516 | 来源:发表于2016-08-19 11:04 被阅读226次

该系列文章主要是记录下自己暑假这段时间的学习笔记,暑期也在实习,抽空学了很多,每个方面的知识我都会另起一篇博客去记录,每篇头部主要是另起博客的链接。

JavaSE集合(已写)

JavaEE框架(未写)

虚拟机JVM运行时区域及垃圾回收(已写)

Java并发(未写)

计算机网络(已写)

八大经典排序算法原理及实现(已写)


一、JVM 运行时数据区域

JVM运行时数据区域可以分为:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区(包含运行时常量池)

1、程序计数器:

可看做是当前线程所执行的字节码的行号指示器

  • 线程私有

2、Java 虚拟机栈

描述的是Java方法执行的的内存模型,每一个方法执行的同时都会创建一个栈帧栈帧详解)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行到结束,都对应着一个栈帧入栈到出栈

  • 局部表量表:存放了编译器可知的各种基本数据类型、对象引用
  • 线程私有
  • 如果线程请求的栈深度大于虚拟机所允许的深度会抛出StackOverflowError异常
  • 如果虚拟机栈动态扩展时无法申请到足够的内存,会抛出OutOfMemoryError错误

3、本地方法栈

与Java虚拟机栈的发挥的作用差不多,区别在于Java虚拟机栈是为Java方法服务、本地方法栈是为本地(Native)方法服务

  • 线程私有
  • 如果线程请求的栈深度大于虚拟机所允许的深度会抛出StackOverflowError异常
  • 如果虚拟机栈动态扩展时无法申请到足够的内存,会抛出OutOfMemoryError错误

4、Java 堆

堆在虚拟机启动时创建,唯一的目的就是存放几乎所有的对象实例。

  • 线程共享
  • 堆是垃圾收集的主要区域,常被称为GC堆(Garbage Collected Heap)
  • 可分为新生代、老年代
  • 再细致可以分为Eden、From Survivor、To Survivor空间
  • 堆中没有内存完成实例分配,并且堆也无法扩展时,抛出OutOfMemoryError

5、方法区

存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据

  • 线程共享
  • 当方法区无法满足内存分配需求时,将抛出OutOfMemooryError

6、运行时常量池

是方法区的一部分.Class文件除了有类的版本、字段、方法、接口等描述信息外;还有一项常量池,用于存放编译期生成的各种字面量和符号引用??,这些内容再类被加载后进入方法区的运行常量池中存放。

  • 运行时常量池相对于Class文件常量池具有动态性,Java语言不要求常量一定只有编译期才产生,String类的intern()方法可以在运行期间将常量插入运行时常量池
  • 当常量池无法再申请到内存时,会抛出OutOfMemoryError

7、直接内存

并不是虚拟机运行时数据的一部分,也不是Java虚拟机规范中定义的内存区域,但是频繁的使用也会造成OutOfMemooryError

二、对象的创建

大家都知道Java是一门面向对象的编程语言,在Java程序运行过程中无时无刻都有对象被创建。语言层面上有通过 new方式 来创建、也有通过反射机制方式创建。在JVM中是怎么创建的呢?

  1. 类加载检查:当创建对象指令下达后,先检查是否能在常量池中定位到该类的符号引用,并且检查这个类是否已经被加载、解析、和初始化过。若没有,那必须先执行类加载
  2. 为新生对象分配内存:对象所需分配的大小在类加载完成后便可完全确定,分配内存其相当于在堆中划分出一块相应的大小的内存。在为对象分配内存时,需要考虑线程安全的问题
  3. 将分配到的内存空间都初始化为0值(不包括对象头):保证了对象的实例字段在Java代码中可以不赋初值就直接使用
  4. 对对象进行必要的设置:比如对象是哪个类的实例,如何才能找到元数据信息,对象个哈希码、对象的GC分代信息等。这些信息都存储在对象头中
  5. 上述步骤之后,虚拟机层面上一个新对象已经产生,但从Java层面上对象的创建才开始。其 <init>方法还没有执行,所有的字段都还未0

三、对象的访问定位

建立对象都是为了使用它,在Java程序中,通过栈中的 reference 数据来操作堆上的具体对象

  • 句柄访问方式

  • 直接指针访问方式

四、JVM垃圾收集器与内存分配策略

在了解JVM垃圾回收之前,需要知道3个问题:

  1. 哪些内存需要释放?
  2. 什么时候释放?
  3. 如何回收?

首先要知道JVM运行时数据区域中程序计数器、Java虚拟机栈、本地方法都是随线程而生、线程而亡,即线程私有的。在这3个区域的内存分配和回收都具有确定性,在方法结束或线程结束时内存就自动回收了,因此不用考虑垃圾回收。
在Java堆中和方法区则不一样,一个接口的多个实现类需要的内存不一样,一个方法的多个分支需要的内存也不一样,只有在程序运行期间才能知道,这部分内存的分配和回收都是动态的。因此在这两个区域考虑垃圾回收。

判断对象是否存活

1.引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效后,计数器就减1;当计数器为0时的对象就是不可能再被使用的对象

  • 优势:实现简单、效率高
  • 劣势:难以解决对象之间相互循环引用的问题

2、可达性分析算法

通过一系列称为"GC Roots"对象作为起始点,从这些点向下搜索,搜索过程中走过的路径称为引用链,当一个对象到GC Roots没有任何的引用链相连接时,则证明该对象是不可用的

  • 克服引用计数算法无法解决对象之间相互循环引用的问题
  • GC Root 的对象包括:
    1. 虚拟机栈中引用的对象
    2. 方法区中类静态属性引用的对象
    3. 方法区中常量引用的对象
    4. 本地方法栈中JNI(Native方法)引用的对象

3、再次确认对象的存亡

经过上述任意一种算法,判断出不可用的对象并非是非死不可的。一个不可用的对象再第经过上述算法之后,将第一次标记并且进行筛选,筛选条件为该对象是否有必要执行finelize()方法。
当对象没有覆盖finelize()方法,或者finelize()方法已经被虚拟机调用过,JVM将这两种情况都视为没有必要执行

4、回收方法区

很多人认为JVM方法区(永久代)中是没有垃圾收集的,其实是有的,主要回收这两部分:废弃常量和无用的类

垃圾回收算法

1、标记-清除算法

如同其名字一样,算法分为标记清除两个阶段:首先标记所有需要回收的对象,
在统一回被标记的对象;其标记过程和判断对象的存活过程类似

  • 不足:
    1. 标记和清除两个过程效率都低;
    2. 标记清除后会产生大量不连续的内存碎片,空间碎片太多会导致后面分配大内存对象时,无法找到足够的连续内存而不得不提前出发一个垃圾收集动作

2、复制算法

将可用内存划分为大小相等的两块,每次只使用其中的一块;当这一块内存快使用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的那块内存空间一次清理掉。

  • 解决了标记清理的效率和内存碎片问题
  • 不足:将内存缩小为原来的一半,付出的代价太高
新生代中解决复制算法的不足:

研究表明新生代中的对象98%是“朝生夕死”的,所以并不需要按1:1比例来划分空间,而是将内存划分为一块较大的Eden空间和两块较小的Survivor空间,其比例为8:1:1,每次使用Eden空间和一块Survivor空间,即新生代中可用内存空间为90%。
若另一块Survivor空间没有足够空间存放上一次新生代中收集下来存活的对象,这些对象将会直接被分配到老年代

3、标记整理算法

  • 复制算法在对象存活率较高时就要进行较多复制操作,效率也会变低,更关键是会浪费空间

针对老年代的特点,提出标记-整理算法,其标记过程和标记清除算法中一样,而后续步骤则不一样,而是让所有存活的对象都向一端移动,然后直接清除掉该端边界以外的内存。

  • 标记-整理主要解决了内存碎片问题

分代收集算法

将JVM堆分为新生代老年代,这样可以根据各个年代的特点采用最合适的收集算法

  • 新生代中,每次垃圾收集时发现大批对象死去,只有少量对象存活,就选用复制算法,只需付出少量存活对象的复制成本就可以完成收集

  • 老年代中,对象的存活率很高,无额为的空间为其进行分配担保,就必须使用标记-清除标记-整理算法

简单总结:

  • 两个最基本的java回收算法:复制算法和标记清理算法
    • 复制算法:两个区域A和B,初始对象在A,继续存活的对象被转移到B。此为新生代最常用的算法
    • 标记清理:一块区域,标记要回收的对象,然后回收,一定会出现碎片,那么引出
    • 标记-整理算法:多了碎片整理,整理出更大的内存放更大的对象
  • 两个概念:新生代和年老代
    • 新生代:初始对象,生命周期短的
    • 永久代:长时间存在的对象
  • 整个java的垃圾回收是新生代和年老代的协作,这种叫做分代回收

垃圾收集器

  • **Serial **收集器是针对新生代的收集器,采用的是复制算法

  • Parallel New(并行)收集器,新生代采用复制算法,老年代采用标记整理

  • Parallel Scavenge(并行)收集器,针对新生代,采用复制收集算法

  • Serial Old(串行)收集器,新生代采用复制,老年代采用标记清理

  • Parallel Old(并行)收集器,针对老年代,标记整理

  • CMS收集器,针对老年代,基于标记清理

  • G1收集器,整体上是基于标记清理,局部采用复制

  • 综上:新生代基本采用复制算法,老年代采用标记整理算法。cms采用标记清理。

JVM内存分配参数

  • Xmx:最大堆大小
  • Xms:初始堆大小,即最小内存值
  • Xmn:年轻带大小
  • XXSurvivorRatio:年轻带中 Eden:FromSurvivor:ToSurvivor=3:1:1

相关文章

网友评论

      本文标题:虚拟机JVM运行时区域及垃圾回收

      本文链接:https://www.haomeiwen.com/subject/acdbsttx.html