Java面试题集-JVM(三)

Java面试题集-JVM(三)

1、内存溢出和内存泄漏的区别?

内存溢出 OutOfMemory,指程序在申请内存时,没有⾜够的内存空间供其使⽤。
内存泄露 Memory Leak,指程序在申请内存后,⽆法释放已申请的内存空间,内存泄漏最终将导致内存溢出。

2、堆溢出的原因?

堆⽤于存储对象实例,只要不断创建对象并保证 GC Roots 到对象有可达路径避免垃圾回收,随着对象数量的增加,总容量触及最⼤堆容量后就会 OOM,例如在 while 死循环中⼀直 new 创建实例。
堆 OOM 是实际应⽤中最常⻅的 OOM,处理⽅法是通过内存映像分析⼯具对 Dump 出的堆转储快照分析,确认内存中导致 OOM 的对象是否必要,分清到底是内存泄漏还是内存溢出。
如果是内存泄漏,通过⼯具查看泄漏对象到 GC Roots 的引⽤链,找到泄露对象是通过怎样的引⽤路径、与哪些 GC Roots 关联才导致⽆法回收,⼀般可以准确定位到产⽣内存泄漏代码的具体位置。如果不是内存泄漏,即内存中对象都必须存活,应当检查 JVM 堆参数,与机器内存相⽐是否还有向上调整的空间。再从代码检查是否存在某些对象⽣命周期过⻓、持有状态时间过⻓、存储结构设计不合理等情况,尽量减少程序运⾏期的内存消耗。

3、栈溢出的原因?

由于 HotSpot 不区分虚拟机和本地⽅法栈,设置本地⽅法栈⼤⼩的参数没有意义,栈容量只能由 -Xss参数来设定,存在两种异常:
StackOverflowError: 如果线程请求的栈深度⼤于虚拟机所允许的深度,将抛出StackOverflowError,例如⼀个递归⽅法不断调⽤⾃⼰。该异常有明确错误堆栈可供分析,容易定位到问题所在。
OutOfMemoryError: 如果 JVM 栈可以动态扩展,当扩展⽆法申请到⾜够内存时会抛出OutOfMemoryError。HotSpot 不⽀持虚拟机栈扩展,所以除⾮在创建线程申请内存时就因⽆法获得⾜够内存⽽出现 OOM,否则在线程运⾏时是不会因为扩展⽽导致溢出的。

4、运行时常量池溢出的原因?

String 的 intern ⽅法是⼀个本地⽅法,作⽤是如果字符串常量池中已包含⼀个等于此 String 对象的字符串,则返回池中这个字符串的 String 对象的引⽤,否则将此 String 对象包含的字符串添加到常量池并返回此 String 对象的引⽤。
在 JDK6 及之前常量池分配在永久代,因此可以通过 -XX:PermSize 和 -XX:MaxPermSize 限制永久代⼤⼩,间接限制常量池。在 while 死循环中调⽤ intern ⽅法导致运⾏时常量池溢出。在 JDK7 后不会出现该问题,因为存放在永久代的字符串常量池已经被移⾄堆中。

5、方法区溢出的原因?

⽅法区主要存放类型信息,如类名、访问修饰符、常量池、字段描述、⽅法描述等。只要不断在运⾏时产⽣⼤量类,⽅法区就会溢出。
例如使⽤ JDK 反射或 CGLib 直接操作字节码在运⾏时⽣成⼤量的类。很多框架如 Spring、Hibernate 等对类增强时都会使⽤ CGLib 这类字节码技术,增强的类越多就需要越⼤的⽅法区保证动态⽣成的新类型可以载⼊内存,也就更容易导致⽅法区溢出。J
DK8 使⽤元空间取代永久代,HotSpot 提供了⼀些参数作为元空间防御措施,例如 -XX:MetaspaceSize 指定元空间初始⼤⼩,达到该值会触发 GC 进⾏类型卸载,同时收集器会对该值进⾏调整,如果释放⼤量空间就适当降低该值,如果释放很少空间就适当提⾼。

6、创建对象的过程是什么?

字节码⻆度
NEW: 如果找不到 Class 对象则进⾏类加载。加载成功后在堆中分配内存,从 Object 到本类路径上的所有属性都要分配。分配完毕后进⾏零值设置。最后将指向实例对象的引⽤变量压⼊虚拟机栈顶。
DUP: 在栈顶复制引⽤变量,这时栈顶有两个指向堆内实例的引⽤变量。两个引⽤变量的⽬的不同,栈底的引⽤⽤于赋值或保存局部变量表,栈顶的引⽤作为句柄调⽤相关⽅法。
INVOKESPECIAL: 通过栈顶的引⽤变量调⽤ init ⽅法。
执⾏⻆度
① 当 JVM 遇到字节码 new 指令时,⾸先将检查该指令的参数能否在常量池中定位到⼀个类的符号引⽤,并检查引⽤代表的类是否已被加载、解析和初始化,如果没有就先执⾏类加载。
② 在类加载检查通过后虚拟机将为新⽣对象分配内存。
③ 内存分配完成后虚拟机将成员变量设为零值,保证对象的实例字段可以不赋初值就使⽤。
④ 设置对象头,包括哈希码、GC 信息、锁信息、对象所属类的类元信息等。
⑤ 执⾏ init ⽅法,初始化成员变量,执⾏实例化代码块,调⽤类的构造⽅法,并把堆内对象的⾸地址赋值给引⽤变量。

7、对象分配内存的方式有哪些?

对象所需内存⼤⼩在类加载完成后便可完全确定,分配空间的任务实际上等于把⼀块确定⼤⼩的内存块从 Java 堆中划分出来。
指针碰撞: 假设 Java 堆内存规整,被使⽤过的内存放在⼀边,空闲的放在另⼀边,中间放着⼀个指针作为分界指示器,分配内存就是把指针向空闲⽅向挪动⼀段与对象⼤⼩相等的距离。
空闲列表: 如果 Java 堆内存不规整,虚拟机必须维护⼀个列表记录哪些内存可⽤,在分配时从列表中找到⼀块⾜够⼤的空间划分给对象并更新列表记录。
选择哪种分配⽅式由堆是否规整决定,堆是否规整由垃圾收集器是否有空间压缩能⼒决定。使⽤Serial、ParNew 等收集器时,系统采⽤指针碰撞;使⽤ CMS 这种基于清除算法的垃圾收集器时,采⽤空间列表。

8、对象分配内存是否线程安全?

对象创建⼗分频繁,即使修改⼀个指针的位置在并发下也不是线程安全的,可能正给对象 A 分配内存,指针还没来得及修改,对象 B ⼜使⽤了指针来分配内存。
解决⽅法:① CAS 加失败重试保证更新原⼦性。② 把内存分配按线程划分在不同空间,即每个线程在Java 堆中预先分配⼀⼩块内存,叫做本地线程分配缓冲 TLAB,哪个线程要分配内存就在对应的 TLAB分配,TLAB ⽤完了再进⾏同步。

9、Java的引用有哪些类型?

JDK1.2 后对引⽤进⾏了扩充,按强度分为四种:
强引⽤: 最常⻅的引⽤,例如 Object obj = new Object() 就属于强引⽤。只要对象有强引⽤指向且 GC Roots 可达,在内存回收时即使濒临内存耗尽也不会被回收。
软引⽤: 弱于强引⽤,描述⾮必需对象。在系统将发⽣内存溢出前,会把软引⽤关联的对象加⼊回收范围以获得更多内存空间。⽤来缓存服务器中间计算结果及不需要实时保存的⽤户⾏为等。
弱引⽤: 弱于软引⽤,描述⾮必需对象。弱引⽤关联的对象只能⽣存到下次 YGC 前,当垃圾收集器开始⼯作时⽆论当前内存是否⾜够都会回收只被弱引⽤关联的对象。由于 YGC 具有不确定性,因此弱引⽤何时被回收也不确定。
虚引⽤: 最弱的引⽤,定义完成后⽆法通过该引⽤获取对象。唯⼀⽬的就是为了能在对象被回收时收到⼀个系统通知。虚引⽤必须与引⽤队列联合使⽤,垃圾回收时如果出现虚引⽤,就会在回收对象前把这个虚引⽤加⼊引⽤队列。

10、类加载是什么?

Class ⽂件中描述的各类信息都需要加载到虚拟机后才能使⽤。JVM 把描述类的数据从 Class ⽂件加载到内存,并对数据进⾏校验、解析和初始化,最终形成可以被虚拟机直接使⽤的 Java 类型,这个过程称为虚拟机的类加载机制。
与编译时需要连接的语⾔不同,Java 中类型的加载、连接和初始化都是在运⾏期间完成的,这增加了性能开销,但却提供了极⾼的扩展性,Java 动态扩展的语⾔特性就是依赖运⾏期动态加载和连接实现的。
⼀个类型从被加载到虚拟机内存开始,到卸载出内存为⽌,整个⽣命周期经历加载、验证、准备、解析、初始化、使⽤和卸载七个阶段,其中验证、解析和初始化三个部分称为连接。加载、验证、准备、初始化阶段的顺序是确定的,解析则不⼀定:可能在初始化之后再开始,这是为了⽀持 Java 的动态绑定。

11、类加载的过程是什么?

加载
该阶段虚拟机需要完成三件事:① 通过⼀个类的全限定类名获取定义类的⼆进制字节流。② 将字节流所代表的静态存储结构转化为⽅法区的运⾏时数据区。③ 在内存中⽣成对应该类的 Class 实例,作为⽅法区这个类的数据访问⼊⼝。
验证
确保 Class ⽂件的字节流符合约束。如果虚拟机不检查输⼊的字节流,可能因为载⼊有错误或恶意企图的字节流⽽导致系统受攻击。验证主要包含四个阶段:⽂件格式验证、元数据验证、字节码验证、符号引⽤验证。
验证重要但⾮必需,因为只有通过与否的区别,通过后对程序运⾏期没有任何影响。如果代码已被反复使⽤和验证过,在⽣产环境就可以考虑关闭⼤部分验证缩短类加载时间。
准备
为类静态变量分配内存并设置零值,该阶段进⾏的内存分配仅包括类变量,不包括实例变量。如果变量被 final 修饰,编译时 Javac 会为变量⽣成 ConstantValue 属性,准备阶段虚拟机会将变量值设为代码值。
解析
将常量池内的符号引⽤替换为直接引⽤。
符号引⽤以⼀组符号描述引⽤⽬标,可以是任何形式的字⾯量,只要使⽤时能⽆歧义地定位⽬标即可。与虚拟机内存布局⽆关,引⽤⽬标不⼀定已经加载到虚拟机内存。
直接引⽤是可以直接指向⽬标的指针、相对偏移量或能间接定位到⽬标的句柄。和虚拟机的内存布局相关,引⽤⽬标必须已在虚拟机的内存中存在。
初始化
直到该阶段 JVM 才开始执⾏类中编写的代码。准备阶段时变量赋过零值,初始化阶段会根据程序员的编码去初始化类变量和其他资源。初始化阶段就是执⾏类构造⽅法中的 ⽅法,该⽅法是 Javac⾃动⽣成的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值