1虚拟机概述
所谓虚拟机,就是一台虚拟机的机器,它是一款软件,用来执行一系列虚拟计算机指令,大体上虚拟机分为系统虚拟机(VMWare、Visual Box等) 和
程序虚拟机(Java虚拟机等)。
系统虚拟机:他们是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。
程序虚拟机:典型代表就是JVM,它专门为执行单个计算机程序而设计。
无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
2 jvm的基本结构
2.1 基本概念说明
1)类加载子系统:负责从文件系统或者网络中加载Class信息,加载的信息存放在一块称之为方法区的内存空间。
2)方法区:存放类信息,常量信息、常量池信息、包括字符串字面量和数字常量等。
3)java堆:
在java虚拟机启动的时候建立java堆,它是java程序最主要的内存工作区域,几乎所有的对象实例都存放到java堆中,对空间是所有线程共享的。
4)栈: 相对于Java Heap来讲,Java
Stack是线程私有的。她的生命周期与线程相同(每个虚拟机线程都有一个私有的栈,一个线程的java栈在线程创建的时候被创建)。Java
Stack描述的是Java方法执行时的内存模型,每个方法执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、方法参数、返回值、操作数栈、动态链接
、方法出口等信息。从下图从可以看到,每个线程在执行一个方法时,都意味着有一个栈帧在当前线程对应的栈帧中入栈和出栈。
图中可以看到每一个栈帧中都有局部变量表。局部变量表存放了编译期间的各种基本数据类型,对象引用等信息。
5)本地方法栈:本地方法栈(Native Stack)与Java虚拟机站(Java
Stack)所发挥的作用非常相似,他们之间的区别在于虚拟机栈为虚拟机栈执行java方法(也就是字节码)服务,而本地方法栈则为使用到本地方法(通常使用C编写)服务
6)PC(Program Count
Register)寄存器也是每个线程私有的空间,java虚拟机会为每个线程创建PC寄存器,在任意时刻,一个java线程总是在执行一个方法,这个方法被称为当前方法,如果当前方法不是本地方法,PC寄存器就会执行当前正在被执行的指令,如果是本地方法,则PC寄存器值为undefined,寄存器存放如当前执行环境指针、程序计数器、操作栈指针、计算的变量指针等信息。
7)垃圾回收器: 垃圾收集系统是java的核心,java有一套自己进行垃圾清理的机制,开发人员无需手工清理,下面会进行详解
8)执行引擎: 虚拟机最核心的组件,它负责执行虚拟机的字节码。一般会先进行编译成机器码后执行
9) 直接内存:Java的NIO库允许java程序使用直接内存,从而提高性能,通常直接内存速度会优于java堆。读写频繁的场合可能会考虑使用。
2.2 java堆详解
java堆是和java应用程序关系最为密切的内存空间,几乎所有的对象都存放在其中,并且java堆完全是自动化管理的,通过垃圾回收机制,垃圾对象会自动清理,不需要显示地释放。
根据垃圾回收机制不同,java堆有可能拥有不同的结构。最为常见的就是将整个java堆分为新生代和老年代
。其中新生代存放新生的对象或者年龄不大的对象,老年代则存放老年对象。
新生代分为eden区、s0区(from区)、s1区(to区),s0和s1是两块大小相等并且可以互换角色的空间。
绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还存活,则会进入s0或者s1区,之后每经过一次新生代回收,如果对象存活则它的年龄+1,当对象达到一定的年龄后,则进入老年代。
永久代也是在堆内存中保存的,但是永久代不会被回收。
jdk 8 以后的jvm,移除了永久代,取而代之的是metaspace(元空间)。
元空间是JDK 1.8 之后才有的,功能和永久代类似。唯一到的区别是,永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制。
2.3 java栈详解
java栈是一块线程私有的内存空间,一个栈,一般有三部分组成:局部变量表、操作数栈和帧数据区
局部变量表:用于保存函数的参数及局部变量。
操作数栈:主要保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
帧数据区
:除了局部变量表和操作数栈以外,栈还需要一些数据来支持常量池的解析,这里帧数据区保存着访问常量池的指针,方便程序访问常量池,另外,当函数返回或者出现异常时,虚拟机必须有一个异常处理表,方便发生异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。
2.4 java方法区
java方法区和堆一样,方法区是一块所有线程共享的内存区域,,它保存系统的类信息,比如类的字段、方法、常量池等。方法区的大小决定了系统可以保存多少个类,如果系统定义大多的类,导致方法区溢出。虚拟机同样会抛出内存溢出错误,方法区可以理解为永久区(Perm).
2.5 总结
1) 堆解决的是数据存储的问题,即数据怎么放,放在哪儿。
2) 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。
3)方法区则是辅助堆栈的一块永久区,解决堆栈信息的产生,是先决条件。
案例说明:
我们创建一个新的对象User: 那么User类的一些信息(类信息、静态信息都存在于方法区中),
而User类被实例化出来之后,被存储到java堆中,一块内存空间
当我们去使用的时候,都是使用User对象的引用,形如User user = new User();
这里的user就是存放在java栈中的,即User真实对象的一个引用。
4) 线程私有的数据区域有:
Java虚拟机栈(Java Stack)
本地方法栈(Native Stack)
5) 线程共有的数据区域有:
堆(Java Heap)
方法区
3 垃圾回收概念和其算法
3.1 垃圾回收(Garbage Collection,简称GC)
需要先知道什么是垃圾,类比日常生活中的垃圾,我们会把他们丢入垃圾桶,然后倒掉。GC中的垃圾,特指存在于内存中、不会再被使用的对象,而回收就是相当于把垃圾“倒掉”。
3.2 垃圾回收算法
1)引用计数法
:这是个比较古老而经典的垃圾收集算法,其核心就是在对象被其他所引用时计数器加1,而当引用失效时则减一,但是这种方式有非常严重的问题:无法处理循环引用的情况、还有就是每次进行加减操作比较浪费系统性能。
2)复制算法
:其核心思想就是将内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存块中所有的对象,反复去交换两个内存的角色,完成垃圾收集。(java中新生代的from和to空间就是使用这个算法)
3)标记清除法
:就是分为标记和清除两个阶段进行处理内存中的对象,当然这种方式也有非常大的弊端,就是空间碎片问题,垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。
4)标记压缩法:标记压缩法在标记清除法基础上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清理。
(java中老年代使用的就是标记压缩法)
5)分代算法:就是根据对象的特点把内存分成N块,而后根据每个内存的特点使用不同的算法。
对于新生代和老年代来说,新生代回收频率很高,但是每次回收耗时都很短,而老年代回收频率较低,但是耗时会相对较长,所以应该尽量减少老年代的GC.
6)分区算法:
其主要就是将整个内存分为N多个小的独立空间,每个小空间都可以独立使用,这样细粒度的控制一次回收多少个小空间和哪几个小空间,而不是对这个空间进行GC,从而提升性能,并减少GC的停顿时间。
3.3 垃圾收集器
在java虚拟机中,垃圾回收器不仅仅只有一种,什么情况下该使用哪种,对性能又有什么样的影响,让我们一起探讨一下:
1)串行垃圾回收器:它为单线程环境设计并且只使用一个线程进行垃圾回收,会暂停所有的用户线程。
2)并行垃圾回收器:多个垃圾回收线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景。
ParNew回收器
ParallelGC回收器
ParallelOldGC回收器
3)并发垃圾收集器:用户线程和垃圾收集线程同时执行(不一定是并行,可能交替执行),不需要停顿用户线程适用于对响应时间有要求的场景。
CMS回收器
G1回收器
如有错误,请指出。未完待续...