前提知识~
JDK 基本介绍
- JDK 的全称(Java Development Kit Java 开发工具包)
- JDK = JRE + java 的开发工具[java, javac,javadoc,javap 等]
- JDK 是提供给Java 开发人员使用的,其中包含了java 的开发工具,也包括了JRE。
- 可开发、编译、调试......
JRE 基本介绍
- JRE(Java Runtime Environment Java 运行环境)
- JRE = JVM + Java 的核心类库[类]
- 包括Java 虚拟机(JVM Java Virtual Machine)和Java 程序所需的核心类库等,如果只想运行开发好的.class 文件只需要JRE。也称最小运行环境。
- 只运行
JVM是什么?
- JVM(Java Virtual Machine, Java虚拟机)
JVM有什么用?
与其他语言不同,Java 语言并不直接将代码编译成与系统有关的机器码,而是编译成一种特定的语言规范,这种语言规范我们称之为字节码。无论 Java 程序要在 Windows 系统,还是 Mac OSX 系统,或是 Linux 系统,它首先都得编译成字节码文件,之后才能运行。
我们编译成字节码之后,无论但是 Linux 系统、 Windows 系统都还是不认识。
这时候 Java 虚拟机就是一个翻译官,解析字节码文件的内容,在 Linux 系统上翻译成 Linux 机器码给 Linux 系统听,在 Windows 系统上翻译成 Windows 机器码给 Windows 系统听。
实际上 Java 虚拟机运行的是字节码文件(Class文件) ,并不是Java代码
总结: Java 虚拟机是一个字节码翻译器,它将字节码文件翻译成各个系统对应的机器码,确保字节码文件能在各个系统正确运行。

JVM的内存结构:
首先先说虚拟机内存结构和运行时数据区两个说法是一个意思。
分为5 个:

1.Java 堆(Heap):
- 是内存中最大的一块
- Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建
- 作用:存放对象实例,几乎所有的对象实例都在这里分配内存。
- 有些时候小对象会直接在栈上进行分配,这种现象我们称之为「栈上分配」
Java 堆++根据对象存活时间的不同++ ,Java 堆还被分为年轻代、老年代两个区域;年轻代还被进一步划分为 Eden 区、From Survivor 0、To Survivor 1 区。
当有对象需要分配时,一个对象永远优先被分配在年轻代的 Eden 区,等到 Eden 区域内存不够时,Java 虚拟机会启动垃圾回收。此时 Eden 区中没有被引用的对象的内存就会被回收,而一些存活时间较长的对象则会进入到老年代。在 JVM 中有一个名为 -XX:MaxTenuringThreshold 的参数专门用来设置晋升到老年代所需要经历的 GC 次数,即在年轻代的对象经过了指定次数的 GC 后,将在下次 GC 时进入老年代。
Eden:S0 :S1 = 8:1:1

2.方法区(Method Area):
- 作用:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 常量池其实是存放在方法区中的
- 方法区在不同版本的虚拟机有不同的表现形式,例如在 1.7 版本的 HotSpot 虚拟机中,方法区被称为永久代(Permanent Space),而在 JDK 1.8 中则被称之为 MetaSpace。
3.程序计数器(Program Counter Register):
- 是一块较小的内存空间
- 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储
- 如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie方法,这个计数器值则为空(Undefined)
4.JVM栈(JVM Stacks):
- 与线程同时创建
- 每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。
- 每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
- 简单来说:执行 Java 代码
5.本地方法栈(Native Method Stacks) :
本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
总结一下:一个 Java 文件就加载到内存中了,并且 Java 类信息就会存储在我们的方法区中。如果创建对象,那么对象数据就会存放在 Java 堆中。如果调用方法,就会用到 PC 寄存器、Java 虚拟机栈、本地方法栈等结构。
字节码:
前面我们知道了,在不同操作系统、不同硬件平台上都可以做到不用修改代码就可以运行,怎么实现的跨平台?中间码诞生了!即"字节码"!
Java所有的指令有200个左右,一个字节(8位)可以存储256种不同指令信息,一个这样的字节成为字节码。
那么,Java源代码是如何转成字节码的呢?如图:

当字节码通过类加载到JVM环境后,才可以执行,而执行分为三种模式:
- 解释执行(javac编译器):启动快,运行慢
- JIT编译执行:启动慢,运行快
- JIT编译与解释混合执行(默认): 在启动时先解释执行,省去编译时间
解释器不需要像 JIT 编译器一样,将所有字节码都转化为机器码,所以就减少了时间。而JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。大家都知道,机器码的运行效率肯定是高于 Java 解释器的,所以我们会默认使用混合执行

类加载机制
程序三阶段:编译,加载,运行
JVM 虚拟机将字节码读取进内存,从而进行解析、运行等的这整个过程,我们叫:Java 虚拟机的类加载机制。(即第二阶段)
类加载阶段又可以细分为三个阶段,为加载Load,连接Link,初始化Init。

- 加载
- 连接:
- 验证 :验证成员的安全性(更详细的校验,比如final是否合规、类型是否正确、静态变量是否合理)
- 准备 :将类中的静态成员初始化为默认值(为静态变量分配内存,设定默认值)
- 解析 :将间接地址转换为直接地址(将其在常量池中的符号引用替换成直接其在内存中的直接引用)
- 初始化
尾声
JVM入门教程第7讲:JVM 类加载机制 - 陈树义 - 博客园