前边我们讲了一下JVM的原理、规范和Java语言中整数在JVM中的存储形式,即JVM是一种程序虚拟机,本质上是一个程序,这个程序可以运行Java字节码类型的文件,因为当Java程序编写好之后,前台的编译器如javac将其编译成.class字节码的文件,然后JVM可以将其解释成系统处理器可以理解的指令,所以实现了所谓的"一次编译,处处运行",---即Java语言的跨平台性。现在大规模部署应用的JVM是Hotspot虚拟机,其他的还有IBM在用的J9虚拟机。JVM是一个可以执行字节码文件的虚拟机,他运行的字节码不一定是Java语言编译而成的,像Groovy、Scala等语言就可以编译字节码文件然后在JVM中运行。立足于JVM,可以衍生出许多的跨平台的语言,除了语言规范不同之外,这些编译之后的.class字节码文件可以共享JVM带来的跨平台、优秀的垃圾回收机制和即时编译功能(将字节码解释成处理器可以理解的代码)。我们在Java中定义的数据类型都是以二进制数据的形式保存在了内存中或者计算机中,对于整数而言,在JVM的内存中保存的是其对应的二进制数据的补码(整数补码是其自身,负数补码是其反码加一)。
今天我们来讲一讲JVM的基本结构。
1.Java虚拟机的架构
Java虚拟机是一个可以执行.class字节码文件的程序,具有跨平台、垃圾回收和即时编译的功能,其具体的内存区域大致可分为九个部分
① 类加载子系统和方法区
这个区域的类加载子系统是负责从文件系统或者网络中加载Class信息,加载到的类的信息存放于一块成为方法区的内存空间。
方法区中除了类的信息之外,还可以有运行时常量池信息,包括字符串字面量和数字常量。
方法区是一个线程共享的区域。
②堆内存
Java的堆区域是在JVM启动的时候建立,它是Java程序最主要的工作区域。
几乎所有的Java对象实例都存放在Java堆中。
堆空间是线程共享的,这时一块与Java应用密切相关的内存空间。
③ 直接内存
Java的NIO库允许Java程序使用直接内存。
直接内存是堆之外的(不会受制于参数设置:-Xmx),直接向系统申请的内存空间。
通常,访问直接内存的速度回优于Java堆,所以从性能方面考虑,读写频繁的场合可能会考虑使用直接内存。
④ 垃圾回收系统
垃圾回收系统是JVM重要组成部分。
垃圾回收器可以对方法区、Java堆、直接内存进行回收。其中,Java堆是垃圾回收器的工作重点。
对于Java程序中不再使用的垃圾对象,垃圾回收系统会在后台默默工作,默默查找、标识并释放垃圾对象,完成包括Java堆、方法区和直接内存中的全自动化管理。
⑤ 栈区
栈不同堆的是,它是一个线程独有的存储区域。
当一个线程被创建时就在JVM中创建了一个对应该线程的Java栈。
Java栈中保存着帧信息,具体指的是方法中的局部变量和方法参数变量。
⑥ 本地方法栈
和Java栈非常类似,最大不同是Java栈用于Java方法的调用而开辟,而本地方法栈用于本地方法调用而开辟。JVM允许Java直接调用本地方法(通常使用C编写)。
⑦ PC寄存器
他也是每个线程独有的空间,JVM会为每一个线程创建一个独有的PC寄存器。
任意时刻,JVM总在调用一个方法,若这个方法不是本地方法,PC寄存器就指向这个方法;若这个方法是本地方法,那么PC寄存器的值就是undefined。
⑧ 执行引擎
执行引擎是Java虚拟机最核心的一个组件,它负责来执行Java的字节码文件。
现代虚拟机为了提高执行效率,会使用JVM里的即时编译器将字节码编译成机器码(即可以被处理器理解的代码)之后再执行。
2 . 设置Java虚拟机的参数
① -Xmx:这个参数可以指定Java虚拟机为当前程序开辟的最大堆内存的容量。
我们可以在启动程序的run configration中配置,也可以在eclipse的init文件中配置。
② -Xss:这个参数可以指定JVM为当前程序开辟的Java栈内存的容量。
3.函数如何调用:出入Java栈
前边我们讲过,Java的栈是属于线程私有的,线程执行的基本行为就是函数的调用,每次函数调用的数据都是通过Java栈传递的。
Java的栈与数据结构上的栈有着类似的含义,都是一块先进后出的数据结构,只支持出栈和入栈两种操作。
栈中存储的是栈帧,每一次函数的调用,都会有一个与之对应的栈帧被亚入栈中,每一个函数调用的结束,都会有一个栈帧被弹出Java栈。
当前正在实行的函数对应的帧就是顶部的帧。
不论方法是执行成功返回,还是抛出异常,都会导致栈帧被退出。
4.栈帧
一个栈帧中至少要包含局部变量表、操作数栈和帧数据区。
① 局部变量表
用于保存函数的局部变量和参数变量。这些变量只在当前函数中有用,随着函数执行完毕,栈帧就被销毁,那么栈帧中的局部变量表也会随着销毁。
② 操作数栈
他也是一种先进后出的栈,主要功能是保存计算过程中的中间结果,同时作为计算过程中临时变量的存储空间。进行参数的传递。
③ 帧数据区
除了局部变量表和操作数栈之外,Java栈帧还需要一些数据来支撑常量池解析、正常方法返回和异常处理等。
大部分Java字节码指令都需要进行常量池访问,在帧数据区中保存着访问常量池的指针,方便程序访问常量池。
5.栈上分配
栈上分配是JVM提供的一种优化技术,基本思想是对于那些线程私有的对象,可以将他们打散分配在栈上,而不是堆中。好处是函数结束后可以自行销毁,而不需要垃圾回收器的介入,从而提高系统的性能。
对于大量的零散小对象,栈上分配提供了一种很好的对象分配优化策略,栈上分配速度快,并且可以有效的避免垃圾回收带来的负面影响,但由于和堆空间相比,栈空间较小,因此对于大对象不适合在栈上分配。
6.方法区
方法区和堆一样也是线程共享的。
内部存储了类的信息,如属性,方法和常量池等。
方法区的大小决定了程序中可以有多少个类。
在JDK1.6和JDK1.7中,方法区叫做永久区(Perm)。永久区可以使用参数-XX:PermSize和-XX:MaxPermSize指定,默认值为64M。
需要注意的是:当程序中使用了动态代理,那么有可能会在运行时生成大量的类,就需要为方法区设置一个合适的值确保不会发生内存溢出的情况。
在JDK1.8中,永久去已经被彻底废除,取而代之的是元数据区,元数据区大小可以使用参数-XX:MaxMetaspaceSize指定,这时元数据区相当于一块堆外的直接内存
与永久区不同的是,若不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存来存放Java类的信息。异常:OutOfMemoryError:Metaspace