vlambda博客
学习文章列表

JVM 大厂面试都会问,都会这么问,你能顶住么?


www.cnblogs.com/QG-whz/p/9636366.html

下面总结了 JVM 的 4 个问题,看你能顶住么?





1、JVM的内存区域是怎么划分的?















2、OOM可能发生在哪些区域上?


根据javadoc的描述,OOM是指JVM的内存不够用了,同时垃圾收集器也无法提供更多的内存。从描述中可以看出,在JVM抛出OutOfMemoryError之前,垃圾收集器一般会出马先尝试回收内存。

从上面分析的Java数据区来看,除了程序计数器不会发生OOM外,哪些区域会发生OOM的情况呢?

第一,堆内存。堆内存不足是最常见的发送OOM的原因之一,如果在堆中没有内存完成对象实例的分配,并且堆无法再扩展时,将抛出OutOfMemoryError异常。当前主流的JVM可以通过-Xmx和-Xms来控制堆内存的大小,发生堆上OOM的可能是存在内存泄露,也可能是堆大小分配不合理。

第二,Java虚拟机栈和本地方法栈,这两个区域的区别不过是虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务,在内存分配异常上是相同的。

在JVM规范中,对Java虚拟机栈规定了两种异常:1.如果线程请求的栈大于所分配的栈大小,则抛出StackOverFlowError错误,比如进行了一个不会停止的递归调用;2. 如果虚拟机栈是可以动态拓展的,拓展时无法申请到足够的内存,则抛出OutOfMemoryError错误。

第三,直接内存。直接内存虽然不是虚拟机运行时数据区的一部分,但既然是内存,就会受到物理内存的限制。在JDK1.4中引入的NIO使用Native函数库在堆外内存上直接分配内存,但直接内存不足时,也会导致OOM。

第四,方法区。随着Metaspace元数据区的引入,方法区的OOM错误信息也变成了“java.lang.OutOfMemoryError:Metaspace”。对于旧版本的Oracle JDK,由于永久代的大小有限,而JVM对永久代的垃圾回收并不积极,如果往永久代不断写入数据,例如String.Intern()的调用,在永久代占用太多空间导致内存不足,也会出现OOM的问题,对应的错误信为“java.lang.OutOfMemoryError:PermGen space”

JVM 大厂面试都会问,都会这么问,你能顶住么?

3、堆内存结构是怎么样的?


可以借助一些工具来了解JVM的内存内容,具体到特定的内存区域,应该用什么工具去定位呢?


  • 图形化工具。图形化工具的优点是直观,连接到Java进程后,可以显示堆内存、堆外内存的使用情况,类似的工具有JConsole,VisualVm等。

  • 命令行工具。这类工具可以在运行时进行查询,包括jstat,jmap等,可以对堆内存、方法区等进行查看。定位线上问题时也多会使用这些工具。jmap也可以生成堆转储文件(Heap Dump)文件,如果是在linux上,可以将堆转储文件拉到本地来,使用Eclipse MAT进行分析,也可以使用jhap进行分析。


关于内存的监控与诊断,在后面会进行深入了解。现在来看下一个问题:堆内的结构是怎么的呢?


站在垃圾收集器的角度来看,可以把内存分为新生代与老年代。内存的分配规则取决于当前使用的是哪种垃圾收集器的组合,以及内存相关的参数配置。往大的方向说,对象优先分配在新生代的Eden区域,而大对象直接进入老年代。


第一, 新生代的Eden区域,对象优先分配在该区域,同时JVM可以为每个线程分配一个私有的缓存区域,称为TLAB(Thread Local Allocation Buffer),避免多线程同时分配内存时需要使用加锁等机制而影响分配速度。TLAB在堆上分配,位于Eden中。TLAB的结构如下:


// ThreadLocalAllocBuffer: a descriptor for thread-local storage used by
// the threads for allocation.
// It is thread-private at any time, but maybe multiplexed over
// time across multiple threads. The park()/unpark() pair is
// used to make it avaiable for such multiplexing.
class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
  friend class VMStructs;
private:
  HeapWord* _start; // address of TLAB
  HeapWord* _top; // address after last allocation
  HeapWord* _pf_top; // allocation prefetch watermark
  HeapWord* _end; // allocation end (excluding alignment_reserve)
  size_t    _desired_size; // desired size (including alignment_reserve)
  size_t    _refill_waste_limit; // hold onto tlab if free() is larger than this




JVM 大厂面试都会问,都会这么问,你能顶住么?

第二、新生代的Survivor区域。当Eden区域内存不足时会触发Minor GC,也称为新生代GC,在Minor GC存活下来的对象,会被复制到Survivor区域中。我认为Survivor区的作用在于避免过早触发Full GC。如果没有Survivor,Eden区每进行一次Minor GC都把对象直接送到老年代,老年代很快便会内存不足引发Full GC。


新生代中有两个Survivor区,我认为两个Survivor的作用在于提高性能,避免内存碎片的出现。在任何时候,总有一个Survivor是empty的,在发生Minor GC时,会将Eden及另一个的Survivor的存活对象拷贝到该empty Survivor中,从而避免内存碎片的产生。新生代的内存结构大体为:


JVM 大厂面试都会问,都会这么问,你能顶住么?




JVM 大厂面试都会问,都会这么问,你能顶住么?

通过一些参数,可以来指定上述的堆内存区域的大小:


  • -Xmx value 指定最大的堆大小

  • -Xms value 指定初始的最小堆大小

  • -XX:NewSize = value 指定新生代的大小

  • -XX:NewRatio = value 老年代与新生代的大小比例。默认情况下,这个比例是2,也就是说老年代是新生代的2倍大。老年代过大的时候,Full GC的时间会很长;老年代过小,则很容易触发Full GC,Full GC频率过高,这就是这个参数会造成的影响。

  • -XX:SurvivorRation = value . 设置Eden与Srivivor的大小比例,如果该值为8,代表一个Survivor是Eden的1/8,是整个新生代的1/10。


4、常用的性能监控与问题定位工具有哪些?



JVM 大厂面试都会问,都会这么问,你能顶住么?




JVM 大厂面试都会问,都会这么问,你能顶住么?

系统负荷0.5,意味着桥上一半路段上有车

JVM 大厂面试都会问,都会这么问,你能顶住么?

系统负荷1,意味着桥上道路已经被车占满

JVM 大厂面试都会问,都会这么问,你能顶住么?

系统负荷1.7,代表着在桥上车子已经满了(100%),同时还有70%的车子在等待从桥上通过:





具体的使用方式可以参考从一次线上 思考Java问题定位思路。


参考


  • https://www.cnblogs.com/dreamroute/p/5946272.html

  • https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-2.html#jvms-2.5

  • https://www.cnblogs.com/Kidezyq/p/8040338.html

  • https://www.cnblogs.com/baihuitestsoftware/articles/6405580.html

  • https://www.jianshu.com/p/cd85098cca39

  • http://www.ruanyifeng.com/blog/2011/07/linux_load_average_explained.html




猜你喜欢

1、

2、

3、

4、

5、

6、

7、

8、