java虚拟机JVM和jmm内存模型

学Java的朋友, 相信都听过一句话:java语言是跨平台的。那java是怎么跨平台的呢, 靠的就是JVM(Java Virtual Machine)java虚拟机。java编译以后会生成class字节码文件, 然后字节码文件运行在JVM上, 然后JVM就把class字节码文件转成机器指令, 可以在不同的平台上运行了。

这里要注意的是, 跨平台的是java语言, 而不是JVM,不同平台上JVM的实现是不同的, 但是他们的整体结构是相同的。了解一下JVM的结构, 有利于我们更深入的理解java。

JVM 结构图
首先来看一下java虚拟机的结构图:

å¨è¿éæå¥å¾çæè¿°

可以看出, JVM主要组成有:类加载器子系统、运行时数据区、执行引擎以及本地方法接口等。

一、ClassLoader类加载器
负责把存储设备上的class文件加载到JVM的运行时数据区,使JVM可以实例化或以其它方式使用加载后的类。JVM的类加载子系统支持在运行时的动态加载

二、Runtime Data Area–运行时数据区
我们通常所说的堆栈就在这里,运行时数据区由方法区、堆、Java栈、PC寄存器、本地方法栈w五部分组成, 结构图如下:
 å¨è¿éæå¥å¾çæè¿°

图中橙色部分是线程私有的,其他线程无法访问, 而蓝色部分(堆区和方法区)是整个进程共享的, 所有线程均可以访问。其中JDK1.7以前(不含1.7),常量池在方法区中, JDK1.7及以后常量池移到了堆区。下面对这五部分一一介绍

 

https://www.cnblogs.com/aiqiqi/p/10770864.html 讲的不错

2.1 java虚拟机栈(Java Virtual Mathion Stack):
jvm栈, 有的也称为java栈,主要任务是存储方法参数、局部变量、中间运算结果, 并提供部分其他模块工作需要的数据。

JVM栈是私有的, 生命周期和线程一致,每当创建线程就创建JVM栈。每个JVM栈中会包含多个栈帧(Stack Frame), 每当调用一个方法,就创建一个一个栈帧, 每个栈帧回包含调用的这个方法的局部变量、操作栈、方法返回值等信息。在jvm栈栈顶的栈帧就是当前正在执行的活动栈帧, PC寄存器会指向该内存地址。如果在这个方法内调用了另一个方法, 又会创建一个新的栈帧,那这个新栈帧会被放到jvm栈 栈顶, 变成当前的活动栈帧, PC寄存器也重新会指向这里。当一个方法执行完成时,该栈帧就会弹出栈帧的元素作为这个方法的返回值,并且清除这个栈帧,然后继续执行下一个栈帧。

在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

2.2 本地方法栈(Native Method Stack):
本地方法栈类似于Java栈,主要存储了本地方法调用的状态。区别不过是Java栈为JVM执行Java方法服务,而本地方法栈为JVM执行Native方法服务。本地方法栈也会抛出StackOverflowError和OutOfMemoryError异常。有一些虚拟机(如HotSpot, Sun JDK中的)将Java虚拟机栈和本地方法栈合并,所以其本地方法栈和Java栈是同一个。

2.3 程序计数器( PC寄存器(Program Counter Register) ):
严格来说是一个数据结构,用于保存当前正在执行的程序的内存地址,由于Java是支持多线程执行的,所以程序执行的轨迹不可能一直都是线性执行。当有多个线程交叉执行时,被中断的线程的程序当前执行到哪条内存地址必然要保存下来,以便用于被中断的线程恢复执行时再按照被中断时的指令地址继续执行下去。为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各个线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存,这在某种程度上有点类似于“ThreadLocal”,是线程安全的。

2.4 方法区(Method Area):

注意: 不能把任何方法体内的变量声明为静态的,因为静态变量是属于整个类的变量而不是属于某个对象的。static 只能修饰 成员变量 或者成员方法。不能修饰局部变量。在Java 中,用static 修饰的成员 被是为 共享的。定义在方法中,被视为 局部的。显然是不能共享的。


类型信息和全局静态变量和全局常量(全局非静态变量应该是放在栈中,因为非static修饰的属性是每个对象独有的,只有在创建对选哪个的时候,其属性才会赋值)都存储在方法区中。方法区中对于每个类存储了以下数据:

类及其父类的全限定名(java.lang.Object没有父类)
类的类型(Class or Interface)
访问修饰符(public, static, abstract, final)
实现的接口的全限定名的列表
常量池
字段信息
方法信息
静态变量
ClassLoader引用
Class引用
可见类的所有信息都存储在方法区中。由于方法区是所有线程共享的,所以必须保证线程安全,举例来说,如果两个类同时要加载一个尚未被加载的类,那么一个类会请求它的ClassLoader去加载需要的类,另一个类只能等待而不会重复加载。

2.5 堆区(Heap):
堆是JVM所管理的内存中最大的一块,是被所有Java线程锁共享的,不是线程安全的,在JVM启动时创建。我们通常说的GC回收内存也是回收这一块的内存

Java虚拟机规范中这样描述:所有的对象实例以及数组都要在堆上分配。堆中有指向类数据的指针,该指针指向了方法区中对应的类型信息。堆中还可能存放了指向方法表的指针。堆是所有线程共享的,所以在进行实例化对象等操作时,需要解决同步问题。此外,堆中的实例数据中还包含了对象锁,并且针对不同的垃圾收集策略,可能存放了引用计数或清扫标记等数据。

三、执行引擎 (了解即可)
执行引擎是JVM执行Java字节码的核心,执行方式主要分为解释执行、编译执行、自适应优化执行、硬件芯片执行方式。

JVM的指令集是基于栈而非寄存器的,这样做的好处在于可以使指令尽可能紧凑,便于快速地在网络上传输(别忘了Java最初就是为网络设计的),同时也很容易适应通用寄存器较少的平台,并且有利于代码优化,由于Java栈和PC寄存器是线程私有的,线程之间无法互相干涉彼此的栈。每个线程拥有独立的JVM执行引擎实例。

JVM指令由单字节操作码和若干操作数组成。对于需要操作数的指令,通常是先把操作数压入操作数栈,即使是对局部变量赋值,也会先入栈再赋值。注意这里是“通常”情况,之后会讲到由于优化导致的例外。

4.1 解释执行
和一些动态语言类似,JVM可以解释执行字节码。Sun JDK采用了token-threading的方式,感兴趣的同学可以深入了解一下。解释执行中有几种优化方式:

栈顶缓存:将位于操作数栈顶的值直接缓存在寄存器上,对于大部分只需要一个操作数的指令而言,就无需再入栈,可以直接在寄存器上进行计算,结果压入操作数站。这样便减少了寄存器和内存的交换开销。

部分栈帧共享:被调用方法可将调用方法栈帧中的操作数栈作为自己的局部变量区,这样在获取方法参数时减少了复制参数的开销。
执行机器指令:在一些特殊情况下,JVM会执行机器指令以提高速度。

4.2 编译执行
为了提升执行速度,Sun JDK提供了将字节码编译为机器指令的支持,主要利用了JIT(Just-In-Time)编译器在运行时进行编译,它会在第一次执行时编译字节码为机器码并缓存,之后就可以重复利用。Oracle JRockit采用的是完全的编译执行。

4.3 自适应优化执行
自适应优化执行的思想是程序中10%20%的代码占据了80%90%的执行时间,所以通过将那少部分代码编译为优化过的机器码就可以大大提升执行效率。自适应优化的典型代表是Sun的Hotspot VM,正如其名,JVM会监测代码的执行情况,当判断特定方法是瓶颈或热点时,将会启动一个后台线程,把该方法的字节码编译为极度优化的、静态链接的C++代码。当方法不再是热区时,则会取消编译过的代码,重新进行解释执行。

自适应优化不仅通过利用小部分的编译时间获得大部分的效率提升,而且由于在执行过程中时刻监测,对内联代码等优化也起到了很大的作用。由于面向对象的多态性,一个方法可能对应了很多种不同实现,自适应优化就可以通过监测只内联那些用到的代码,大大减少了内联函数的大小。

Sun JDK在编译上采用了两种模式:Client和Server模式。前者较为轻量级,占用内存较少。后者的优化程序更高,占用内存更多。

在Server模式中会进行对象的逃逸分析,即方法中的对象是否会在方法外使用,如果被其它方法使用了,则该对象是逃逸的。对于非逃逸对象,JVM会在栈上直接分配对象(所以对象不一定是在堆上分配的),线程获取对象会更加快速,同时当方法返回时,由于栈帧被抛弃,也有利于对象的垃圾收集。Server模式还会通过分析去除一些不必要的同步,感兴趣的同学可以研究一下Sun JDK 6引入的Biased Locking机制。

此外,执行引擎也必须保证线程安全性,因而JMM(Java Memory Model)也是由执行引擎确保的。

延伸:

      

jmm

Java内存模型(Java Memory Model,JMM) 是在硬件内存模型上的更高层的抽象,它屏蔽了各种硬件和操作系统访问的差异性同时规定了线程和内存之间的一些关系,保证了Java程序在各种平台下对内存的访问都能达到一致的效果。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主内存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

现代的CPU往往是有多个核心的,每个核心都有自己的缓存,而多个核心之间是不存在时间片的竞争的,它们可以并行地执行,那么,怎么保证这些缓存与主内存中的数据的一致性就成为了一个难题。

jvm和jmm之间的关系

jmm中的主内存、工作内存与jvm中的Java堆、栈、方法区等并不是同一个层次的内存划分,这两者基本上是没有关系的,如果两者一定要勉强对应起来,那从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。从更低层次上说,主内存就直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机(甚至是硬件系统本身的优化措施)可能会让工作内存优先存储于寄存器和高速缓存中,因为程序运行时主要访问读写的是工作内存。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值