一、JVM简介
1.1一次编译,到处运行
java有个很突出的特性就是跨平台,只需要编译一次,就能在不同的操作系统、不同的平台上运行,也就是所谓的“一次编译,到处运行”。事实上,这个功能和java虚拟机是密不可分的。Java源文件,通过编译器,能够生产相应的.class文件,也就是字节码文件,而字节码文件又通过Java虚拟机中的解释器,编译成特定机器上的机器码,每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是Java为什么能够跨平台的原因。
一次编译,到处运行.jpg
1.2JVM概念
JVM是Java Virtual Machine的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
简单来说JVM是可运行Java代码的假想计算机
1.3JVM体系结构
jvm体系结构.jpg.class文件被class loader加载,由执行引擎来具体执行包在装载类的方法中的指令,也就是方法,而运行时数据区则是从整个计算机内存中开辟一块内存存储Jvm需要用到的对象,变量等,运行区数据有分很多小区,分别为:方法区,虚拟机栈,本地方法栈,堆,程序计数器,这个后文会详细说明。
这里需要说明的是,在运行时数据区,方法区和堆是被线程共享的数据区,而java栈、本地方法栈、程序计数器则是线程私有的。
2.运行时数据区详细说明
2.1方法区
方法区是可供各线程共享的运行时内存区域,主要存放有已被加载的类的元数据信息、静态变量、即时编译器编译后的代码等,另外在JDK1.6及之前,这里还会有运行时常量池,在1.7以后常量池以挪到堆中。
方法区、永久代、元空间
这里有三个相关但不同的概念,方法区在上面已经解释了,而永久代可以认为是方法区在HotSpot的具体实现。永久代的垃圾回收是和老年代的垃圾回收绑定的,一旦一个区域被占满,这两个区域都要进行垃圾回收,由于类及方法的信息等比较难确定其大小,对于永久代的调优是困难的,而且也给GC带来了不必要的复杂度。
在jdk1.8后,永久代被移动到了一个跟堆完全不相连的本地内存区域,也就是元空间,也就是说元空间的最大可分配空间就是系统可用内存空间。因此,我们就不会遇到永久代存在时的内存溢出错误。
2.2 堆
对于绝大多数的应用来说,这块区域是JVM所管理的内存中最大的一块,也是线程共享的区域,主要用于存放对象实例和数组,内部会划分出多个线程私有的分配缓冲区(TLAB)。
2.3 JAVA栈
java的虚拟机栈是线程私有的,其生命周期和线程一致。每个方法在执行时都会创建一个栈桢被压入栈中,栈桢中的数据主要包括以下三类:
本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
栈操作(Operand Stack):记录出栈、入栈的操作;
栈帧数据(Frame Data):包括类文件、方法等等。
每个方法从被调用到执行完,对应着一个栈桢在虚拟机中入栈到出栈的过程,举例说明,当方法A被调用时,就往栈中压入栈桢F1,如果A又调用了B方法,则产生的栈桢F2也被压入,执行完毕后,先弹出F2再弹出F1。
2.4 本地方法栈
和JAVA栈不同,本地方法栈为虚拟机执行本地方法服务。
2.5 程序计数器
线程私有,就是一个指向方法区中下一个将要执行的指令代码的指针,由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不计。
3 HotSpot虚拟机中对象的相关知识总结
3.1 对象的内存分配布局
在hotspot虚拟机中,对象主要分为三部分:对象头、实例信息以及对其填充,如下图所示:
Hotspot虚拟机对象内存分部.jpg
对象头
对象头主要包含两部分,一部分是对象自身运行时所需要的数据,例如哈希码、GC年代信息、锁状态标志、偏向线程id,线程持有的锁等等;第二部分是类型指针,指向它的类的元数据。如果是数组类型,对象头中还会有一部分用来记录数组的长度
实例数据
程序代码中所定义的各种类型的字段内容(包含父类继承下来的和子类中定义的)
对齐填充
主要是为了确保对象的大小是字节的整数倍的占位,并非必须
3.2 如何访问对象
在访问(使用)对象时,会用到java栈上本地变量表里面的reference数据来操作,具体有通过句柄访问和直接访问两种方式。
3.2.1 通过句柄访问
通过句柄访问对象.jpg3.2.2 直接访问
由于对象头上有类型指针,所以访问时看也可以通过类型指针直接访问,如图所示
指针直接访问对象.jpg4 垃圾回收
4.1 什么叫对象不可达
对于java而言,不可用不可达的对象,将会被GC回收掉,那什么是对象不可达呢?
所谓的对象不可达,是指通过一系列的“GC Roots”的对象作为起点,从这些起点,从这些节点出发所走过的路径称为引用链。当一个对象到任何GC Root都没有任何引用链时,则称为对象不可达,如下图所示:
对象不可达.jpg
可以作为GC Root的对象有:
1.栈桢中本地变量表里面的引用对象
2.方法区类的静态属性引用的对象
3.方法区常量引用对象
4.本地方法栈中JNI引用的对象
4.2 垃圾回收算法
一般有以下几种
1 标记——清除算法
标记后直接清除。缺点是效率不高、且容易产生大量空间碎片
2.复制算法
把空间分成两块,每次对其中一块GC。即当一块的内存使用完时,把存活对象copy到另一块上。缺点是空间利用率不高。
一般新生代的GC是采用的该算法,因为大多数新生代对象都熬不过第一次GC,所以没必要按1:1进行分区。一般是一块较大的Eden区和两块较小的Survivor区,比例大致是8:1:1,当GC时,将Eden区和其中一块Survivor区的存活对象copy到另一块Survivor区,超出部分直接进入老年代,然后清理Eden和Survivor区。
3.标记——整理算法
把存活的对象移动到内存的一端,然后对剩余部分进行清理。
4.3 分代回收
根据存活对象划分几块内存区,按大的来分可以分为新生代和老年代,针对不同的年代特点可以制定不同的回收算法。
5. ClassLoader的几种类型
一般分为以下三种:
-
BootstrapClassLoader 启动类加载器
加载 lib 下或被 -Xbootclasspath 路径下的类 -
ExtensionClassLoader 扩展类加载器
加载 lib/ext 或者被java.ext.dirs系统变量所指定的路径下的类 -
ApplicationClassLoader 应用程序类加载器
加载用户路径上所指定的类库
网友评论