Java虚拟机——JVM

2020-02-20 16:01:33来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

Java虚拟机——JVM

一、JVM整体架构

1、JVM(Java虚拟机):指以软件的方式模拟具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统,是物理机的软件实现。常用的虚拟机有VMWare、Virtual Box、Java Virtual Machine。

2、JVM由三个主要的子系统构成

  • 类加载子系统 (即类加载器 / ClassLoader)
  • 运行时数据区(即内存结构 / 内存模型 / JMM)
  • 执行引擎(包含垃圾收集器)

通过类加载器将.class文件加载进内存中,再由执行引擎去运行。

堆和方法区是所有线程都共享的。

Java栈、本地方法栈和程序计数器是非线程共享的。

二、JVM内存结构

下面会用到的例子:定义一个Math类。

1、本地方法栈(线程私有):登记native本地方法(即native方法是存到本地方法栈),在执行引擎执行时加载本地方法库(C语言实现的库)。

举例:Thread类中的 start0() 方法底层实现是用C语言写的。

2、程序计数器(线程私有):就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即指向将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。

举例:对.class文件进行 javap -c 反编译,下图为 math() 方法反编译后的结果:(iconst_1和istore_1两条指令代表 int a=1)

3、方法区(线程共享):类的所有字段和方法字节码,以及一些特殊方法如构造函数、接口代码也定义在此(类加载器会将字节码文件加载到方法区中,分解成多个部分)。简单说,所有定义的方法的信息都保存在该区域,静态变量+常量+类信息(构造方法/接口定义)+运行时常量池都存在方法区中,虽然JVM规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

4、Java栈(线程私有)Java线程执行方法的内存模型,一个线程对应一个栈,每个方法在执行的同时都会创建一个栈帧(用于存储局部变量表、操作数栈、动态链接、方法出口等信息),不存在垃圾回收问题,只要线程一结束该栈就释放,生命周期和线程一致。

JVM对该区域规范了两种异常:

① 线程请求的栈深度大于虚拟机栈所允许的深度时,将抛出StackOverFlowError异常。

② 若虚拟机栈可动态扩展,当无法申请到足够内存空间时将抛出OutOfMemoryError,通过JVM参数 -> Xss指定栈空间,空间大小决定函数调用的深度。

下图左边为Java栈的内存结构图:

动态链接:如Map map = new HashMap(),程序运行时map变量需要去找到运行时的HashMap实例对象(多态)的过程,就叫动态链接。

方法出口:如main()方法中的math()方法运行结束return返回的值用于main()方法中,return的这个过程就叫方法出口。

5、堆(线程共享): 虚拟机启动时创建,用于存放对象实例,几乎所有的对象(包含常量池)都放在堆上分配内存,当对象无法在该空间申请到内存时将抛出OutOfMemoryError异常。同时也是垃圾收集器管理的主要区域。可通过 -Xmx -Xms 参数来分别指定最大堆和最小堆。

下图为堆的内存结构图:

JVM调优的根本目的:尽量减少Full GC的次数,并且缩短每次Full GC的时间长度。(Full GC性能非常低,执行这个过程时会停止整个JVM,即STW(Stop The World),包括程序运行的那些线程,然后专门去做垃圾收集。现在新的GC会尽量缩短STW时间长度,尽量让垃圾收集和业务线程并发执行)

垃圾收集器(GC)存在的意义:回收堆中无引用的对象,释放空间

堆中新生代(Young Generation)占1/3的堆空间,新生代包括伊甸园区(Eden Space)和幸存者区(Survivor Space);老年代(Old Generation)占2/3的堆空间。

JDK1.8以前是没有元数据区(MetaData Space)的,而是永久代,从1.8开始元空间取代了永久代,本质和永久代类似,都是对JVM规范中方法区的实现,区别在于元数据区并不在虚拟机中,不属于堆中的内存结构,而是直接使用本机物理内存,永久代在虚拟机中,逻辑结构上属于堆,但是物理上不属于堆,堆大小=新生代+老年代。元数据区也有可能发生OutOfMemory异常。

  • JDK1.6及之前:有永久代,常量池在方法区
  • JDK1.7:有永久代,但已逐步“去永久代”,常量池在堆
  • JDK1.8及之后:无永久代,常量池在元空间

提问:为什么JDK1.8用元数据区取代了永久代?

官方解释:移除永久代是为融合HotSpot JVM与JRockit VM而做出的努力,因为JRockit没有永久代,不需要配置永久代。

(JDK 8实际上是这两种虚拟机合并之后的产物,HotSpot JVM(Sun公司开发),JRockit VM(BEA公司开发)。猜测:HotSpot JVM输了,没错,开发出Java语言的Sun公司最后输了,就听JRockit VM的)

new出来的对象会放在Eden区中,当Eden区占满时会做一次Minor GC(即轻GC),回收Eden区中无引用的对象,剩下的存活对象则会放到From区中。

② 后面又有新的new出来的对象不断存放在Eden区中,当Eden区占满时再做Minor GC,又有对象存放在From区中,当From区占满时也会做Minor GC,GC会回收Eden区和From区中无引用的对象,剩下的存活对象则会放到To区中,此时角色转变,From区变成To区,To区变成From区。

③ 接着,还会有新的对象存放到Eden区中,当Eden区占满时再做Minor GC,此时存活的对象放到新的From区中,当新的From区放满时又做Minor GC,把存活对象放入新的To区,依次轮循。

④ 如果一直有新的对象过来,不断的做Minor GC,当幸存者区的From区和To区轮循大概15次时,如果To区还有存活的对象,再做一次Minor GC则会把存活的对象移入老年代。

如果有一天老年代也占满了,则会触发Full GC再次回收无引用对象(有的GC会回收所有区,有的只会回收老年代,得看使用了哪种GC,后面会讲)。

三、JVM执行引擎

JVM执行引擎:读取运行时数据区的Java字节码并逐个执行。

解释执行字节码的方式:① JIT编译器;② 字节码解释器;③ mixed mode(前两种的混合模式,JDK 8是采用这种,之前的我不知道)

JIT编译器(Just In Time,即时编译):一次性解释所有指令,第一次执行慢,后面执行快。(会缓存)

字节码解释器:逐行解释指令,每次执行需要重新解释,前几次可能比JIT编译器快,后面比它慢。(不缓存)

 


原文链接:https://www.cnblogs.com/ZekiChen/p/12319401.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:0219 springmvc-拦截器和响应增强

下一篇:SpringBoot整合持久层技术--(二)MyBatis