JVM相关

Posted 买糖买板栗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM相关相关的知识,希望对你有一定的参考价值。

目录

基础

1 System.gc()

2 强引用、软引用

​3 堆外内存

4 类加载

5 类加载JVM参数调优

6 GCroots

GC算法与调优

1 Minor GC:

2 Major GC:

3 垃圾收集器 

CMS

G1


基础

1 System.gc()

这个方法执行后是立即回收内存吗?答:程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行;因为这个命令只是建议JVM安排GC运行, 还有可能完全被拒绝。 GC本身是会周期性的自动运行的,由JVM决定运行的时机,而且现在的版本有多种更智能的模式可以选择,还会根据运行的机器自动去做选择,就算真的有性能上的需求,也应该去对GC的运行机制进行微调,而不是通过使用这个命令来实现性能的优化

2 强引用、软引用


3 堆外内存

  • 堆内存完全由JVM负责分配和释放;
  • 使用堆外内存,就是为了能直接分配和释放内存,提高效率。
  • JDK5.0之后,代码中能直接操作本地内存的方式有2种:使用未公开的Unsafe和NIO包下ByteBuffer。
  •  NIO直接内存的回收,需要依赖于System.gc()。如果我们的应用中使用了java nio中的direct memory,那么使用-XX:+DisableExplicitGC一定要小心(这个参数作用是禁止代码中显示调用GC),存在潜在的内存泄露风险
  • 我们知道java代码无法强制JVM何时进行垃圾回收,也就是说垃圾回收这个动作的触发,完全由JVM自己控制,它会挑选合适的时机回收堆内存中的无用java对象。代码中显示调用System.gc(),只是建议JVM进行垃圾回收,但是到底会不会执行垃圾回收是不确定的,可能会进行垃圾回收,也可能不会。什么时候才是合适的时机呢?一般来说是,系统比较空闲的时候(比如JVM中活动的线程很少的时候),还有就是内存不足,不得不进行垃圾回收。我们例子中的根本矛盾在于:堆内存由JVM自己管理,堆外内存必须要由我们自己释放;堆内存的消耗速度远远小于堆外内存的消耗,但要命的是必须先释放堆内存中的对象,才能释放堆外内存,但是我们又不能强制JVM释放堆内存。
  • Direct Memory的回收机制:Direct Memory是受GC控制的,例如ByteBuffer bb = ByteBuffer.allocateDirect(1024),这段代码的执行会在堆外占用1k的内存,Java堆内只会占用一个对象的指针引用的大小,堆外的这1k的空间只有当bb对象被回收时,才会被回收,这里会发现一个明显的不对称现象,就是堆外可能占用了很多,而堆内没占用多少,导致还没触发GC,那就很容易出现Direct Memory造成物理内存耗光。Direct ByteBuffer分配出去的内存其实也是由GC负责回收的,而不像Unsafe是完全自行管理的,Hotspot在GC时会扫描Direct ByteBuffer对象是否有引用,如没有则同时也会回收其占用的堆外内存。
  • 使用堆外内存与对象池都能减少GC的暂停时间,这是它们唯一的共同点。生命周期短的可变对象,创建开销大,或者生命周期虽长但存在冗余的可变对象都比较适合使用对象池。生命周期适中,或者复杂的对象则比较适合由GC来进行处理。然而,中长生命周期的可变对象就比较棘手了,堆外内存则正是它们的菜。堆外内存的好处是:(1)可以扩展至更大的内存空间。比如超过1TB甚至比主存还大的空间;(2)理论上能减少GC暂停时间;(3)可以在进程间共享,减少JVM间的对象复制,使得JVM的分割部署更容易实现;(4)它的持久化存储可以支持快速重启,同时还能够在测试环境中重现生产数据

4 类加载

加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到APP ClassLoaderExtention ClassLoaderBootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是说当发现这个类没有的时候会先去让自己的父类去加载,父类没有再让儿子去加载。面试题:自己写一个String类会被加载嘛?答:不能,因为java.lang.String已经被Bootstrap ClassLoader加载了,所以App ClassLoader就不会再去加载我们写的String类了,导致我们写的String类是没有被加载的。

5 类加载JVM参数调优

JVM调优总结 -Xms -Xmx -Xmn -Xss

GCroots

所有正在运行的线程的栈上的引用变量。
所有的全局变量。
Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。我们需要注意的一点就是,通过用户自定义的类加载器加载的类,除非相应的java.lang.Class实例以其它的某种(或多种)方式成为roots,否则它们并不是roots。

GC算法与调优

1 Minor GC:

  • eden区满时,触发MinorGC,即申请一个对象时,发现eden区不够用,则触发一次MinorGC(优化:扩容新生代)
  • 在cms算法中,young gc的实现过程?先找出根对象,如Java栈中引用的对象、静态变量引用的对象和系统词典中引用的对象等待,把这些对象标记成活跃对象,并复制到to区,接着遍历这些活跃对象中引用的对象并标记,找出老年代对象在eden区有引用关系的对象并标记,最后把这些标记的对象复制到to,在复制过程还要判断活跃对象的gc年龄是否已经达到阈值,如果已经达到阈值,就直接晋升到老年代,YGC结束之后把from和to的引用互换。

2 Major GC:

  • CMS在重标记(Remark)阶段,Remark阶段是Stop-The-World,即在执行垃圾回收时,Java应用程序中除了垃圾回收器线程之外其他所有线程都被挂起,意味着在此期间,用户正常工作的线程全部被暂停下来,这是低延时服务不能接受的。新生代对象持有老年代中对象的引用,这种情况称为“跨代引用”。因它的存在,Remark阶段必须扫描整个堆来判断对象是否存活,包括图中灰色的不可达对象。新生代中对象的特点是“朝生夕灭”,这样如果Remark前执行一次Minor GC,大部分对象就会被回收。CMS就采用了这样的方式
  • 触发条件 (1)调用System.gc时,系统建议执行Full GC,但是不必然执行;(2)老年代空间不足;(3)方法区空间(Perm空间)不足;(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存;(5)由Eden区、From区向To区复制时,对象大小>To内存,把该对象转存到老年代,但老年代可用内存<该对象大小; (6)  CMS GC时出现promotion failed和concurrent mode failure(concurrent mode failure发生的原因一般是CMS正在进行,但是由于老年代空间不足,需要尽快回收老年代里面的不再被使用的对象,这时停止所有的线程,同时终止CMS,直接进行Serial Old GC);

3 垃圾收集器 

CMS

  • CMS垃圾回收器设计目的:为了避免「老年代 GC」出现「长时间」的卡顿(Stop The World)
  • CMS垃圾回收器回收过程:初始标记、并发标记、并发预处理、重新标记和并发清除。初始标记以及重新标记这两个阶段会Stop The World
  • CMS垃圾回收器的弊端:1、会产生内存碎片&&需要空间预留(CMS可以一边回收垃圾,一边处理用户线程,那需要在这个过程中保证有充足的内存空间供用户使用);2、停顿时间是不可预知的
  • 如果CMS运行过程中预留的空间不够用了,会报错(Concurrent Mode Failur e),这时会启动 Serial Old垃圾收集器进行老年代的垃圾回收,会导致停顿的时间很长

G1

  • 你可以设置一个STW的时间
  • 分区,分小区后,对这些小区回收就会比较容易控制收集的时间

以上是关于JVM相关的主要内容,如果未能解决你的问题,请参考以下文章

JVM的相关概念

jvm常用相关参数

JVM 相关知识

jvm GC日志 相关参数

JVM相关参数介绍

面试相关之 JVM &设计模式