深入了解JVM

JDK: Java Development Kit Java开发工具包。

JRE:Java Runtime Environment Java运行环境。        

JVM:Java Virtual Machine Java虚拟机。

三者之间的关系 JDK包含JRE  JRE包含JRE。


重点理解JVM

JVM是一种规范,就是一个虚拟的用于执行bytecode字节码的计算机。  有了这个虚拟机使得Java程序 一次编译到处执行的特点。实现了跨平台的特点。

运行流程

我们都知道java一直宣传的口号是:一次编译,到处运行。那么它如何实现的呢?我们看下图:

image

  java程序经过一次编译之后,将java代码编译为字节码也就是class文件,然后在不同的操作系统上依靠不同的java虚拟机进行解释,最后再转换为不同平台的机器码,最终得到执行。这样我们是不是可以推演,如果要在mac系统上运行,是不是只需要安装mac java虚拟机就行了。那么了解了这个基本原理后,我们尝试去做更深的研究,一个普通的java程序它的执行流程到底是怎样的呢?例如我们写了一段这样的代码:

public class HelloWorld { public static void main(String[] args) { System.out.print("Hello world"); } }

这段程序从编译到运行,最终打印出“Hello world”中间经过了哪些步骤呢?我们直接上图:

image

  java代码通过编译之后生成字节码文件(class文件),通过:java HelloWorld执行,此时java根据系统版本找到jvm.cfg,各位可以搜索一下自己电脑上的jvm.cfg文件在哪,它会根据你的系统版本放在不同的位置,比如我的这个文件就在:C:\Program Files\Java\jdk1.8.0_101\jre\lib\amd64\jvm.cfg,打开看一下:

S{6G7J8WOT3$)72FL1DW5U7

  这是我电脑上的文件,其中-server KNOWN就表示名称为server的jvm可用。如果这时你搜索一下你电脑上jvm.dll,你就会发现它一定在你的某个server目录下,比如我的:C:\Program Files\Java\jdk1.8.0_101\jre\bin\server\jvm.dll。简而言之就是通过jvm.cfg文件找到对应的jvm.dll,jvm.dll则是java虚拟机的主要实现。接下来会初始化JVM,并且获取JNI接口,什么是JNI接口,就是java本地接口,你想啊java被编译成了class文件,JVM怎么从硬盘上找到这个文件并装载到JVM里呢,就是通过JNI接口(它还常用于java与操作系统、硬件交互),找到class文件后并装载进JVM,然后找到main方法,最后执行。

JVM基本结构

  可能通过上面的描述,大家对JVM运行流程有了一个粗略的认识,那么JVM内部到底是怎么执行一个class文件的呢,也就是上图中最后一步第6步的内部细节是怎样的呢?要了解这个问题,我们首先得看一下JVM的内部结构:

image

  从这个结构不难看出,class文件被jvm装载以后,经过jvm的内存空间调配,最终是由执行引擎完成class文件的执行。当然这个过程还有其他角色模块的协助,这些模块协同配合才能让一个java程序成功的运行,下面就详细介绍这些模板,它们也是后面学习jvm最重要的部分

内存空间:

JVM内存空间包含:方法区、java堆、java栈、本地方法栈

方法区:是各个线程共享的区域,存放全局变量,静态变量和字符串常量,和方法,不释放。

java堆:也是线程共享的区域,我们的类的实例就放在这个区域,可以想象你的一个系统会产生很多实例,因此java堆的空间也是最大的。如果java堆空间不足了,程序会抛出OutOfMemoryError异常

详解:存放new的对象和数组(jvm不定时查看这个对象,如果没有引用指向这个对象就回收)

对于堆的GC机制:

JVM区域总体分两类,heap区非heap区

其中heap区又分为:Eden Space(伊甸园)、SurvivorSpace(幸存者区)、Tenured Gen(老年代-养老区)Perm Gen(永久代)。GC(gabage collection)

非heap区又分:Code Cache(代码缓存区)、Perm Gen(永久代)、Jvm Stack(java虚拟机栈)、Local Method Statck(本地方法栈)。--在java栈中会涉及。

1、new Object() 后会放入在Eden Space,GC会定期查看该对象是否有引用,如果没有回收该内存。如果有会进入Survivor Space。

2、对象进入Survivor Space 后 GC还会定期(可以自定义)来查看引用状况,如果有的话就让其进入了Tenured Gen。如果没有直接kill掉。

3、进入到Tenured Gen的对象基本就可以很安全啦,但如果发现没有引用,GC还是会kill掉。

分区的目的:Eden Space由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而Tenured Gen生命力很强,则采用复制算法,针对不同情况使用不同算法。

通知GC销毁对象 : obj  = null ;   System.gc(); 

GC工作机制

SUN的jvm内存池被划分为以下几个部分:

1、Eden Space (heap)  内存最初从这个线程池分配给大部分对象。

2、Survivor Space (heap) 用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。

3、Tenured Generation (heap用于保持已经在survivor space内存池中存在了一段时间的对象。

4、Permanent Generation (non-heap保存虚拟机自己的静态(reflective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的。

5、Code Cache (non-heapHotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)。

>>综上 jvm的内存回收过程是这样的:

对象在Eden Space创建,当Eden Space满了的时候,gc就把所有在Eden Space中的对象扫描一次,把所有有效的对象复制到第一个Survivor Space,同时把无效的对象所占用的空间释放。当Eden Space再次变满了的时候,就启动移动程序把Eden Space中有效的对象复制到第二个Survivor Space,同时,也将第一个Survivor Space中的有效对象复制到第二个Survivor Space。如果填充到第二个Survivor Space中的有效对象被第一个Survivor Space或Eden Space中的对象引用,那么这些对象就是长期存在的,此时这些对象将被复制到Permanent Generation。

若垃圾收集器依据这种小幅度的调整收集不能腾出足够的空间,就会运行Full GC,此时jvm gc停止所有在堆中运行的线程并执行清除动作。

java栈:是每个线程私有的区域,它的生命周期与线程相同,一个线程对应一个java栈,每执行一个方法就会往栈中压入一个元素,这个元素叫“栈帧”,而栈帧中包括了方法中的局部变量、用于存放中间状态值的操作栈,这里面有很多细节,我们以后再讲。如果java栈空间不足了,程序会抛出StackOverflowError异常,想一想什么情况下会容易产生这个错误,对,递归,递归如果深度很深,就会执行大量的方法,方法越多java栈的占用空间越大。存放函数的参数值,局部变量的值等,方法执行结束后自动回收

本地方法栈:角色和java栈类似,只不过它是用来表示执行本地方法的,本地方法栈存放的方法调用本地方法接口,最终调用本地方法库,实现与操作系统、硬件交互的目的。

PC寄存器,说到这里我们的类已经加载了,实例对象、方法、静态变量都去了自己改去的地方,那么问题来了,程序该怎么执行,哪个方法先执行,哪个方法后执行,这些指令执行的顺序就是PC寄存器在管,它的作用就是控制程序指令的执行顺序

执行引擎当然就是根据PC寄存器调配的指令顺序,依次执行程序指令

结语

  本文主要介绍了java虚拟机运行的基本流程,以及java虚拟机内部结构。下一篇我们将学习java内存模型以及探索java变量的可见性、有序性、指令重排等问题。






猜你喜欢

转载自blog.csdn.net/yangshaojun1992/article/details/79589599