通过问题带你理解JVM

讲一下对于常量池与运行时常量池的理解

常量池表(Constant Pool Table)是class字节码文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。
常量池表(Constant Pool Table)主要包含以下信息

  • 数量值
  • 字符串值
  • 类引用
  • 字段引用
  • 方法引用

运行时常量池(Runtime Constant Pool)是方法区的一部分。
运行时常量池,相对于class文件常量池的另一重要特征是:具备动态性。(运行时常量池里的信息会比常量池里的信息多一点,String.intern())

什么是逃逸分析、栈上分配、标量替换

逃逸分析(Escape Analysis) - 分析对象的动态作用域。假如我们在一个方法内定义了一个对象,如果它被作为参数传递到其他地方,被本方法外的方法引用,这就就叫做方法逃逸。

有了逃逸分析,我们就可以判断出一个方法中的变量是否有可能被其他线程所访问或改变,JIT就可以据此进行一系列的优化,如标量替换、栈上分配。

如果我们经过逃逸分析发现,某个对象并没有发生方法逃逸,那么它的生命周期则始于方法调用,卒于方法结束,那么此时它就是方法内的局部变量,而堆内存是线程间共享的,如果将它分配到堆中,方法结束后,它将不在被任何对象所引用,还需要GC进行回收,很不划算,于是 JIT就会将其分配到方法的栈帧中,这就是栈上分配。实际上在HotSpot中,栈上分配并不是直接在方法的栈帧中放入一个对象,它是通过标量替换的方式存储的,即将对象分解成组成对象的若干个成员变量,这些变量是无法再分解的更小的数据,叫做标量,然后用这些标量来代替之前的对象,这就叫标量替换。通过标量替换,原本的一个对象,被替换成多个成员变量。而原本需要在堆上分配的内存,也就不再需要了,完全可以在本地方法栈中完成对成员变量的内存分配。

堆是分配对象存储的唯一选择么?

如果经过逃逸分析(Escape Analysis)后发现,一个对象并没有逃逸出方法的话,那么就可能被优化成栈上分配。这样就无需在堆上分配内存,也无须进行垃圾回收了。这也是最常见的堆外存储技术。

基于openJDK深度定制的TaoBaoVM,其中创新的GCIH (GC invisible heap)技术实现off-heap,将生命周期较长的Java对象从heap中移至heap外,并且GC不能管理GCIH内部的Java对象,以此达到降低GC的回收频率和提升GC的回收效率的目的。

讲一下Minor GC、Major GC 和 Full GC

针对HotSpot VM的实现,它里面的GC按照回收区域分为两大种类型:
一种是部分收集(Partial Gc)
一种是整堆收集(Full Gc)

部分收集(Partial Gc)

  • 新生代收集(Minor GC):Eden区满的时候会触发Minor GC。
  • 老年代收集(Major GC):目前只有CMS GC会有单独收集老年代的行为。
  • 混合收集(Mixed GC) :收集整个新生代以及部分老年代的垃圾。目前,只有G1会有这种行为。

整堆收集(Full GC)

收集整个Java堆和方法区的垃圾。

为什么需要给Java堆分代?

不同对象的生命周期不同。70%-99%的对象是临时对象。
分代的唯一理由就是优化GC性能。不同区对应不同的垃圾收集算法。

survivor区的对象一定是age到达MaxTenuringThreshold才会被放进老年代么?

不是的。如果survivor区中相同年龄的所有对象大小的总和大于survivor空间的一半,那么年龄大于或等于该年龄的对象可以直接进入老年代,无须等到MaxTenuringThreshold 中要求的年龄。

什么是老年代的空间分配担保?

早期绑定与晚期绑定

方法的调用,将符号引用转换为直接引用时:
如果被调用的目标方法在编译期可知,且运行期保持不变时。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。
如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接。

虚方法与非虚方法

说一下JDK1.6/1.7/1.8堆区与方法区内存区域的变化

首先需要明确,只有HotSpot才有永久代。BEA JRockit、IBM J9等来说,是不存在永久代的。
先简单讲下 方法区、永久代、元空间 三者的关系。
方法区是JVM规范,《java虚拟机规范》对如何实现方法区,不做统一要求。 永久代和元空间是根据规范对方法区的具体实现。元空间与永久代最大的区别在于:元空间不再虚拟机设置的内存中,而是使用本地内存(PC内存)。具体变化如下:
JDK1.6 使用永久代实现方法区
有永久代,静态变量存放在永久代上。
在这里插入图片描述

JDK1.7 将字符串常量池/静态变量存放在堆上
有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中。
在这里插入图片描述

JDK1.8 彻底干掉了永久代,在直接内存中划出一块区域作为元空间。
无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,字符串常量池、静态变量保存在堆中。
在这里插入图片描述
在这里插入图片描述

为什么使用元空间替代永久代作为方法区的实现?

永久代更容易遇到内存溢出的问题。因为永久代仍然使用JVM的内存。
永久代的空间大小是很难确定的。
使用-XX:MaxPermsize参数设置永久代的大小。
对永久代进行调优是很困难的。

字符串常量池(StringTable)为什么挪到堆中?

JDK7中将StringTable放到了堆空间中。 因为永久代的回收效率很低,在Full GC的时候才会执行永久代的垃圾回收,而Full GC是老年代的空间不足、永久代不足时才会触发。
这就导致StringTable回收效率不高,而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

给对象分配内存时会不会存在线程不安全的问题?如何解决?

会存在线程不安全问题。
解决方式:
1.同步处理 CAS+失败重试
2.Thread Local Allocation Buffer(TLAB)
每个线程在 Java堆中预先分配一小块内存,只有TLAB用完并分配新的TLAB时才需要同步锁。

什么是TLAB?

JVM对Eden区继续进行划分,为每个线程分配一个私有缓存区域。
多线程同时分配内存时,使用TLAB可以避免一系列的线程安全为题,同时还可以提升内存分配的吞吐量,把这种内存分配方式称为快速分配策略
JVM将TLAB作为内存分配的首选。
默认情况下,TLAB空间的内存非常小,仅占整个Eden空间的1%。

一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。

清楚栈的指令集架构与寄存机指令集架构么?

指令集的架构模型分为基于栈的指令集架构与基于寄存器的指令集架构两种,HotSpot虚拟机是基于栈的指令集架构。传统PC上x86的二进制指令集、Android的Davlik虚拟机是基于寄存器架构的指令集典型应用。

基于栈式指令集架构特点:
可移植性较好,不需要硬件支持,可以实现跨平台。
基于零地址指令方式分配,适用于资源受限的系统。而基于寄存器架构的指令集往往以一地址指令、二地址指令、三地址指令为主。
指令集小,相同的操作要比基于寄存器架构的指令集数量多。
速度慢一点点,速度没寄存机架构指令集快。

对象的创建过程了解么?

在OOM之前一定会触发一次垃圾收集么?

发生OOM之前,不是在任何情况下垃圾收集器都会被触发的。比如,我们去分配一个超大对象,类似一个超大数组超过堆的最大值,JVM可以判断出垃圾收集并不能解决这个问题,所以直接抛出OutOfMemoryError。

你用过 WeakHashMap 么?

如何查看默认的垃圾收集器

-XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)

使用命令行指令: jinfo -flag [ 相关垃圾回收器参数 ] [ 进程ID ]

为什么需要Serial Old 作为CMS的后备预案?

CMS垃圾收集器由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CNS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

CMS使用标记清除算法,会产生内存碎片,为什么不使用标记整理算法呢?

因为当并发清除的时候,用标记整理算法的话,原来的用户线程使用的内存还怎么用呢?
标记整理会改变对象的地址,如果这个时候,用户线程正在使用对象,会很难处理。

Java堆区,新生代与老年代的比例是多少?如何设置新生代与老年代的比例?

默认新生代与老年代比例是 1:2

可以通过一下参数设置新生代与老年代的空间比例:
-XX:NewRatio=2 (表示新生代占1,老年代占2,新生代占整个堆区的1/3,这是默认设置)
-XX:NewRatio=4(表示新生代占1,老年代占4,新生代占整个堆区的1/5)

双亲委派模型有什么作用?

保证核心.class不被篡改

比如用户自定义了一个 java\lang\String.class。双亲委派模型,保证了不会加载用户自定义的String类,因为在jvm启动的时候已经加载了jdk中的String类。

防止加载同一个.class

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值