26 JVM基础

1.1 java虚拟机的运原理

所谓虚拟机,就是一台虚拟的机器,它是一款软件,用来执行一系列虚计算指令,大体上虚拟机可以分为系统虚拟机和程序虚拟机VMare就属于系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。程序虚拟机典型代表就是java虚拟机,它专门为执行单个计算机程序而设计,在kava虚拟机中执行的指令我们称为java字节码指令。无论是程序虚拟机还是系统虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。

 

1.2 认识java虚拟机的基本结构

 26 JVM基础

1、类加载子系统:负责从文件系统或者网络中加载CLass信息,加载的信息存放在一块称之为方法区的内存空间

2、方法区:就是存放类信息、常量信息、常量池信息、包括字符串字面量和数字常量等

3、Java堆:在java虚拟机启动的时候建立java,它是java程序最主要的内存工作区域,几乎所有的对象实例都存放到java堆中,堆空间是多线程共享的。

4、直接内存:javaNIO库允许kava程序使用直接内存,从而提高性能,通常直接内存速度会优于java堆。读写频繁的场合可能就考虑使用过。

 

5、java栈:每个虚拟机线程都有一个私有的栈,一个线程的java栈在线程创建的时候就被创建了。java栈中保存着局部变量、方法参数、同时java的方法调用、返回值。

6、本地方法栈:和java栈非常类似,最大的不同为本地方法栈用于本地方法调用。Java虚拟机允许java直接调用本地方法(通常使用c编写)。

 

7、垃圾收集系统:是java的核心,也是必不可少的,java有一套自己进行垃圾清理的机制,开发人员无需手工清理。

 

8、PCProgram Counter)寄存器也是每个线程私有的空间java虚拟机会为每个线程创建PC寄存器,在任意时刻,一个java线程总是在执行一个方法,这个方法被称为当前方法,如果当前方法不是本地方法,PC寄存器就会执行当前正在被执行的指令,如果是本地方法,则PC寄存器值为undefined,寄存器皴法如当前执行环境指针、程序计数器、操作栈指针、计算的变量指针等信息。

9、虚拟机最核心的组件就是执行引擎了,它负责执行虚拟机的字节码,一般用户先进行编译称机器码后执行

 

1.3 堆、栈、方法区概念和联系

堆解决的是数据存储的问题,即数据怎么放,放在哪?

栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。

方法区则是辅助堆栈的块永久区,解决堆栈信息的产生,最先决条件

我们创建一个新的对象,User:那么User类的一些信息(类信息、静态信息)都存在于方法区中

User类被实例化出来之后,被存储在java堆中,一块内存空间。

当我们去使用的时候,都是使用User对象的引用,形如User user = new User();

这里的user就是存放在java中的,即User真实对象的一个引用。

 

 26 JVM基础

 

 

 

 

 

 

 

1.4 辨清java

Java堆是和java应用程序关系最密切的内存空间,几乎所有的对象都存在其中,并且java堆完全是自动化管理的,通过垃圾回收机制,垃圾对象会自动清理,不需要显示的释放。

根据垃圾回收机制不同,java堆可能拥有不同的结构。最常见的就是将整个java堆分为新生代和老年代。其中新生代存放新生的对象,老年代存放老年的对象。

新生代分为eden区、s0区、s1区,s0s1也被称为fromto区,它们是两块大小相等并且可以互换角色的空间。(采用复制算法进行垃圾回收)

绝大多数情况下,对象首先分配在eden区,在一次新生代会后,如果对象还存活则进入s0或者s1区,之后没经过几次新生代的回收,如果对象还存活其年龄就加一。当对象的年龄到达一定值后就进入老年代。

 

 26 JVM基础

 

1.4 java

java栈是一块线程私有的内存空间,一个栈,一般有三部分组成:局部变量表、操作数栈和帧数据区。

局部变量表:用于报错函数的参数及局部变量

操作数栈:主要保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。

帧数据区:除了局部变量表和操作数栈以外ia,栈还需要一些数据来支持常量池的解析,这里帧数据区保存着访问常量池的指针,方便程序访问常量池,另外,当函数返回或者出现异常时,虚拟机必须有一个异常处理表,方便发送异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。

 

Java方法区中,如果定义的类太多,导致方法区溢出,虚拟机同样会抛出内存溢出错误。

 

 

 

 

 

 

 

1.5 虚拟机参数

在虚拟机运行的过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定的帮助,为此,虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行java虚拟机,就可以在系统运行时打印相关日志,用于分析实际问题。我们进行虚拟机参数配置,其实主要就是围绕着堆、栈、方法区进行配置。

 

1.5.1 堆分配参数()

-XX:+PrintGC 使用这个参数,虚拟机启动后,只要遇到GC就会打印日志

-XX:+UseSerialGC 配置串行回收器

-XX:+PrintGCDetails可以查看详细信息,包括各个区的情况

-Xms:设置java程序启动时初始化堆的大小

-Xmx:设置java程序能获得的最大堆大小

-Xmx20m -Xms5m -XX:+PrintCommandLineFlags:可以将隐式或者显示传给虚拟机的参数输出

 

总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小设置相等,这样的好处是可以减少程序运行时的垃圾回收次数,从而提高性能。

 

1.5.2 堆分配参数()

新生代的配置

-Xmn:可以设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个参数对系统性能以及GC行为有很大的影响,新生代大小一般会设置整个堆空间的1/31/4左右。

-XX:SurvivorRatio:用来设置新生代中eden空间和from/to空间的比例。含义:-XX:SurvivorRatio=eden/from=eden/to

总结:不同的堆分布情况,对系统执行会产生一定的影响,在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数。

除了可以设置新生代的绝对大小(-Xmn),还可以使用(-XX:NewRatio)设置新生代和老年代的比例:-XXNewRatio=老年代/新生代

 

 

 

 

 

 

 

 

 

 

 

1.6 垃圾回收概念及其算法

 

垃圾回收(Garbage Collection,简称GC),GC中的垃圾指的是存在与内存中、不会再被使用的对象,而回收就是相当于把把垃圾清除。

垃圾回收有很多算法:引用计数法、标记压缩法、复制算法、分代分区的思想。

 

引用计数法:其核心就是在对象被其他所引用时计数器就加1,而当引用生效时则减一。但是这种方法有非常严重的问题:无法处理循环引用的情况,还有就是每次进行加减操作比较浪费系统性能。

标记清除法:就是分为标记和清除两个阶段进行处理内存中的对象,这种方式也有比较大的弊端,就是空间碎片问题垃圾回收后的空间不是连续的,不连续的内存空间的工作效率要低于连续的内存空间。

复制算法:其核心思想就是将内存空间分为两块,每次只是用其中一块,在垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存块中所有的对象。反复去交换两个内存的角色,完成垃圾回收。(java堆的新生代采用的就是这种算法)

标记压缩法:标记压缩法在标记清除法的基础上做了优化,把存活的对象压缩到内存一端,而后进行垃圾清理java拉年代使用的就是标记清除法)

 

为什么新生代和老年代采用不用的算法?

因为新生代的GC比较频繁,对象不稳定;而老年代的对象比较稳定。所以老年代的GC次数比较少。这样的话,因为老年代需要清除的少,采用压缩的话将大部分对象压缩到一端,也就是清理的空间就比较少了。如果老年代采用复制算法的话,因为老年代的存活率比较高,这样就要把大部分的对象复制到另一块区域,这样效率会比较低。

分代算法:java堆的新生代和老年代就是这种思想。

分区算法:主要就是紧啊整个内存分为N个小块,每个小空间都可以独立使用,这样细粒度的控制一次回收都是针对这些小空间,而不是整个内存空间

 

 

 

 

1.6 垃圾回收时的停顿现象

垃圾回收的任务是识别和回收垃圾对象进行内存清理的,为了让垃圾回收器可以高效的实行,大部分情况下,会要求系统进入一个停顿状态。停顿的目的是终止所有的线程,只有这样系统才不会有新的垃圾产生,同时停顿保证了系统状态在某一个瞬间的一致性,也有益于更好的标记垃圾对象。因此在垃圾回收时,都会产生应用程序的停顿。

 

 

1.7 TLAB

TLAB全称时Thread Local Allocation Buffrr即线程本地分配缓存,从名字上看是一个线程专用的内存分配区域,是为了加速对象分配而生的。每一个线程都会产生一个TLAB,该线程独享的工作区域java虚拟机使用这种TLAB区来避免多线程冲突问题,提高了对象的分配的效率。TLAB空间一般不会太大,当大对象无法在TLAB分配时,则会直接分配到堆上。

 

1.8 对象创建流程图

一个对象创建在什么位置,我们的jvm会有一个比较细节的流程,根据数据的大小,参数的设置,决定如何创建分配,以及其位置。

 26 JVM基础

 

1.9 垃圾收集器

串行垃圾回收器:

指使用单线程进行垃圾回收的回收器。每次回收时,串行回收器只有一个工作线程。可以在新生代和老年代使用,根据作用于不同的堆空间,分为新生代串行和老年代串行。

 

并行垃圾回收器:

可以使用多个线程同时进行垃圾回收。

ParNew回收器是一个工作在新生代的垃圾收集器,它只是简单的将串行回收器多线程化,它的回收策略和算法和串行回收器一样。

新生代ParallelGC回收器使用了复制算法的收集器,也是多线程独占形式的收集器,它非常关注系统的吞吐量。

 

*CMS回收器

Concurrent Mark Sweep意为并发标记清除,他使用的是标记清除法。主要关注系统的停顿时间。可以配置并发线程数量。

CMS并不是独占的回收器,也就是SMS回收的过程中,应用程序仍然在不停的工作,当然新的垃圾会产生,这时需要考虑内存的容量。

CMS不会等到应用程序饱和的时候再回收,而是到达某个阀值后进行回收。

标记清除法有个缺点就是会产生内存碎片,所以可以进行配置,使得在CMS回收完成后进行一次碎片整理,然后CMS回收多次后进行一次内存压缩。

 

G1回收器

G1属于分代垃圾回收器,区分新生代和老年代。它并不要求edenfromto、老年代的内存连续,使用了分区算法。