当前位置: 首页 > 后端技术 > Java

Java面试题(JVM)总结

时间:2023-04-01 20:09:35 Java

1.JVM运行时内存区划分线程独占区:程序计数器、本地方法栈、虚拟机栈线程共享区:元空间(<=1.7方法区)、堆**程序计数器**:线程私有,是一个很小的内存空间,可以看作是当前线程执行的字节码指标,也是唯一没有定义OOM的块**本地方法栈**:用于执行Native方法使用**虚拟机栈**:用于存放局部变量、操作数栈、动态链接、方法出口等信息**元空间**:存放类元信息、常量、已经被虚拟机加载的静态变量、**代码just-in-time编译器编译后的其他数据仍然保存在方法区,方法区位于堆中****heap**:存储对象实例的例子:/***@author:jujunchen*@description:CGLIB用于动态生成类,元空间存储类信息,-XX:MetaspaceSize=10m-XX:MaxMetaspaceSize=10m*如果只设置堆的大小,不会溢出*@date:2019/4/7*/publicclassJavaMetaSpaceOOM{staticclassOOMObject{}publicstaticvoidmain(finalString[]args){while(true){Enhancerenhancer=newEnhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback(newMethodInterceptor(){@OverridepublicObjectintercept(Objecto,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{returnmethodProxy.invokeSuper(o,对象);}});增强器.create();}}}2.OOM、SOE实例、原因、排查方法//OOM-Xmx20m-Xms20m-XX:+HeapDumpOnOutOfMemoryErrorpublicclassOOMTest{publicstaticvoidmain(String[]args){ListobjList=newArrayList();while(true){objList.add(newObject());}}}//SOE堆栈异常-Xss125kpublicclassSOETest(){staticintcount=0;publicstaticvoidmain(String[]args){try{stackMethod();}catch(Errorerr){err.printStackTrace();System.out.println("执行计数="+计数);}}privatestaticvoidstackMethod(){count++;堆栈方法();}}OOM排查:如果能看到log,可以从打印的log中得到发送异常的那行代码,然后再去代码中找出是哪里block的代码有问题如果没有记录log,通过设置-XX:+HeapDumpOnOutOfMemoryError生成OOM时的.hprof文件,然后导入JProfiler查看是哪个对象导致了OOM,然后用这个对象在代码中找到SOE:stack深度为一般1000-2000深度,如果超过深度或堆栈大小,就会造成SOE。通过打印的log定位错误码位置,查看是否存在无限递归,出现死循环。修改代码3.如何判断对象能否被回收或存活判断能否被回收或存活主要看堆中是否存在该实例以及加载该类的类加载器是否被回收。这个类的java.lang.Class对象在任何地方都没有被引用,即无法通过反射方式获取到这个类的信息4.哪些对象可以作为GCROOT对象虚拟机栈中引用的对象(本地栈帧中的变量表)静态变量和方法区常量引用的对象JNI在本地方法中引用的对象5.常见的GC算法mark-clear算法:先标记存活的对象,再清除未标记的对象。缺点是:标记清除过程效率不高;它会产生内存碎片。复制算法:将内存分成两部分相同大小的块,只使用其中的一块内存,当这块内存快用完时,将存活的对象复制到另一块内存中,然后清理已用内存spaceonce-finishingalgorithm:标记存活对象,将对象移动到内存的一侧,然后清理回收一侧外的内存查看正在运行的虚拟机进程,并显示虚拟机的主类和进程ID机器。jstat[optionvmid[interval[s|ms][count]]]可以显示本地或远程虚拟机中的类加载、内存、垃圾回收、JIT编译等运行数据jinfo查看和调整虚拟机的各种参数机器实时jmap生成堆转储快照jhat生成页面分析导出的堆存储快照jstack用于生成虚拟机当前时刻的线程快照jstatd启动RMI服务器程序,为远程计算机代理本地Java进程连接调试查看当前JVM使用的垃圾收集器java-XX:+PrintFlagFinal-version或java-XX:+PrintCommandLineFlags-versionjconsoleJava监控管理管理控制台,可监控内存、线程、类等。jvisualvm一体式监控工具更多信息请了解官网:https://docs.oracle.com/en/ja...7.JVM优化响应时间优先级:young使生成变大,直到接近系统的最小响应时间限制。扩大新生代以减少到达老年代的对象数量。永久代的设置需要参考:永久代并发收集数,新生代与永久代回收时间的比例,调整为合适的值。吞吐量优先级:年轻代设置大,永久代小。8.什么时候触发FullGC。永久代空间不足手动触发gc9。有几种类型的类加载器。BootstrapClassLoader负责加载JDK自带的rt.jar包中的类文件。它是所有类加载器的父加载器。BootstrapClassLoader没有任何父类加载器。ExtensionClassLoader负责加载Java的扩展类库,即从jre/lib/ext目录或java.ext.dirs系统属性指定的目录加载类。SystemClassLoader负责从类路径环境变量中加载类文件。classpath环境变量通常由“-classpath”或“-cp”命令行选项定义,或者由jar中Mainfest文件的classpath属性指定。SystemClassLoader是ExtensionClassLoaderSubloaderCustomLoader10.什么是双亲委派模型?双亲委托模型的破坏当一个类被加载时,它会首先将加载请求委托给双亲加载器。只有当父加载器反馈请求无法加载时,子加载器才会尝试加载双亲委托模型。最重要的是不要按照双亲委派模型来加载类,比如JNDI,它的代码是由启动类加载器加载的,但是JDNI需要调用部署在ClassPath中的JNDI接口,而启动类加载器并不知道这些代码,于是就有了线程上下文类加载器(ThreadContextClassLoader),可以通过java.lang.Thread类setContextClassLoader来设置类加载器,通过这个父类加载器,可以请求子类加载器完成类加载动作。11.类生命周期一个类的生命周期有7个阶段:加载、验证、准备、解析、初始化、使用、卸载加载:在加载阶段,虚拟机需要完成以下3件事才能通过完全类的限定名这种类型的二进制字节流将字节流表示的静态存储结构转换为方法区中的运行时数据结构,并在内存中生成表示本类的java.lang.Class对象作为本类的各种数据类在方法区的访问入口校验:分为4种校验文件格式,校验是否符合Class文件格式的规范,可以被当前版本的虚拟机处理,进行元数据校验,语义分析对字节码所描述的信息进行处理,以确保其描述。这些信息符合Java语言规范的要求。字节码验证。通过对数据流和控制流的分析,判断程序语义是否合法、合乎逻辑。符合引用)准备:正式为类变量分配内存并设置初值阶段,这里设置初值为数据类型的默认值解析:虚拟机替换常量池中的符号引用withadirectreferenceprocessInitialization:executeclassconstruction12.强引用、软引用、弱引用和幻象引用强引用:大部分使用的都是强引用。当内存不足时会出现OOM,程序会异常终止,强引用的对象也不会随意回收。引用:当内存足够时,不会清除对象,当内存不足时,会回收这些对象。弱引用:弱引用的对象在GC发生时会被回收。幻影引用:幻影引用主要用于跟踪垃圾回收活动,幻影引用必须与引用队列配合使用。13、编译器会对指令做哪些优化?编译器优化分为编译期和运行时编译期:1.标签检查,检查变量在使用前是否声明过,变量与赋值之间的数据类型是否匹配,折叠常量2.数据和控制流分析,检查程序局部变量在使用前是否赋值,是否正确处理了所有检查异常等问题3.将语法糖恢复到基本语法结构4.生成字节码运行时:JITJIT会把频繁运行代码被编译成与本地平台相关的机器代码,并在各个层面进行优化。ClientCompiler:局部优化将分三个阶段进行:第一阶段,平台无关的前端将字节码构造成一个代表HIR的高级中间码。HIR使用静态单一分配(SSA)来表示代码值,并对字节码进行方法内联和常量传播等基础优化;在第二阶段,从HIR生成低级中间代码。在此之前会进行空检查消除、范围检查消除等。第三阶段,在LIR上分配寄存器,在LIR上进行窥孔优化,最后生成机器码。服务器编译器:将执行无用代码消除、循环展开、循环表达式提取、公共子表达式消除、常量传播、基本块重新排序、范围检测消除和空值检查消除。此外,还可以根据解释器或ClientCompiler提供的性能监控信息,进行一些不稳定的激进优化,如guardinginline、branchfrequencyprediction等。计算出来,E中所有变量的值从上次计算到现在都没有变化,那么这次出现E就变成了普通子表达式数组范围检查,省去了编译时判断数组是否在合理范围内,以及如果是,则可以在循环中去掉对数组上下界的检查。此外,还有隐式异常处理。虚拟机为段错误信号注册一个异常处理程序。但是,如果代码经常为空,耗时比空判断慢,但是虚拟机根据运行时收集的信息选择使用空判断还是隐藏判断,内联异常处理方法可以提供其他优化技术的基础,例如“公共子表达式消除”。在虚方法或者多态的情况下,如果要判断是否可以内联,虚拟机需要查询“类型继承关系分析器”。如果只有一个版本,可以内联,但会有“逃生门”。这种内联称为“Guardian内联”。如果继承关系发生变化,虚拟机就会通过“逃生门”回到解释态执行,或者重新编译。如果是多版本方法,虚拟机会在第一次调用时通过“内联缓存”缓存目标方法的版本,下次调用时检查版本是否一致。如果不一致,则取消内联,找到虚方法表进行methoddispatchescapeanalysis,分析对象的动态作用域,对象是否作为调用参数传递给其他方法(方法转义),是否它被其他线程访问(线程逃逸)。如果没有以上情况,虚拟机会做一些高效的优化:栈上分配,同步消除(去除同步措施),标量替换(将对象成员变量恢复为原来的类型)14.Serial,Parallel,CMS,G1收集器特性Serialsingle线程收集器,在进行垃圾收集时,必须挂起所有工作线程,直到结束。收集器暂停了很长时间。-XX:+UseSerialGC使用串行垃圾回收器Parallel多线程扫描压缩堆,停顿时间短,回收效率高,-XX:+UseParNewGC采用并发标记-清除垃圾收集器CMS基于“标记-清除”算法,分为初始标记、并发标记、重新标记、并发清除、并发重置5个阶段initialmark,remarking阶段需要concurrentresetSTW,CMS收集器占用大量CPU资源,无法处理浮动垃圾。concurrentreset阶段重新初始化CMS数据结构和数据,为下一次垃圾回收做准备(-XX:CMSInitiatingOccupancyFraction调整多少次触发老年代占用Recycling;-XX:+UseCMSCompactAtFullCollection默认开启,在触发FullGC前整理内存;-XX:CMSFullGCsBeforeCompaction设置执行多少次非压缩FullGC,一次压缩碎片整理)G1可以与用户程序并发进行垃圾回收;分代收集,将堆分成多个大小相等的独立Region区域;空间整合,内存组织将默认执行;predictablepause,G1跟踪每个Region回收得到的空间大小,回收需要15.JVM加载class文件的原理是什么?JVM中的类加载是通过ClassLoader及其子类实现的。JavaClassLoader是一个重要的Java运行时系统组件。它负责在运行时从类文件中查找和加载类。Java中的所有类都需要通过类加载器加载到JVM中才能运行。类加载器本身也是一个类,它的工作就是将类文件从硬盘读入内存。在写程序的时候,我们几乎不需要关心类加载,因为这些都是隐式加载的,除非我们有特殊用途,比如反射,否则需要显式加载需要的类。类加载有两种方式:(1)隐式加载,当程序在运行过程中遇到new等产生的对象时,隐式调用类加载器将相应的类加载到jvm中,(2)显式loading,通过class.forname()等方法,显式加载需要的类,隐式加载和显式加载的区别:两者的本质是一样的。Java类的加载是动态的。它不会一次加载所有类然后运行它们。而是确保程序运行的基础类(如基类)完全加载到jvm中。至于其他的类,只需要在什么时候加载。这当然是为了节省内存开销。16、jvm的最大内存限制是多少(1)堆内存分配JVM分配的初始内存由-Xms指定,默认为物理内存的1/64;JVM分配的最大内存由-Xmx指定,默认为物理内存的1/4。当默认的空闲堆内存小于40%时,JVM会增加堆直到-Xmx的最大限制;当空闲堆内存大于70%时,JVM将减少堆,直到达到-Xms的最小限制。所以服务器端一般会设置-Xms和-Xmx相等,以避免每次GC后调整heapsize。(2)非堆内存分配JVM使用-XX:PermSize设置非堆内存的初始值,默认为物理内存的1/64;最大非堆内存大小由XX:MaxPermSize设置,默认为物理内存的1/4。(3)VM最大内存首先,JVM内存被限制为实际最大物理内存。假设物理内存无限大,那么JVM内存的最大值与操作系统有很大关系。简单来说,虽然32位处理器的可控内存空间为4GB,但具体操作系统会有限制,一般为2GB-3GB(一般来说Windows系统下为1.5G-2G,而Linux系统下1.5G-2G)。2G-3G),64位以上的处理器就没有限制了。(3)以下是目前流行的几种不同公司、不同版本的JVM的最大堆内存:17.jvm是如何实现线程的?线程是比进程更轻量级的调度执行单元。线程可以分离进程的资源分配和执行调度。一个进程中可以启动多个线程,每个线程可以共享进程的资源(内存地址、文件IO等),并且可以独立调度。线程是CPU调度的基本单位。所有主流操作系统都提供线程实现。Java语言为线程操作提供了相同的API。java.lang.Thread类的每一个执行过start()但还没有结束的实例代表一个线程。Thread类的关键方法都声明为Native。这意味着此方法不能或尚未使用与平台无关的方式实现,可能是为了提高执行效率。线程的实现方式A、使用内核线程实现内核线程(Kernel-LevelThread,KLT)是操作系统内核直接支持的线程。内核完成线程切换。内核通过调度器对线程进行调度,并将线程的任务映射到各个CPU。资源使用用户线程来实现系统内核无法感知线程存在的实现。用户线程的建立、同步、销毁、调度完全在用户态完成。所有的线程操作都需要用户程序自己处理,复杂度高。用户线程和轻量级进程的混合实现轻量级进程作为用户线程和内核线程之间的桥梁18.什么是Java内存模型Java内存模型(简称JMM),JMM决定了线程对共享变量的写操作何时可见另一个线程。JMM从抽象的角度定义了线程与主存的抽象关系:线程间的共享变量存储在主存(mainmemory)中,每个线程都有一个私有的本地内存(localmemory),线程的一份副本读/写共享变量存储在本地内存中。本地内存是JMM的一个抽象概念,并不真正存在。它涵盖了高速缓存、写入缓冲区、寄存器以及其他硬件和编译器优化。其关系模型图如下图所示: