最近参考各种资料,尤其是《深入理解Java虚拟机 JVM高级特性和最佳实践》,大牛之作。把最近学习的Java虚拟机组成和垃圾回收机制总结一下。

你不会的都是新知识,学无止境,每天进步一点点。

一、认识Java虚拟机

在开始学Java之时,必做的一件事就是从Java官网下载并安装Java到我们的电脑之上,然后从HelloWorld开始走上编程的不归路。

上图中下载的Java安装包全称是Java SE Development Kit(单词依次翻译:Java 标准版本 开发 工具包),简称JDK,也就是供程序员使用的Java开发工具包。另外,JDK一般都是和它的2个小弟一起出现的,一个是JRE,一个是JVM,它们的关系如下图所示:

JDK=JRE+Java编译器、开发工具和更多的类库

简单来说JDK支持Java程序的开发。

JRE是Java Runtime Environment的简称

JRE=Java虚拟机+Java基础类库

是Java程序运行所需要的软件环境,简单的来说JRE支持Java程序的运行。图片中也提到了,JRE只支持java字节码的运行,是没办法把Java代码编译成.class文件的。

最内部也是最核心的就是Java虚拟机了,那么问题来了,什么是Java虚拟机?

Java虚拟机实际上是一种规范,是一种抽象机器,依附在真实的操作系统之上,这个规范描述了一个指令集,一组寄存器,一个堆栈,一个“垃圾堆”,和一个方法区。一旦一个Java虚拟机在给定的平台上运行,任何Java程序都能在这个平台上运行。

二、JVM运行时数据区

编写好的java代码交给java虚拟机,java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,称为运行时数据区,如下图所示。

图中绿色标记的区域是每个线程私有的,也就是线程安全的。灰色标记的区域是所有线程共享的,非线程安全。这幅图中我们只需要关注运行时数据区中的5个部分。

2.1 程序计数器

程序计数器的功能:

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

.java文件编译成.class文件,最终交给jvm执行,.class文件也是要按逻辑找到行号执行的,程序计数器就是对当前线程应该执行哪一行字节码做指示的。假设线程A暂停,过一段时间后线程A继续执行,这时候线程A应该从哪个地方继续就是靠程序计数器来完成的。

每个线程一个程序计数器,不同线程之间的程序计数器互不影响。

2.2 虚拟机栈

虚拟机栈描述的是java方法执行的内存模型。

一个线程一个栈,一个方法一个栈帧。栈帧可以理解成栈中的一小块,一个栈中有多个栈帧。栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

如下,MethodStackTest类中有三个方法:methodA、methodB和methodC,methodA调用了methodB,methodB调用了methodC,在main方法中执行methodA。

代码及打印结果:

class MethodStackTest{
    public static void methodA(){

        methodB();
        System.out.println("Method A");
    }
    public static void methodB(){
        methodC();
        System.out.println("Method B");
    }
    public static void methodC(){
        System.out.println("Method C");
        //methodA();取消注释会出现栈内存溢出
    }
   public static void main(String[] args) {
      methodA();
   }
}
结果:

Method C
Method B
Method A

这个小例子有助于解释了栈的含义,A调用了B,B调用了C,只有C执行结束,B才会结束,最终B执行完成A才会结束。

另外,如果在C中再调用A的话就一直循环调用,超过虚拟机所允许的栈的深度以后就会抛出StackOverflowError异常,如果不能申请到足够的内存,就会抛出OutOfMemory异常。

2.3 本地方法栈

本地方法栈和虚拟机栈发挥的作用相似,本地方法栈为本地方法服务。

2.4 堆

Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。堆被划分成三个不同的区域:新生代 ( Young )、老年代 ( Old )和永久区。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。Java堆也是垃圾回收的主战场,也被称为GC堆。

堆大小=新生代+老年代+永久区

新生代=Eden+From Survivor+To Survivor

默认:Eden:From:To=8:1:1

另外新生代和老年代的比例可以通过参数设置,并不一定是1:1。

2.5 方法区

方法区也被称为永久区,里面有类信息、常量、静态变量等数据,别名为非堆,为各个线程所共享。

三、垃圾回收机制

3.1 什么是垃圾?

Java中的垃圾是指已经分配内存但不再有任何引用(不完全准确,后面会再说)的对象,垃圾回收(GC)就是自动清理这部分内存。

3.2 两种垃圾判断算法

3.2.1 引用计数法

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

这种算法的有点事实现简单,判定效率也高,但是主流虚拟机并没有采用,主要是对于循环引用的对象无法进行回收。

3.2.3 可达性分析法

主流的JVM回收垃圾主要采用可达性分析法,算法的核心思想是选定一系列GC ROOTS对象作为起始点,从节点开始向下搜索,搜索路径称为引用链。一个对象到GC ROOTS没有任何引用链证明对象不可用。

可作为GC ROOTS的对象:

  1. 虚拟机栈中引用的对象
  2. 方法区中静态属性引用的对象
  3. 方法区中常量引用的对象
  4. 本地方法中JNI引用的对象

3.3 四种垃圾回收算法

3.3.1 标记-清除法(Marked-Sweep)

标记清除算法是最基础的收集算法,其他收集算法都是基于这种思想。标记清除算法分为“标记”和“清除”两个阶段:首先标记出需要回收的对象,标记完成之后统一清除对象。

它的主要缺点:

  1. 标记和清除过程效率不高 。
  2. 标记清除之后会产生大量不连续的内存碎片。

3.3.2 复制算法(Copying)

它将可用内存容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完之后,就将还存活的对象复制到另外一块上面,然后在把已使用过的内存空间一次理掉。这样使得每次都是对其中的一块进行内存回收,不会产生碎片等情况,只要移动堆订的指针,按顺序分配内存即可,实现简单,运行高效。

主要缺点:内存缩小为原来的一半。

3.3.3 标记-整理法

标记操作和“标记-清除”算法一致,后续操作不只是直接清理对象,而是在清理无用对象完成后让所有存活的对象都向一端移动,并更新引用其对象的指针。

主要缺点:在标记-清除的基础上还需进行对象的移动,成本相对较高,好处则是不会产生内存碎片。

3.3.4 分代算法

分代算法就是根据堆内存中新生代和老年代对象的特点,不同的区域采用不同的垃圾回收算法。新生代大多数对象“朝生夕死”,每次都有大量对象被回收,因此采用复制算法,上图堆内存中FROM和TO就是为了复制算法而设计的。老年代对象存活率较高,没有额外空间进行分配担保,使用“标记-清理”或者“标记整理”算法。

3.4 七个垃圾收集器

七种作用于不同分代的垃圾收集器:



垃圾收集器这部分没有写的太详细,这里有一篇已经整理好的:http://www.jianshu.com/p/50d5c88b272d,其内容也是根据《深入理解Java虚拟机 JVM高级特性和最佳实践》整理的。

3.4.1 Serial收集器

Serial收集器是最基本、发展历史最悠久的单线程收集器,最大的特点是Stop The World,垃圾回收时要终止用户进程,等GC结束用户进程方可继续。

JVM参数-XX:+UseSerialGC

3.4.2 ParNew收集器



ParNew收集器是Serial收集器的多线程版本。

3.4.3 Parallel Scavenge收集器

Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。

3.4.4 Serial Old收集器

Serial Old是Serial收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法。

3.4.5 Parallel Old收集器

Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。

3.4.6 CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。

3.4.7 G1收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。

3.4 GC范围

按范围分,GC有Minor GC、Major GC和Full GC。

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,从老年代回收内存被称为Major GC,同时回收新生代和老年代称为Full GC。

四、内存分配策略

4.1 对象优先分配在Eden区

对象优先在新生代Eden中分配,Eden中没有足够空间进行分配时,虚拟机发起一次Minor GC。

4.2 大对象直接进入老年代

大对象是指需要大量连续内存空间的Java对象,典型的是长字符串和数组,这种大对象会被分配到老年代之中。

4.3 老不死的对象进入老年代

新生代中的对象经过不断GC仍然存活,达到一定的“年龄”就会进入老年代,每经历一次Minor GC,年龄+1,达到15时(默认)就会进入老年代。晋升老年代的年龄阈值可以通过-XX:MaxTenuringThreshold参数指定。

4.4 动态对象年龄判定

JVM也不是严格执行4.3中的年龄要求,Survivor空间相同年龄所有对象大小的总和大于等于Suivivor空间的一半,年龄大于或等于该年龄的对象就会直接进入老年代,无需达到XX:MaxTenuringThreshold的要求年龄。

4.5 空间分配担保

Minor GC之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果是,GC是确保安全的。JVM继续检查老年代最大的可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则正常进行一次YGC,尽管有风险(因为判断的是平均大小,有可能这次的晋升对象比平均值大很多);

如果小于,或者HandlePromotionFailure设置不允许空间分配担保,这时要进行一次Full GC。

五、小结

JVM这一部分初步整理这么多,欢迎批评指正。

JVM运行时数据区和垃圾回收机制的更多相关文章

  1. Java内存管理:Java内存区域 JVM运行时数据区

    转自:https://blog.csdn.net/tjiyu/article/details/53915869 下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些, ...

  2. JVM 运行时数据区 (三)

    JVM运行时数据区 运行时数据区由 程序计数器.java虚拟机栈.本地方法栈.堆.方法区 组成: 1.程序计数器 每一个Java线程都有一个程序计数器,用于保存程序执行到当前方法的哪一个指令,它是线程 ...

  3. JVM总结(一):概述--JVM运行时数据区

    大三下,趁着寒假重温一遍JVM,准备在一个系列来总价一下学习JVM的整个过程.争取在接下来的一个星期内更新完这一个系列,然后回家过年. JVM运行时数据区 线程私有的数据区 程序计数器 虚拟机栈 本地 ...

  4. Jvm运行时数据区

    一:运行时数据区 Java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域.这些区域有着各自的用途,一级创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户 ...

  5. JVM运行时数据区及对象在内存中初始化的过程

    JVM运行时数据区 Java虚拟机所管理的内存区域,也称为运行时数据区,分为以下几个运行时数据区,如图所示 程序计数器:当前程序所执行字节码的行号指示器 程序计数器(Program Counter R ...

  6. JVM运行时数据区与JVM堆内存模型小结

    前提 JVM运行时数据区和JVM内存模型是两回事,JVM内存模型指的是JVM堆内存模型. 那JVM运行时数据区又是什么? 它包括:程序计数器.虚拟机栈.本地方法栈.方法区.堆. 来看看它们都是干嘛的 ...

  7. Jvm运行时数据区 —— Java虚拟机结构小记

    关于jvm虚拟机的文章网上都讲烂了.尤其是jvm运行时数据区的内容. 抱着眼见为实的想法,自己翻了翻JVM规范,花了点时间稍微梳理了一下. 以下是阅读Java虚拟机规范(Java SE 8版)的第二章 ...

  8. Java中的字符串常量池和JVM运行时数据区的相关概念

    什么是字符串常量池 JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池 工作原理 当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量 ...

  9. JVM 运行时数据区(二)

    @ 目录 运行时数据区 共享区 堆区 方法区 隔离区 虚拟机栈 栈帧 本地方法栈 程序计数器 运行时数据区 JVM 运行时数据区主要分为5块 方法区 JDK1.8以后叫做元数据区(Metaspace) ...

随机推荐

  1. 20145329 《Java程序设计》实验五总结

    实验内容: 1.用老师代码编写,实现服务器与客户端. 2.客户端与服务器连接 3.客户端中输入明文,利用DES算法加密,DES的秘钥用RSA公钥密码中服务器的公钥加密,计算明文的Hash函数值,一起传 ...

  2. platform_device和platform_driver的注册过程,及probe函数何时调用的分析 ⭐⭐⭐

    add  platform_device之后,需要注意的一个地方是这里,add是通过系统初始化里边调用platform_add_devices把所有放置在板级platform_device数组中的所有 ...

  3. python使用百度api翻译中英文

    python使用百度api翻译中英文 写程序取变量名的时候,常常需要翻译单词,或者将中文翻译成英语.有道词典,必应词典都很好,可是...命令行习惯了还是觉得用在cmd里面调出程序使用起来也许会更爽.于 ...

  4. LDA学习之beta分布和Dirichlet分布

    ---恢复内容开始--- 今天学习LDA主题模型,看到Beta分布和Dirichlet分布一脸的茫然,这俩玩意怎么来的,再网上查阅了很多资料,当做读书笔记记下来: 先来几个名词: 共轭先验: 在贝叶斯 ...

  5. 这样获取celery的结果 有啥隐患没有啊?

  6. centos7 systemctl一些用法

    systemctl 是管制服务的主要工具, 它整合了chkconfig 与 service功能于一体. systemctl is-enabled servicename.service #查询服务是否 ...

  7. maven笔记(1)

    maven环境搭建:http://www.cnblogs.com/fnng/archive/2011/12/02/2272610.html 项目管理利器(Maven)——常用的构建命令1. mvn - ...

  8. go Rails 知识点,Concepts Series:url和parameter; 建立Rails App Templates;报错页面debug; counter_cache

    Rails Concepts Series: https://gorails.com/series/rails-concepts 基本都是免费的 一些细小的知识点,很有帮助. URL和paramete ...

  9. Android----- 版本更新和 服务器下载新版本APK并安装

    前段时间有朋友问我版本更新的问题,所以来写一篇版本更新和APK下载并安装的博客. 版本更新,几乎在所有的项目中都用的到,一般是这样的流程,当进入APP首页是便会检测版本是否为最新版本,不是则提示你下载 ...

  10. 使用Mysql Workbench 导入数据库提示 ERROR 1227 (42000) at line 18: Access denied; you need (at least one of) the SUPER privilege(s) for

    今天再复制服务器上数据库的时候(使用Mysql Workbench )提示1227错误, 数据库版本5.7.18,复制到的数据库也是5.7.18. 总结一下网上的几种方法: 方法一: 最直观的翻译是说 ...