jvm面试题目补充

本文详细解析了Java开发中的JDK与JRE概念,重点探讨了Java虚拟机的HotSpot技术、垃圾回收中的空间分配担保、安全点与用户线程中断,以及并发标记中的三色标记策略。还讨论了内存溢出、垃圾回收场景、内存优化措施等内容。
摘要由CSDN通过智能技术生成

jdk&jre

Java程序设计语言、Java虚拟机、Java API类库这三部分统称为JDK(Java Development Kit)。

把Java API类库中的Java SE API子集 [1] 和Java虚拟机这两部分统称为JRE(Java Runtime Environment),JRE是支持Java程序运行的标准环境

HotSpot VM:

HotSpot指的就是它的热点代码探测技术

垃圾回收中的空间分配担保

为了减少垃圾回收时间

minor gc前,检查老年代最大连续可用空间是否大于新生代对象总大小,如大于,则进行minor gc;

如小于,检查是否开启了分配担保,未开启,直接full gc;

开启后,检查老年代最大连续可用空间是否大于历次晋升老年代对象大小,小于则进行minorgc,同时将新生代放不下的对象提前放到老年代中,大于则full gc

根节点枚举

在特定位置时就记录对象关系到OopMap中

暂停用户线程;使用OopMap存储:类加载完成时,将什么对象存储在什么位置取出来;即时编译时,将对象存储取出放到特定位置;不需要一字不漏的将gcroot从方法区等位置取出

安全点

在特定的位置生成OopMap记录,称为安全点;到安全点时,用户线程才停止,进行垃圾回收

用户线程主动式中断:主动轮询中断标识,为true时,中断

  • 循环的末尾
  • 方法返回前
  • 调用方法的 call 之后
  • 抛出异常的位置

安全区

用户线程sleep或者blocked状态时,无法响应系统的中断请求,挂起自己线程

某个区域内,引用关系不会发生变化,进行垃圾回收是安全的

记忆集和卡表

存在跨区域垃圾回收时,GC root并不是包含所有区域的root节点,非回收区域存在回收区域的指针时,才需要加入gc root节点;用于缩小gc root扫描范围

使用以下结构存储了这些引用关系

记忆集:是否存在非回收区域指向回收区域的指针

卡表是记忆集(一种抽象概念)的实现,一个卡表存在多个卡页,一个卡页存在一个(或多个对象)对象存在跨代指针时,记录变脏标志为1,后续将这个内存页的数据加入GC ROOT一并扫描

写屏障

卡表变脏时间:引用类型对象赋值时,卡表可能变脏

引用类型对象赋值后,使用写后屏障,更新卡表。

写前屏障在G1垃圾处理器后才使用到

三色标记:并发标记阶段(可达性分析)

gcroot向下遍历对象时的算法

按照是否被垃圾回收器访问过,分为白色(没被访问过,不可达),黑色(被访问过,所有引用都被访问过,不能直接指向白色,需通过灰色间接指向白色,原因是黑色是被遍历完成的,下次标记不能重新扫描引用,此时白色会被误清理),灰色(至少一个引用未被访问过,正在枚举过程中)

假设访问对象A,访问对象A的所有引用,变为灰色对象,访问完成时,将A变为黑色对象;所有对象遍历完成时,剩余的白色对象即为垃圾;

并发标记阶段,用户线程同步进行,对象的引用关系发生了变化,因此在重新标记阶段需要对变化的引用进行处理;

引用关系两种变化场景:

1 删除引用,黑色对象引用被删除,成为浮动垃圾,下次垃圾回收时回收即可;

2 新增引用,黑色对象下添加白色对象A->F,当原来引用白色的关系被删除时B.f=null,此时进行重新标记时,B变为黑色,F还是白色,会被误清理

解决方案

增量更新

从增量角度,A.f=F,增加引用时,添加写屏障,将黑色引用白色的引用关系记录一下,重新标记时,将引用关系重新扫描,实现方案是将A变为灰色,a的引用关系重新扫描

原始快照

从删除角度,在执行B.f=nul,插入一个写屏障,记录B.f,再进行置空操作,重新标记时,将B.F变为黑色对象,不管吧B.F是否还有引用都不会被清理,如果没有引用下次垃圾回收会清理掉。宁可放过,不可杀错的思想。

引用逃逸

如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针发生了逃逸。

gcroot包含静态类,synch锁定对象、常量池中对象

垃圾回收

fullgc场景

大对象(sql未分页等)分配

metaspace溢出,classloader未回收导致元空间溢出,发生full gc

内存泄漏,由于内存泄漏会导致内存溢出

jvm参数设置不合理

内存溢出

不断创建对象时,gc root存在引用关系时,堆空间不足时会发生内存溢出

确认oom对象是否是必要的,即内存泄漏还是内存溢出,内存泄漏需要使用工具判断引用链,内存溢出需要判断堆大小相关配置

StackOverflow

虚拟机容量太小,栈帧太大,都会引起新的栈帧无法分配内存

线程请求栈的深度大于虚拟机允许的最大深度时

oom

创建线程时无法获得内存时会出现oom(jvm实现上不允许栈自动扩容,理论上允许)

方法区(类型信息(类名,父类,修饰符,实现的接口列表等)、域(属性)、方法、常量、静态变量,运行时的常量池,编译后的代码缓存)

垃圾回收主要包含:常量池和类的卸载

jdk1.6 PerGen OOM

jdk1.7 java heap

jdk1.8 metasapce oom

本地直接内存

使用NIO时,日志内容较少

永久代和方法区的关系?和元空间的关系?

收集器的分代设计扩展到方法区时,用永久代的概念实现方法区的垃圾回收,效果不理想,因此从jdk7开始逐步将永久代的字符串常量池和静态变量等移出(堆中),jdk8中永久代消失,将剩余内容(主要是类型信息)移动到直接内存metaspace元空间中

gc和内存溢出的关系

分配空间时,发现空间不足(应用空闲时也会)时会进行gc,gc后空间还不够,抛出内存溢出相关异常

减少GC开销的措施 

 (1)不要显式调用System.gc()  此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。 

 (2)尽量减少临时对象的使用  临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。 

 (3)对象不用时最好显式置为Null  一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。 

 (4)尽量使用StringBuffer,而不用String来累加字符串  由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。 

 (5)能用基本类型如Int,Long,就不用Integer,Long对象  基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。 

 (6)尽量少用静态对象变量  静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

 (7)分散对象创建或删除的时间  集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值