您的位置:首页 > 职场人生

Java菜鸟面试突破系列之java虚拟机

2017-08-12 00:11 483 查看

Java菜鸟面试突破系列之java虚拟机

前言:JDK是java开发的必备工具箱,JDK其中有一部分是JRE,JRE是JAVA运行环境,JVM则是JRE最核心的部分。换言之,JVM是java的核心,是java可以一次编译到处运行的本质所在,使用最广泛的java虚拟机是最广泛的oracle的HotSpot JVM。Java HotSpot Client VM和Java HotSpot Server VM是JDK关于JVM的两种不同的实现,前者可以减少启动时间和内存占用,而后者则提供更加优秀的程序运行速度!

JVM由4大部分组成:ClassLoader,Runtime Data Area,Execution Engine,Native Interface。

1)ClassLoader 是负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

2)Native Interface 是负责调用本地接口的。他的作用是调用不同语言的接口给JAVA用,他会在Native Method Stack中记录对应的本地方法,然后调用该方法时就通过Execution Engine加载对应的本地lib。

3)Execution Engine 是执行引擎,也叫Interpreter。Class文件被加载后,会把指令和数据信息放入内存中,Execution Engine则负责把这些命令解释给操作系统。

4)Runtime Data Area 则是存放数据的,分为五部分:Stack,Heap,Method Area,PC Register,Native Method Stack。几乎所有的关于java内存方面的问题,都是集中在这块,故而就有了内存模型之说。

一、java虚拟机内存模型

提及java虚拟机,那么首先不得不谈的自然是java虚拟机的内存模型咯,java虚拟机内存模型是java程序运行的基础,JVM虚拟机将其内存数据分为程序计数器、虚拟机栈、本地方法栈、java堆和方法区等部分。

程序计数器即PC用于存放下一条运行的指令(线程私有的内存空间,每个线程的PC独立互不影响);java虚拟机栈(线程私有的内存空间 和java线程同时创建 用于保存方法的局部变量、结果并参与方法的调用和返回!其在运行时,使用一种叫做栈帧的数据结构保存上下文数据,栈帧中存放了方法的局部变量表、操作数栈等,这里的局部变量表中的字可能会影响GC的回收,如果这个字没有被后续代码复用,那么它所引用的对象不会被GC释放)和本地方法栈(管理本地方法的调用)用于存放函数调用堆栈信息;java堆(java运行时内存中最为重要的部分,几乎所有对象和数组都是在堆中分配空间的,java堆分为新生代和老年代两部分,新生代用于存放刚刚产生的对象和年轻的对象,老年代用于存放一直没有被回收、生存足够长的老年对象)用于存放java程序运行时所需的对象等数据;方法区(跟java堆空间类似 被所有线程共享)用于存放程序的类元数据信息,方法区也称为永久区,主要存放常量和类的定义信息。

PS:堆空间可以简单分为新生代和老年代。新生代进一步可分为eden(伊甸园 即对象刚建立存放区)、survivor s0(幸存s0空间或from空间)survivor s1(幸存s1空间或to空间)!



堆空间结构

二、JVM内存分配参数

1)设置最大堆内存:-Xmx 比如java -Xmx5M

PS:最大堆指的是新生代和老年代的大小之和的最大值 是java应用程序的堆上限。

2)设置最小堆内存:-Xms 初始堆大小

PS:最小堆空间也是JVM启动时,所占据的OS内存大小。JVM会试图将系统内存尽可能限制在-Xms中,因此当内存实际使用量触及-Xms指定的大小时,会触发Full GC。So把-Xms设置为-Xmx时,可以在系统运行初期减少GC的次数和耗时。

3)设置新生代:-Xmn(新生代大小一般设置为整个堆空间的1/4-1/3左右)

PS:在Hot Spot虚拟机 -XX:NewSize设置新生代初始大小 -XX:MaxNewSize设置新生代的最大值 -Xmn等同于设置了相同的 -XX:NewSize和-XX:MaxNewSize

4)设置持久代:-XX:MaxPermSize设置持久代最大值 -XX:PermSize设置持久代初始大小;持久代的大小直接决定了系统可以支持多少个类定义和多少常量。

5)设置线程栈:-Xss 设置线程栈的大小(设置一个较小的堆和较小的栈有助于提高系统所能承受的最大线程数)

6)堆的比例分配:-XX:SurvivorRatio用来设置新生代中 Eden空间和s0空间的比例关系,s0空间和s1空间分布称为from和to空间 它们大小是相同的职能也一样并在每次GC后会互换角色;-XX:NewRatio用来设置新生代和老年代的比例,-XX:NewRatio=老年代/新生代



三、垃圾回收算法(明天总结)

接上次没更新完的继续写完这篇文章!

我们知道,Java语言的一大特点就是可以进行自动垃圾回收处理,而无需开发人员花过多时间来关注系统资源(内存资源)的释放情况,自动垃圾回收大大减轻了开发人员的工作量,但有利必有弊,所以好好的研读一下java为我们提供的垃圾回收机制还是很有必要的。

垃圾收集器需要处理的基本问题是:

A、哪些对象需要回收?

B、何时回收这些对象?

C、如何回收这些对象?

关于这几个问题容后我再来解答,下面且先来看看几个概念以及垃圾回收的算法:

可达对象:通过根对象进行引用搜索,最终可以达到的对象。
不可达对象: 通过根对象进行引用搜索,最终没有被引用到的对象。


垃圾回收算法与思想:

1、引用计数法

引用计数法是最经典也最古老的一种垃圾回收算法,其实现非常简单:

1)对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就+1,当引用失效时,引用计数器-1,只要A的引用计数器为0,则对象A就不可能再被调用,即可被回收了。

2)一个严重的问题无法处理循环引用的情况!Eg:对象A,B,对象A中含有对象B的引用,对象B中有对象A的引用,此时,相互引用,其引用计数器都不为0,但在系统中却没有任何第3个对象引用了A或者B,也就是说,这两个对象没用了,应该被垃圾回收器回收才对,但由于垃圾对象之间相互引用,从而导致垃圾回收器无法识别,引起内存泄露!So,在Java的垃圾回收器中,没有使用这个算法!!!

PS:由于无法处理循环引用的问题,引用计数法实现垃圾回收是不可行的,不适合用于JVM垃圾回收!


2、标记-清除算法(mark-sweep)

标记-清除算法是现代垃圾回收算法的思想基础,其将垃圾回收分为两个阶段:标记阶段和清除阶段。在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象,So,未被标记的对象就是未被引用的垃圾对象;然后在清除阶段,清除所有未被标记的对象。标记-清除算法最大的问题就是:空间碎片!



这里图是用截图软件大致描的,大概形容下就是,不用太在意!遗漏了根节点,大家知道就好,由图可知,当垃圾对象被回收之后,回收后的空间是不连续的!我们知道,在堆空间分配过程中,尤其是大对象的内存分配,不连续的内存空间的工作效率要低于连续的空间,So这也是该算法最大的缺点!!!!

PS:标记-清除算法先通过根节点标记所有可达对象,然后清除所有不可达对象,完成垃圾回收!其最大问题在于:不连续的内存空间,空间碎片!!


这里补充一张完整的标记清除算法的工作图:见下:


3、复制算法(copying)-年轻代 新生代

思想:将原有的内存空间分为两块,每次只用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收!



复制算法工作图

根据复制算法的核心思想,我们很清楚,如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量就相对不多,因此,其效率很高效率(当然如果不是如果,恰恰相反的话,那效率会很低甚至引起系统卡顿的);另一方面,由于对象是在垃圾回收过程中统一复制到新的内存空间中,So可以确保回收后的内存空间是没有碎片的!!但是,其代价缺点是将系统内存折半!!!

结合复制算法的思想,我们可以在Java的新生代串行垃圾回收器中,使用复制算法。我们知道,新生代分为Eden空间,from空间和to空间3个部分。其中from和to空间可以作为复制的两块大小相同、地位相等,且可进行角色互换的空间块。from和to空间也称为Survivor空间,即幸存者空间,用于存放未被回收的对象。

在垃圾回收时,eden空间中存活的对象会被复制到未使用的survivor空间中(假设是to),正在使用的survivor空间(假设是from)中的年轻对象也会被复制到to空间中(大对象,或者老年对象会直接进入老年代,如果to空间已满,则对象也会直接进入老年代)。此时,eden空间和from空间中的剩余对象就是垃圾对象,可以直接清空,to空间则存放此次回收后的存活对象。

这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间浪费。当所有存活对象都被复制到survivor区后(图中为to)。简单的清空eden区和无效的survivor区(图中为from)即可。



改进的复制算法

复制算法比较适用于新生代。因为在新生代,垃圾对象通常会多于存活对象。复制算法的效果会比较好。


4、标记-压缩算法(Mark-Compact)-老年代

老年代中,显然存活对象较多,垃圾对象较少,在这种情况下,复制算法的复制成本必将提高,导致效率低下!

标记压缩算法是一种老年代的回收算法。它在标记清除算法的基础上做了一些优化,和标记清除算法一样,标记压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,他并不是简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后清理边界外所有的空间这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比较高。



标记-压缩算法工作图

5、分代算法

根据垃圾回收对象的特性,使用合适的算法回收,分代算法就是基于这种思想。新生代使用复制算法,老年代使用标记压缩算法或标记清除算法,以提高垃圾回收效率。

分代的思想被现有的Hot spot虚拟机广泛使用,几乎所有垃圾回收都区分年轻代和老年代


结束语:写到这里,主要是介绍了几种常用的垃圾回收算法的思想和实现,现在我们回到前文提到的三个问题,想必大家心里都已经有了自己的答案,小生在这里就自己的理解小小的回答一番,不当之处,欢迎大家指正!

我们知道,垃圾回收的基本思想是考察每一个对象的可及性,即从根节点开始是否可以访问到这个对象,如果可以,则说明当前对象正在被使用,如果从所有的根节点都无法访问到某个对象,说明对象已经不再使用了,一般来说,此对象需要被回收。但事实上,一个不可达的对象有可能在某一条件下“复活”自己,如果这样,那么对他的回收就是不合理的,为此,需要给出一个对象可及性的定义,并规定在什么状态下,才可以安全的回收对象。

A、哪些对象需要回收?

不可达对象。。。。

B、何时回收这些对象?

当监听器检测到对象不可达,便可回收,但具体回收时间,由垃圾回收器决定。

C、如何回收这些对象?

由垃圾回收器回收。

--by 小仇哥
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: