下图是JAVA虚拟机的结构图:

每个Java虚拟机都有一个类装载子系统,它根据给定的全限定名来装入类型(类或接口)。同样,每个Java虚拟机都有一个执行引擎,它负责执行那些包含在被装载类的方法中的指令。 当JAVA虚拟机运行一个程序时,它需要内存来存储许多信息,Java虚拟机把这些信息都组织到几个“运行时数据区”中,以便于管理。运行时数据区共包括五个部分(方法区、Java堆区、Java栈区、程序计数器、本地方法栈)。
1.类加载子系统
在JAVA虚拟机中,负责查找并加载类型的那部分被称为类加载子系统。
类加载器子系统在Java虚拟机中实现了类加载的全过程动作,这些动作必须严格按以下顺序进行:
(1)加载——主要完成3件事:
1.通过一个类的全限定名来获取定义此类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
(2)连接——指向验证、准备、以及解析(可选)。
● 验证 确保Class文件的字节流中包含的的信息符合当前虚拟机的要求,并且不会危害到虚拟机自身的安全。
● 准备 为类变量分配内存,并将其初始化为默认值。
● 解析 把类型中的符号引用转换为直接引用。
(3)初始化——把类变量初始化为正确初始值。
类加载器:用来加载 Java 类到 Java 虚拟机中,实现“通过一个类的全限定名来获取描述此类的二进制字节流”动作的代码模块。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间。也就是说,比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。
JAVA虚拟机有两种类加载器:启动类加载器和用户自定义类加载器。前者由C++实现,是JAVA虚拟机实现的一部分;后者则是由Java程序实现,独立于虚拟机外部。
2.运行时数据区
2.1方法区(线程共享)
用于存放已被虚拟机加载的类信息、常亮、静态变量、及时编译后的代码等数据。Java虚拟机规范把方法区描述为堆的一个逻辑部分,但为了将其与Java堆区分开来,称其别名为:Non-Heap(非堆)。
对于每个装载的类型,虚拟机都会在方法区中存储以下类型信息:
● 这个类型的全限定名
● 这个类型的直接超类的全限定名(除非这个类型是java.lang.Object,它没有超类)
● 这个类型是类类型还是接口类型
● 这个类型的访问修饰符(public、abstract或final的某个子集)
● 任何直接超接口的全限定名的有序列表
除了上面列出的基本类型信息外,虚拟机还得为每个被装载的类型存储以下信息:
● 该类型的常量池
● 字段信息
● 方法信息
● 除了常量以外的所有类(静态)变量
● 一个到类ClassLoader的引用
● 一个到Class类的引用
2.1.1常量池
存放字符串常量和基本类型常量(public static final)和符号引用。虚拟机必须为每个被加载的类型维护一个常量池。常量池就是该类型所用常量的一个有序集合,包括直接常量和对其他类型、字段和方法的符号引用,池中的数据项就像数组一样是通过索引访问的。因为常量池存储了相应类型所用到的所有类型、字段和方法的符号引用,所以它在Java程序的动态连接中起着核心的作用。
2.1.2字段信息
对于类型中声明的每一个字段。方法区中必须保存下面的信息。除此之外,这些字段在类或者接口中的声明顺序也必须保存。
○ 字段名
○ 字段的类型
○ 字段的修饰符(public、private、protected、static、final、volatile、transient的某个子集)
2.1.3方法信息
对于类型中声明的每一个方法,方法区中必须保存下面的信息。和字段一样,这些方法在类或者接口中的声明顺序也必须保存。
○ 方法名
○ 方法的返回类型(或void)
○ 方法参数的数量和类型(按声明顺序)
○ 方法的修饰符(public、private、protected、static、final、synchronized、native、abstract的某个子集)
除了上面清单中列出的条目之外,如果某个方法不是抽象的和本地的,它还必须保存下列信息:
○ 方法的字节码(bytecodes)
○ 操作数栈和该方法的栈帧中的局部变量区的大小
○ 异常表
2.1.4类(静态)变量
类变量是由所有类实例共享的,但是即使没有任何类实例,它也可以被访问。这些变量只与类有关——而非类的实例,因此它们总是作为类型信息的一部分而存储在方法区。除了在类中声明的编译时常量外,虚拟机在使用某个类之前,必须在方法区中为这些类变量分配空间。
而编译时常量(就是那些用final声明以及用编译时已知的值初始化的类变量)则和一般的类变量处理方式不同,每个使用编译时常量的类型都会复制它的所有常量到自己的常量池中,或嵌入到它的字节码流中。作为常量池或字节码流的一部分,编译时常量保存在方法区中——就和一般的类变量一样。但是当一般的类变量作为声明它们的类型的一部分数据面保存的时候,编译时常量作为使用它们的类型的一部分而保存。
2.1.5指向ClassLoader类的引用
每个类型被装载的时候,虚拟机必须跟踪它是由启动类装载器还是由用户自定义类装载器装载的。如果是用户自定义类装载器装载的,那么虚拟机必须在类型信息中存储对该装载器的引用。这是作为方法表中的类型数据的一部分保存的。
虚拟机会在动态连接期间使用这个信息。当某个类型引用另一个类型的时候,虚拟机会请求装载发起引用类型的类装载器来装载被引用的类型。这个动态连接的过程,对于虚拟机分离命名空间的方式也是至关重要的。为了能够正确地执行动态连接以及维护多个命名空间,虚拟机需要在方法表中得知每个类都是由哪个类装载器装载的。
2.1.6指向Class类的引用
对于每一个被装载的类型(不管是类还是接口),虚拟机都会相应地为它创建一个java.lang.Class类的实例,而且虚拟机还必须以某种方式把这个实例和存储在方法区中的类型数据关联起来。
方法区使用实例详见参考一
2.2Java堆(线程共享)
Java程序在运行时创建的所有类实例对象(包括String对象)或数组都放在同一个堆中。而一个JAVA虚拟机实例中只存在一个堆空间,因此所有线程都将共享这个堆。又由于一个Java程序独占一个JAVA虚拟机实例,因而每个Java程序都有它自己的堆空间——它们不会彼此干扰。但是同一个Java程序的多个线程却共享着同一个堆空间,在这种情况下,就得考虑多线程访问对象(堆数据)的同步问题了。
JAVA 堆是垃圾收集器管理的主要区域,JAVA虚拟机有一条在堆中分配新对象的指令,却没有释放内存的指令,正如你无法用Java代码区明确释放一个对象一样。虚拟机自己负责决定如何以及何时释放不再被运行的程序引用的对象所占据的内存。通常,虚拟机把这个任务交给垃圾收集器。
↑
关于Java堆和Java栈存储内容简单的区分记忆:凡是new出来的对象,都存在堆区;凡是引用都存在栈区★
↓
2.3Java栈 (线程私有)
存放基本类型的值,对象的引用(包括String引用、数组引用)和 returnAddress,在编译期间完成分配。
每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两 种操作:以帧为单位的压栈和出栈。
某个线程正在执行的方法被称为该线程的当前方法,当前方法使用的栈帧称为当前帧,当前方法所属的类称为当前类,当前类的常量池称为当前常量池。在线程执行一个方法时,它会跟踪当前类和当前常量池。此外,当虚拟机遇到栈内操作指令时,它对当前帧内数据执行操作。
每当线程调用一个Java方法时,虚拟机都会在该线程的Java栈中压入一个新帧。而这个新帧自然就成为了当前帧。在执行这个方法时,它使用这个帧来存储参数、局部变量、中间运算结果等数据。
Java方法可以以两种方式完成。一种通过return返回的,称为正常返回;一种是通过抛出异常而异常终止的。不管以哪种方式返回,虚拟机都会将当前帧弹出Java栈然后释放掉,这样上一个方法的帧就成为当前帧了。
Java帧上的所有数据都是此线程私有的。任何线程都不能访问另一个线程的栈数据,因此我们不需要考虑多线程情况下栈数据的访问同步问题。当一个线程调用一个方法时,方法的的局部变量保存在调用线程Java栈的帧中。只有一个线程能总是访问那些局部变量,即调用方法的线程。
两个局部变量相加时栈帧的内存快照如下:

2.4程序计数器 (线程私有)
对于一个运行中的Java程序而言,其中的每一个线程都有它自己的PC(程序计数器)寄存器,用来指示下一条指令的地址。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。它是在该线程启动时创建的,PC寄存器的大小是一个字长(因此它既能够持有一个本地指针,也能够持有一个returnAddress)。当线程执行某个Java方法时,PC寄存器的内容总是下一条将被执行指令的“地址”,这里的“地址”可以是一个本地指针,也可以是在方法字节码中相对于该方法起始指令的偏移量。如果该线程正在执行一个本地方法,那么此时PC寄存器的值是"undefined"。
2.5本地方法栈 (线程私有)
这是 Java 调用操作系统本地库的地方,它与Java栈的区别是Java虚拟机为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机执行使用到的Native方法服务。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。在虚拟机规范中对本地方法栈中使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。
如果某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。当C程序调用一个C函数时,其栈操作都是确定的。传递给该函数的参数以某个确定的顺序压入栈,它的返回值也以确定的方式传回调用者。同样,这就是虚拟机实现中本地方法栈的行为。
很可能本地方法接口需要回调Java虚拟机中的Java方法,在这种情况下,该线程会保存本地方法栈的状态并进入到另一个Java栈。
下图描绘了这样一个情景,就是当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法。这幅图展示了JAVA虚拟机内部线程运行的全景图。一个线程可能在整个生命周期中都执行Java方法,操作它的Java栈;或者它可能毫无障碍地在Java栈和本地方法栈之间跳转。
该线程首先调用了两个Java方法,而第二个Java方法又调用了一个本地方法,这样导致虚拟机使用了一个本地方法栈。假设这是一个C语言栈,其间有两个C函数,第一个C函数被第二个Java方法当做本地方法调用,而这个C函数又调用了第二个C函数。之后第二个C函数又通过本地方法接口回调了一个Java方法(第三个Java方法),最终这个Java方法又调用了一个Java方法(它成为图中的当前方法)。
3.执行引擎
Java虚拟机的核心,控制装入 Java 字节码并解析;对于运行中的Java程序而言,每一个线程都是一个独立的虚拟机执行引擎的实例,从线程生命周期的开始到结束,他要么在执行字节码,要么在执行本地方法。
4.本地接口
连接了本地方法栈和操作系统库。
《深入理解java虚拟机———jvm高级特性与最佳实践》


- java虚拟机体系分析
一.JVM的生命周期: 1)程序开始执行,他就运行,程序停止,它就结束.有几个程序在执行,就有几个虚拟机在工作.只要Java虚拟机中还有普通的线程在执行,Java虚拟机就不会停止. 2)Java虚 ...
- 深入理解Java虚拟机 - Java体系
使用JAVA已经快三年了,但说来惭愧,一直以来认为Java就是Java语言本身,最多再包括一个JVM,对于整个Java的体系结构还是不甚明了,现在有时间把<深入理解Java虚拟机>这本书读 ...
- 深入理解java虚拟机----java技术体系(一)
1.java技术体系 举例: class文件格式:如下图所示,java源代码可以根据不同的编译器可以编译成不同的代码.即可以自定义语言规范比如beanshell,并编写代码; 然后自己编写java编译 ...
- Java虚拟机结构分析
https://www.cnblogs.com/Eason-S/p/5658188.html https://blog.csdn.net/u013256816/article/details/5148 ...
- Java 虚拟机结构分析
本博文主要介绍了JVM(Java Virtual Machine)的组成部分以及它们内部的工作机制和原理.需要注意的是,虽然平时我们用的大多是Sun(现已被Oracle收购)JDK提供的JVM,但是J ...
- JAVA虚拟机:Java技术体系讲解(一)
按照Java系统的功能划分为: 一.Java语言,即使用Java编程语言进行软件开发. 二.开发过程中使用的工具和API(API(Application Programming Interface,应 ...
- 深入理解java虚拟机----->垃圾收集器与内存分配策略(下)
1. 前言 内存分配与回收策略 JVM堆的结构分析(新生代.老年代.永久代) 对象优先在Eden分配 大对象直接进入老年代 长期存活的对象将进入老年代 动态对象年龄判定 空间分配担保 2. 垃圾 ...
- java安全沙箱(三)之内置于Java虚拟机(及语言)的安全特性
java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是: 类加载体系 .class文件检验器 内置于Java虚拟机(及语言)的安全特性 安全管理器及J ...
- 深入Java虚拟机
第一章:Java体系结构介绍 1.Java为什么重要? Java是为网络而设计的,而Java这种适合网络环境的能力又是由其体系结构决定的,可以保证安全健壮和平台无关的程序通过网络传播. 2 ...
随机推荐
- ios 开发failed to chmod
当XCode遇到此问题的时候,可通过重启模拟器和XCode来解决 http://www.jianshu.com/p/f8e7c5949660 合并分支, xcode报错 couldn't load ...
- android studio 使用CMAKE
前言 之前,每次需要边写C++代码的时候,我的内心都是拒绝的. 1. 它没有代码提示!!!这意味着我们必须自己手动敲出所有的代码,对于一个新手来说,要一个字母都不错且大小写也要正确,甚至要记得住所有 ...
- OA项目_环境搭建
OA项目现在要做成微服务,用的框架是springboot,所用的编程工具是idea,maven,做为一个程序员最关心的就是我需要在那个架包中编写代码,我们只需关注domain,repository,s ...
- 用opencv检测人眼并定位瞳孔位置
最近的研究要用到定位瞳孔的位置,所以上网搜了下相关的代码.总结如下: 1) 定位瞳孔可以直接使用opencv中的自带的分类器(haarcascade_eye_tree_eyeglasses.xml)来 ...
- 微信小程序:WXSS 样式
微信小程序:WXSS 样式 一.WXSS 样式 XSS 具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改. 1,新增了尺寸单位: 在写 CSS 样式时,开发者需要考虑到手机设备的屏 ...
- 20145315 《Java程序设计》第十周学习总结
20145315 <Java程序设计>第十周学习总结 教材学习内容总结 网络概述 为了能够方便的识别网络上的每个设备,网络中的每个设备都会有一个唯一的数字标识,这个就是IP地址.IP地址实 ...
- 20145319 《网络渗透》MS11-050漏洞渗透
20145319 <网络渗透>MS11-050漏洞渗透 一 实验内容 初步掌握平台matesploit的使用 有了初步完成渗透操作的思路 了解MS11_050相关知识: - 安全公告:KB ...
- 20145326 《Java程序设计》课程总结
每周读书笔记链接汇总 20145326第1周学习总结 20145326第2周学习总结 20145326第3周学习总结 20145326第4周学习总结 20145326第5周学习总结 20145326第 ...
- win10下搭建深度学习--总结【学习笔记】
win10 下搭建深度学习开发环境总结: 1.本人环境如下:win10,GTX1050TI.i7,anaconda3,vs2015,cuda9.0,cudnn7.1.4,tensorflow-gpu= ...
- shell脚本中多个条件判断的语法
if [ "$content" = "hello" ]\ || [ "$content" = "kitty" ] the ...