JVM相关知识点整理
平时在开发Java程序时,没有仔细查看过jvm的相关知识,与是来整理这块的知识点。
JDK
所谓的JDK是指程序开发的最小环境,基本上Java程序设计语言、Java虚拟机、Java API类库这三部分组成了JDK
JRE
JRE则是JAVA运行的标准环境,包含Java的api和java的虚拟机组成。
运行时数据区域的分布
属于线程私有的数据
- 程序计数器
程序计数器表示当前线程执行的字节码的行号指示器,JAVA的方法才有这个值,如果调用本地的native方法则该值是不存在的。 - 虚拟机栈
每个方法都会创建一个栈帧,主要存储操作数栈、局部变量表、动态链接,方法出口等数据。
属于线程共享的数据
- 堆
所谓的堆可以简单理解为平时我们new对象时存放的区域,它分为新生代和老年代,还有叫做Eden区,Servior区。 方法区
方法区存放了虚拟机加载类的信息,静态变量,常量等数据。运行时常量池
用于存放编译时生成的各种字面常量和符合引用。比如Stirng s=”hello” 也是直接存放在常量池中的。
笔者认为以上是JVM中最基础的内容了,那么如何来模拟堆和栈的溢出呢,来看看对应的代码实现,以下代码都是基于JDK1.8实现的。
哪些对象需要回收
要回收的垃圾指的是不可能再被任何途径使用的对象,需要怎么样才能找到这些对象呢?
- 引用计数法
它的大概思想是每当一个地方引用这个对象时,计数器值+1,当引用失效时,计数器则-1,但是Java虚拟机并没有采用这种算法来进行判断,来看下面一段测试代码:
1 | public Object instance = null; |
我们以虚拟机参数-XX:+PrintGCDetails -XX:+UseSerialGC为例查看gc日记:
1 | [Full GC (System.gc()) [Tenured: 0K->638K(87424K), 0.0055890 secs] 10995K->638K(126720K), [Metaspace: 3204K->3204K(1056768K)], 0.0056711 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] |
可以看到[Tenured: 0K->638K(87424K), 0.0055890 secs] 10995K->638K(126720K) 意味着虚拟机有对这两个对象的引用进行垃圾回收。
- 可达性分析算法
所谓的可达性指的是通过一系列的“GC Roots”的对象作为起点,从这些节点开始向下搜索,所有走过的路径作为引用链,当一个对象到GC roots没有任何引用的时候就会被回收。
用白话来可以这样形容:比如JAVA中的方法执行与结束是存放在栈帧中的,如果栈帧中存放对象A和对象B,假如A依赖于B,那么A和B就组成一对引用链,不会被回收。如果A没有引用B,且B没有被其他的对象引用,此时B是可以被回收的。
GC Roots包含的对象包含下面几种:
- 虚拟机栈
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中(Native方法)引用的对象。
垃圾回收算法
垃圾回收算法包括以下几种:
- 标记清除算法:该算法是这样的,首先需要标记出所有需要回收的对象,标记完成之后统一回收所有被标记的对象,这种算法效率不会很高,而且清除之后会造成空间不连续,导致程序运行到后期需要分配大对象的时候堆的空间不够抛出异常。
- 复制算法:该算法主要是把内存分成两块来管理,每次只会用到其中一块,当其中一块使用完了,就使用另一块的空间,然后再把使用过的一块的内存清除掉。这样只要对整个半区进行操作,该算法能很好管理内存的使用,不会造成内存碎片,但是空间有限。HotSpot虚拟机默认的使用Eden区和Survivor区的比例为8:1,Survivor区又分成From和To两块区域,每次只会用到其中一块。在垃圾回收时,如果Survivor区不能存放幸存的对象,此时需要依赖老年代进行担保。
现在商业虚拟机大部分都是这两种算法的整合,如果有大量对象死去,需要回收,则采用复制算法,复制成本低。如果对象的成活率高,需要回收的垃圾比较少,此时就采用标志整理算法,剔除不需要的对象。
总结
本章主要介绍Java虚拟机的一些基础知识,包括虚拟机的内存分布及回收算法,这些知识点在面试高级岗位中都有可能会被问到,需要好好理解。