目录
内存区域面试与分析
1.运行时数据区是什么
2.Java虚拟机栈的作用
3.本地方法栈的作用
4.堆的作用
5.方法区作用
6.运行时常量池的作用
7.直接内存是什么
8.内存溢出和内存泄漏的区别
9.栈溢出的原因
10.方法区溢出的原因
垃圾回收器与内存分配策略-面试与分析
1.Java的引用有哪些?
2.如何判断对象是否可以被回收?
3.请列举一些可作为GC roots的对象
4.有哪些GC算法?
5.你知道哪些垃圾回收器?
6.你知道哪些内存分配与回收策略?
内存区域面试与分析
1.运行时数据区是什么
JVM在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区,这些区域有各自的用途,创建和销毁时间。
线程私有:程序计数器,Java虚拟机栈,本地方法栈
线程共享:Java堆,方法区
2.Java虚拟机栈的作用
Java虚拟机栈来表述java方法的内存模型。每当有新线程创建的时候就会分配一个栈空间,线程结束后,栈空间被回收,栈与线程拥有相同的生命周期。栈中元素用于支持虚拟机进行方法调用,每个方法在执行的时候都会创建一个栈帧存储方法的局部变量表,操作数栈,动态链接和方法出口等信息。每个方法从调用到执行完成,就是栈帧从入栈到出栈的过程。
本地方法栈在栈深度异常和扩展失败分别抛出*Error和OutOfMemoryError。
3.本地方法栈的作用
本地方法栈与虚拟机栈作用类似,不同的是虚拟机栈为虚拟机执行Java方法服务。本地方法栈为本地方法服务。
调用本地方法时虚拟机栈保持不变,动态链接并直接调用指定本地方法。
4.堆的作用
堆是被虚拟机所管理内存最大的一块,被所有线程共享,在虚拟机启动时创建。堆用来存放对象实例,java里几乎所有对象实例都在堆分配内存。堆可以处于物理上不连续的内存空间,逻辑上应该连续,但对于例如数组这样的大对象,多数虚拟机实现出于简单,存储高效的考虑会要求连续的内存空间。
5.方法区作用
方法区用于存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码缓存等数据。方法区是所有线程共享的内存区域,与堆内存不同,它不存储对象实例,而是存储类的相关信息。
jdk8之前使用永久代实现方法区,容易内存溢出,因为永久代有-XX:MaxPermSize上限,即使不设置也有默认大小。到jdk8使用元空间替代。
6.运行时常量池的作用
运行时常量池是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项是常量池表,用于存放编译器生成的各种字面量与符号引用,这部分内容在类加载后存放到运行时常量池。
运行时常量池相对于Class文件常量池的一个重要特征是动态性,Java不要求常量只有编译期才能产生,运行期间也可以将新的常量放入池中,这种特性利用较多的是String的intern方法。
运行时常量池是方法区的一部分,受到方法区内存的限制,当常量池无法再申请到内存时会抛出OOME。
7.直接内存是什么
直接内存不属于运行时数据区,也不是虚拟机规范定义的内存区域,但这部分内存被频繁使用,而且可能导致内存溢出。
JDK1.4新加入了NIO这种基于通道与缓冲区的IO,它可以使用Native函数库直接分配堆外内存,通过一个堆里的DirectByteBuffer对象最为内存的引用进行操作,避免了在Java堆和Native堆来回复制数据。
8.内存溢出和内存泄漏的区别
内存溢出(Out of Memory,OOM):
内存溢出是指程序在申请内存时,无法再分配到所需的内存空间,导致程序无法继续执行的错误。通常情况下,内存溢出是由于程序需要的内存超过了系统可用的内存大小,或者由于系统资源不足而导致的。常见的内存溢出包括堆内存溢出、栈溢出等。例如,在创建过多对象时没有及时释放内存,导致堆内存溢出;或者递归调用层次过深导致栈溢出等。
内存泄漏(Memory Leak):
内存泄漏是指程序中的一些对象在不再使用时,没有被正确释放或者回收,导致这些对象一直占用内存,从而造成内存的浪费。内存泄漏会导致可用内存不断减少,最终可能导致系统的性能下降甚至崩溃。内存泄漏通常是由程序中的设计缺陷、错误的数据结构使用、不正确的资源管理等引起的。例如,在使用容器类时忘记清理其中的对象引用,导致对象一直存在于内存中,无法被垃圾回收器回收。
9.栈溢出的原因
由于HotSpot不区分虚拟机栈和本地方法栈,设置本地方法栈大小的参数没有意义,栈容量只能由-Xss参数来设定,存在两种异常:
*Error:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出*Error,例如一个递归方法不断调用自己。
OutOfMemoryError:如果JVM栈可以动态扩展,当扩展无法申请到足够内存时会抛出OutOfMemoryError。HotSpot不支持虚拟机栈扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现OOM,否则在线程运行时是不会因扩展而导致溢出的。
10.方法区溢出的原因
方法去主要存放一些类型信息,如类名,访问修饰符,常量池,字段描述,方法描述等。只要不断在运行时产生大量类,方法去就会溢出。例如使用JDK反射或CGLIb直接操作字节码在运行时产生大量的类。很多框架Spring等对类增强时都会使用CGLib这类字节码技术,增强的类越多就越需要越大的方法区保证动态生成的新类型可以载入内存,也就更容易导致方法区溢出。
JDK8使用元空间取代永久代,HotSpot提供了一些参数作为元空间防御措施,例如-XX:MetaspaceSize指定元空间初始大小,达到该值会出发GC进行类型卸载,同时收集器会对该值进行调整,如果释放大量空间就适当降低该值,如果释放很少空间就适当提高。
垃圾回收器与内存分配策略-面试与分析
1.Java的引用有哪些?
强引用:最常见的引用,例如Object obj = new Object就属于强引用,只要对象有强引用执行并且GC root可以到达,在内存回收的时候即使濒临内存耗尽也不会被回收。
软引用:描述非必需对象,在系统发生内存溢出前,会把软引用关联的对象加入回收范围以获得更多内存空间。用来缓存服务器中间计算结果及不需要实时保存的用户行为等
弱引用:弱于软引用,描述非必须对象。弱引用关联的对象只能生存到下次垃圾回收前,当垃圾回收器开始工作的时候,无论内存是否充足,都会回收掉弱引用引用的对象。
虚引用:最弱的引用,定义完成后,无法通过该引用获取对象。唯一目的就是能在对象回收的时收到一个系统通知。虚引用必须与引用队列联合使用,垃圾回收的时候如果出现虚引用,就会在回收对象前把这个虚引用加入引用队列。
2.如何判断对象是否可以被回收?
引用计数:在对象中添加一个引用计数器,如果被引用,计数器加1,引用失效,计数器减1,如果计数器为0,则可以被当成垃圾回收,但是避免不了循环引用的问题,就是两个对象相互指向。
可达性分析算法:基本思路是通过一系列称为GC roots的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路径称为引用链,如果某个对象到GC roots没有任何引用链相连,则会被标记为垃圾。可作为GC roots的对象包括虚拟机栈和本地方法栈中引用的对象,类静态属性引用的对象,常量引用的对象。
3.请列举一些可作为GC roots的对象
在虚拟机栈引用的对象,例如各个线程被调用的方法堆栈中使用到的参数,局部变量等。
在方法区中类静态属性引用的对象,例如静态变量。
在方法区中常量引用的对象,例如字符串常量池里的引用。
在本地方法栈中native方法引用的对象。
所有被同步锁(synchronized关键字)持有的对象。
java虚拟机内部的引用,如基本数据类型对应的class对象,一些常驻的异常对象(比如NullPointException,OutOfMemoryError)等,还有系统类加载器。
4.有哪些GC算法?
https://blog.****.net/2401_83045332/article/details/137578976?utm_medium=notify.im.blog_flow_coupon3.20240415.a&username=2401_83045332
5.你知道哪些垃圾回收器?
https://blog.****.net/2401_83045332/article/details/137578976?utm_medium=notify.im.blog_flow_coupon3.20240415.a&username=2401_83045332
6.你知道哪些内存分配与回收策略?
1.对象优先进入Eden区分配:
大多数情况下对象在新生代Eden区分配,当Eden没有足够空间时将发起一次Minor GC。
2.大对象直接进入老年代:
大对象指需要大量连续内存空间的对象,典型是很长的字符串或数量庞大的数组。大对象容易导致内存还有不少空间就提前出发垃圾收集以获得足够的连续空间。
HotSpot提供了-XX: PretenureThreshold参数,大于该值的对象直接在老年代分配,避免在Eden和Sursive间来回复制。
3.长期存活对象进入老年代
虚拟机给每个对象定义了一个对象年龄计数器,存储在对象头。如果经历过一次Minor GC仍然存活且能被Sursive容纳,该对象就会被移动Sursive中并将年龄设置为1。对象在Sursive中每煎熬过一次Minor GC,年龄就加1,当增加到一定程度(默认15)就会被晋升到老年代。对象晋升到老年代的阈值可通过-XX:
MaxTenuingThreshold设置。
4.动态对象年龄判断
为了适应不同内存情况,虚拟机不要求对象年龄达到阈值才能晋升老年代,如果在Survivor中相同年龄所有对象大小的总和大于Survivor的一半,年龄不小于该年龄的对象就可以直接进入老年代。
5.空间分配担保
MinorGC前虚拟机必须检查老年代最大可用连续空间是否大于新生代对象总空间,如果满足则说明这次MinorGC确定安全。
如果不满足,虚拟机会查看-XX:HandlePromotionFailure参数是否允许担保失败,如果允许会继续检查老年代最大可用连续空间是否大于历次晋升老年代对象的平均大小,如果满足将冒险尝试一次Minor GC,否则改成一次FullGC。
冒险是因为新生代使用复制算法,为了内存利用率只使用一个Survivor,大量对象在Minor GC后仍然存活时,需要老年代进行分配担保,接收Survivor无法容纳的对象。