前一段时间总结了spring和springmvc相关的知识,面试中常问到的除了这些基本的框架之外,还有底层的基础知识,比如与java虚拟机相关的知识点,这一部分也是面试中经常问到的,在面试中高级java工程师的时候,这一部分是很重要的一个点,倘若一个程序员在这一块没有了解或者看过学习过相关的知识,那么他的基础就是相对薄弱的,面试成功的可能性也会降低很多.
这篇文章会把java的整体运行结构和jvm的关系做个梳理,但是不再用大篇幅的文字叙述的内容,这样不容易记忆,而且容易产生厌看的情绪.所以我决定使用采用绘图+少部分文字描述为主.
一、弄明白java的整体运行结构与jvm的关系
1. jvm是什么?
Java虚拟机(英语:Java Virtual Machine,缩写为JVM),一种能够运行Java bytecode的虚拟机,以堆栈结构机器来进行实做。最早由太阳微系统所研发并实现第一个实现版本,是Java平台的一部分,能够运行以Java语言写作的软件程序Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码-字节码,就可以在多种平台上不加修改地运行。通过对中央处理器CPU所执行的软件实现,实现能执行编译过的Java程序码与应用程序)。
作为一种编程语言的虚拟机,实际上不只是专用于Java语言,只要生成的编译文件匹配JVM对加载编译文件格式要求,任何语言都可以由JVM编译运行。此外,除了甲骨文,也有其他开源或闭源的实现。–摘自维基百科
2.java运行过程与jvm关系
上图能够清晰的展示我们从新编译一个java类到jvm中执行的全部流程,对堆栈等地方的功能做一个解释:
- 堆:java的引用传递实现,依靠的就是堆内存,同一块堆内存可以被不同的栈内存所指向;
- 栈:程序运行的单位,里面存储的信息都与当前的线程有关系,包括局部变量,程序的运行状态,方法返回值等;
- 方法区:在进行递归调用时,所保存的堆栈内容,它由局部变量表,操作数栈,当前方法所属的类的运行时常量的引用,返回地址等;
- 程序计数器:一块非常小的内存空间,主要用来做一个计数操作,对象的晋升问题(关系到垃圾回收(GC)).
二、堆内存组织结构以及与内存有关的参数设置(优化)
在整个jvm运行时数据区,要对jvm进行优化,那么堆内存是重点优化对象.原因是栈本身所占的内存比率很小,而java中所有new对象全部放在堆内存区域.那么对这些对象的回收控制策略就非常重要.
1.堆内存的内部结构
上图展示了堆内存的内部结构,值得注意的是,在1.8之前和之后,java的永久代被取消,被元空间所代替(元空间就是电脑本省的物理内存),下面对各个区的作用做简单的解释:
年轻代:
- Eden区:新生的小对象,每当使用关键字new的时候,默认都会在此空间进行对象创建,如果创建的对象过多,那么最终的的结果就是Eden区的空间爆满,此时会发生晋级操作(在经历若干次minorGC后还保留的对象,晋升到存活区)
- 存活区:minorGC存活的对象保存的区域,存活区有两块空间S0和S1,有一块始终为空,该区域保存对象向老年代晋升
(停止-复制算法)
;
老年代:经历了数次GC之后还保留的对象,这些对象经历了多次GC仍然存活,但是也有可能在接下来的某一次被清除掉,同时要注意,假如是new一个很大的对象,那么是直接保存到老年代来,如果老年代空间不够了,会出现MajorGC(FullGC)进行老年代的清理,非常耗费性能(不建议使用system.gc()的原因
);
在发生MajorGC的时候,jvm会检查每次晋升入老年代的对象的大小是否大于老年代剩余空间的大小,若大于,直接触发一次FullGC,否则可以自定义是否允许担保失败(关键字设置:XX:+HandlePromotionFailure
)(标记-清理算法
);
元空间(永久代):jdk1.8之后,取消了永久代,变成了元空间,不再在堆内存里面保存类,字符串常量等,采用了元空间之后,不会再出现堆溢出的异常.
2. 重要参数
通过调整jvm的相关参数,可以优化堆内存,提高jvm的运行效率,下面对几个重要参数做一下总结:
- -Xms:设置初始化的内存分配大小,默认采用的大小为物理大小的1/64;
- -Xmx:设置最大的内存可用空间,默认采用的大小为物理大小的1/4;
- -Xmn:设置年轻代大小,默认采用的大小为物理大小的1/64;
- -Xss:设置每一个线程所占用的栈的大小
三、GC算法
在jdk1.7之后,正式发布了G1回收算法;
在此之前,GC算法的发展进程如下:
- Serial(串行)收集器
在jdk1.3.1之前,java虚拟机仅仅能使用Serial收集器。 Serial收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。 - Parallel(并行)收集器
Parallel收集器也称吞吐量收集器,相比Serial收集器,Parallel最主要的优势在于使用多线程去完成垃圾清理工作,这样可以充分利用多核的特性,大幅降低gc时间。 - CMS(并发)收集器
CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。 - G1(并发)收集器
G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
以上就是GC算法发展史,具体的各个算法有什么不同,暂时还没有特别深入的研究和比较,后续会再开一篇补上.
总结
这篇文章对java运行的流程和jvm的关系,以及jvm的内部结构,和jvm的堆内存优化,做了一个总结,但是GC算法是一个大头,一篇文章的篇幅实在是无法详述,后续再接再厉,争取把这一快吃透.