java虚拟机深入研究_是时候深入了解JAVA虚拟机了!

垃圾回收

Java语言中一个显著的特点就是引入了内存自动回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得java程序员在编写程序的时候不再考虑内存管理(委托给JVM管理)。由于有个垃圾回收机制,java中的对象弱化了作用域的概念,只有对象的引用才有作用域。垃圾回收可以有效防止内存泄露,有效地使用空闲的内存。

程序计数器、 虚拟机栈、 本地方法栈3个区域随线程而生,随线程而灭;栈中的栈帧随着方法的进入和退出而有条不紊地执行着出栈和入栈操作。 每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的。因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束时,内存自然就跟随着回收了。 而Java堆和方法区则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的是这部分内存。

垃圾回收原理

Java语言规范没有明确地说明JVM使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做2件基本的事情:

(1)发现无用信息对象;

(2)回收被无用对象占用的内存空间,使该空间可被程序再次使用。

引用计数法

概述:引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。

可达性分析

在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

在Java语言中,可作为GC Roots的对象包括下面几种:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中类静态属性引用的对象。

方法区中常量引用的对象。

本地方法栈中JNI(即一般说的Native方法)引用的对象。

注意:

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时它处于“缓刑”阶段,要正真宣告一个对象“死亡”,至少要经历两次标记过程。如果对象在经历可达性分析后发现没有与GC roots的引用链,那它将会被第一次标记并进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过,则虚拟机将这两种情况视为“没有必要执行”。如果该对象被判定为有必要执行finalize()方法,那么这个对象将会被放到一个叫做F-Queue的队列中,并在稍后在一个由虚拟机自动建立的低优先级的Finalizer线程负责运行,但是虚拟机并不“承诺”会等待它执行结束。finalize()方法是对象逃脱死亡命运的最后一次机会。

很多人认为方法区(或者HotSpot虚拟机中的永久代)是没有垃圾收集的,java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾收集。但是,GC仍然会对方法区进行垃圾收集,收集的内容包括无用的类和废弃常量。

垃圾回收算法

标记-清除算法

先标记出哪些对象需要回收,再做清理。没啥突出优点,缺点是,清除后会产生大量的内存碎片。具体如下:

f_612211e22998c7f138956bf56ae4b327.png

标记-整理算法

先标记,再把所有存活的对象向一端移动,然后直接清理端边界意外的内存。具体如下:

f_c9c456f41d309b07016aea27307a29c1.png

复制算法

该算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它开始时把堆分成 一个对象面和多个空闲面, 程序从对象面为对象分配空间,当对象满了,基于copying算法的垃圾 收集就从根集中扫描活动对象,并将每个 活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。一种典型的基于coping算法的垃圾回收是stop-and-copy算法,它将堆分成对象面和空闲区域面,在对象面与空闲区域面的切换过程中,程序暂停执行。

分代回收算法

现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。这种算法没什么特别的,无非是前面算法的结合罢了,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法。

垃圾回收器

垃圾收集器就是上面讲的理论知识的具体实现了。不同虚拟机所提供的垃圾收集器可能会有很大差别,JDK7/8后,HotSpot虚拟机所有收集器及组合(连线),如下图:

f_356872dd23d767700327a2b619a2b973.png

f_f6db17df55f82f01f3d573ce9d0de820.png

ZGC

在JDK 11当中,加入了实验性质的ZGC。它的回收耗时平均不到2毫秒。它是一款低停顿高并发的收集器。ZGC几乎在所有地方并发执行的,除了初始标记的是STW的。所以停顿时间几乎就耗费在初始标记上,这部分的实际是非常少的。那么其他阶段是怎么做到可以并发执行的呢?ZGC主要新增了两项技术,一个是着色指针Colored Pointer,另一个是读屏障Load Barrier。ZGC是一个并发、基于区域(region)、增量式压缩的收集器。Stop-The-World阶段只会在根对象扫描(root scanning)阶段发生,这样的话GC暂停时间并不会随着堆和存活对象的数量而增加。

ZGC的设计目标:

TB级别的堆内存管理;

最大GC Pause不高于10ms;

最大的吞吐率(Throughput)损耗不高于15%;

关键点:GC Pause(停顿)不会随着 堆大小的增加 而增大。

说明:

ZGC虽然目前还在JDK 11还在实验阶段,但由于算法与思想是一个非常大的提升,相信在未来不久会成为主流的GC收集器使用。ZGC在jdk11支持,ZGC目前仅适用于Linux / x64。和G1开启很像,用下面参数即可开启:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

关键技术:

着色指针(Colored Pointer):ZGC利用指针的64位中的几位表示Finalizable、Remapped、Marked1、Marked0(ZGC仅支持64位平台),以标记该指向内存的存储状态。相当于在对象的指针上标注了对象的信息。注意,这里的指针相当于Java术语当中的引用。在这个被指向的内存发生变化的时候(内存在Compact被移动时),颜色就会发生变化。

读屏障(Load Barrier):由于着色指针的存在,在程序运行时访问对象的时候,可以轻易知道对象在内存的存储状态(通过指针访问对象),若请求读的内存在被着色了,那么则会触发读屏障。读屏障会更新指针再返回结果,此过程有一定的耗费,从而达到与用户线程并发的效果。把这两项技术联合下理解,引用R大(RednaxelaFX)的话,与标记对象的传统算法相比,ZGC在指针上做标记,在访问指针时加入Load Barrier(读屏障),比如当对象正被GC移动,指针上的颜色就会不对,这个屏障就会先把指针更新为有效地址再返回,也就是,永远只有单个对象读取时有概率被减速,而不存在为了保持应用与GC一致而粗暴整体的Stop The World。

漫谈至此,你对JAVA的内存机制是否更了解了呢?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值