JVM 入门学习(一)

前言

一、JVM 是什么

二、JVM 组成架构

1. JVM 的发展史

2. JDK默认的JVM HotSpot组成架构

三、Java 代码是如何运行的

1. 前端编译

2. 类加载

3. 运行时编译


前言

  为什么要学习JVM?

  针对为什么要学习JVM,对于一个工作了四年之久的老人,笔者很尴尬的回答主要原因是面试会被问到这个。当然还有啥扩展视野、满足好奇心等,但这些貌似确实敌不过面试。

  平时的开发中,除了根据对应的官方文档对JVM内存大小进行一些简单的配置或者切换GC回收器之外,几乎可以说是没有跟JVM打过交道。虽然在用ES或Hadoop之类的大数据组件,经常有看到过GC之类的Warning或Exception抛出,但也总是见到一个异常就上网去查具体的解决方法。终究是一种知其然,不知其所以然的程度。

  有人说,JVM的知识就像是公式的推导,而其参数等的配置就像是直接使用公式。当然,我们在大多数情况下,对于一些公式不用知道其推导过程也一样可以用的很溜。

  但对于一个还有点追求的程序员,学习点底层的知识还是很重要的,毕竟这个世界变化总是那么快,可还是万变不离综,再说总不能一直保持在知其然不知其所以然的程度吧。

一、JVM 是什么

JVM与JDK、JRE的关系是如何的呢?

  JVM(Java Virtual Machine):Java 虚拟机,其只认.class文件,可以识别.class文件中的字节码指令进而与操作系统进行交互。Java程序能实现“一次编译,多次运行”即可移植性,正是基于JVM的存在。

Java 如何实现可移植性?

  Java 通过Jvm实现了跨平台操作,我们都知道当java代码跟linux交互与跟window交互时所需要的交互命令是不一样的,但是因为有了jvm的存在,其将同一个.class文件根据不同的操作系统解析成不同的指令,进而实现了跨平台操作。

JRE(Java Runtime Environment): Java 运行时环境,其组成是JVM 与 java 的基本类库。jre是java代码运行所必备的环境。

JDK (Java Development Kit):Java 开发工具包,其集成了jre与java常用的工具类如编译器 Javac等。是Java开发环境所需要的,如果只需要运行代码那么可以不装JDK。

因此它们三者关系如下所示,JDK > JRE > JVM。引用百度文档的图如下所示:

 


二、JVM 组成架构

1. JVM 的发展史

  1999年4月27日,Java HotSpot Virtual Machine(简称HotSpot) 正式发布,发布之后JDK1.3版本开始,HotSpot成为Sun JDK默认的虚拟机。

2. JDK默认的JVM HotSpot组成架构

  HotSpot JVM的组成:类加载子系统、运行时数据区、执行引擎、方法接口等组成。如下图所示:

 HotSpot主要职责:

  • 完成类加载(定位、加载、验证);
  • 执行方法所请求的指令和运算;
  • 管理应用内存,包括堆、栈、方法区等。

三、Java 代码是如何运行的

1. 前端编译

   首先.java 文件通过javac工具编译成.class文件。

2. 类加载

这个过程通过类加载类将.class字节码文件加载至内存中,如下图所示:

简述上图:类加载的过程需要经过连接与初始化最终才会被运用。连接的过程包括:验证、准备、解析。

  验证:主要是验证类是否符合Java规范和JVM规范。

  准备:为类的静态变量分配内存,初始化为系统的初始值;静态变量可以不用手动赋值,比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的。

  解析:把类中的符号引用转化为直接引用。在编译时,Java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。类结构文件的常量池中存储了符号引用,包括类和接口的全限定名、类引用、方法引用以及成员变量引用等。如果要使用这些类和方法,就需要把它们转化为JVM可以直接获取的内存地址或指针,即直接引用。

  初始化:为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值。

 类加载器分类:

  • 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
  • 其他类加载器:
  • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
  • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

 双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

类加载过程JVM中内存是如何进行的呢?

   从内存分析来讲类加载就是虚拟机为类分配内存空间的过程,虚拟机将类的.class文件加载到内存,并将它放到运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

3. 运行时编译

  类加载完成后执行引擎则会将字节码转换成机器码,然后才可以在操作系统中执行。如下图所示:

 

  将字节码转化成机器码的过程中,执行引擎中存在着两种编译器:解释执行器与即时编译器JIT(Just In Time)。

  简单来说解释执行器是在运行代码时,边加载字节码边解释执行,这个过程效率不是那么好。即时编译是在执行程序前先将代码编译并进行各层的优化,然后将对应的机器码保存到内存中。

那么JVM如何选择那些代码用JIT执行呢?

   简单来说,JVM会通过给代码块添加一个计数器,通过这个计数器监控那些代码经常被执行,当一段代码执行达到计数器所设置的阈值,那么JVM就会将其认定为“热点代码”,从而运用JIT来编译该代码。

目前JDK8默认启用的是Server方式、混合模式(mixed mode)如下所示:

[root@hdp41 ~]# java -version
java version "1.8.0_91"
Java(TM) SE Runtime Environment (build 1.8.0_91-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)
[root@hdp41 ~]#

   这种混合执行模式是建立在程序符合二八定律的假设上,即百分之二十的代码占据了百分之八十的计算资源。对于不常用代码,我们无需耗费时间将其编译成二进制代码,而是采取解释执行的方式运行;另一方面,对于仅占据小部分的热点代码,JVM则会花费时间将其编译为二进制代码,以达到理想的运行效率。

综合上面,Java代码的执行流程如下所示(引用一个简单java程序的运行全过程):

JVM整体结构示意图(引用一张图看懂JVM) ,笔者本着为梳理知识点为由,自己动手画下GC相关知识点的图,画了挺久,都没能画到满意的。终究还是引用大神的图了,不画不知道,一画吓一跳,向大神致敬。

注:关于虚拟机垃圾回收与JVM内存模型详见后面博文。


参考:

周明耀《深入理解JVM & G1 GC》

六问 Java 虚拟机

一个简单java程序的运行全过程

一张图看懂JVM

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值