👨🎓作者:Java学术趴 🏦仓库:Github、Gitee ✏️博客:CSDN、掘金、InfoQ、云+社区 🚫特别声明:原创不易,未经授权不得转载或抄袭,如需转载可联系小编授权。 🙏版权声明:文章里的部分文字或者图片来自于互联网以及百度百科,如有侵权请尽快联系小编。
☠️每日毒鸡汤:这个社会是存在不公平的,不要抱怨,因为没有用!人总是在反省中进步的!
👋大家好!我是你们的老朋友Java学术趴,又到了一年一度最佳找工作的时节,你拿到心仪的offer了吗?基于大多数粉丝的要求,让小编写整理一些面试题,只要粉丝有需求,那必须的满足,从今天开始我会持续更新面试题,其中涵盖了:Java基础、多线程、IO、高并发、集合框架、数据库、框架以及分布式技术。持续更新中·······
今天给大家分享一下Java中的核心技术JVM。作为一个Java程序员,相比或多或少的都会接触到一些关于Java底层的知识,这些底层知识是非常重要的,相比之下这些知识也是比较难以理解的,小编今天用大白话的方式给大家整理了一些面试题,可以便于大家的理解,喜欢的家人们点赞支持呦.....
JVM在执行Java程序时,会把它管理的内存划分为若干个的区域,每个区域都有自己的用途和创建销毁时间。如下图所示,可以分为两大部分,线程私有区和共享区。
线程私有区
程序计数器
当同时进行的线程数超过CPU数或其内核数时,就要通过时间片轮询分派CPU的时间资源,不免发生线程切换。这时,每个线程就需要一个属于自己的计数器来记录下一条要运行的指令。如果执行的是JAVA方法,计数器记录正在执行的java字节码地址,如果执行的是native方法,则计数器为空。
虚拟机栈
与虚拟机栈作用相似。但它不是为Java方法服务的,而是本地方法(C语言)。由于规范对这块没有强制要求,不同虚拟机实现方法不同。
线程共享区
方法区
线程共享的,用于存放被虚拟机加载的类的元数据信息,如常量、静态变量和即时编译器编译后的代码。若要分代,算是永久代(老年代),以前类大多“static”的,很少被卸载或收集,现回收废弃常量和无用的类。其中运行时常量池存放编译生成的各种常量。(如果hotspot虚拟机确定一个类的定义信息不会被使用,也会将其回收。回收的基本条件至少有:所有该类的实例被回收,而且装载该类的ClassLoader被回收)。
堆
存放对象实例和数组,是垃圾回收的主要区域,分为新生代和老年代。刚创建的对象在新生代的Eden区中,经过GC后进入新生代的S0区中,再经过GC进入新生代的S1区中,15次GC后仍存在就进入老年代。这是按照一种回收机制进行划分的,不是固定的。若堆的空间不够实例分配,则OutOfMemoryError。
Young Generation 即图中的Eden + From Space(s0) + To Space(s1)
Eden 存放新生的对象
Survivor Space 有两个,存放每次垃圾回收后存活的对象(s0+s1)
Old Generation Tenured Generation 即图中的Old Space
主要存放应用程序中生命周期长的存活对象
复制代码
栈是运行时单位,代表着逻辑,内含基本数据类型和堆中对象引用,所在区域连续,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包括成员中基本数据类型、引用和引用对象),所在区域不连续,会有碎片。
栈内存用来存储局部变量和方法调用,而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
栈内存是线程私有的。 堆内存是所有线程共有的。
如果栈内存或者堆内存不足都会抛出异常。 栈空间不足:java.lang.StackOverFlowError。 堆空间不足:java.lang.OutOfMemoryError。
栈的空间大小远远小于堆的。
除直接调用System.gc外,触发Full GC执行的情况有如下四种。
老年代空间不足
老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap spac e 为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。
Permanet Generation空间满
PermanetGeneration中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行FullGC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space 为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。注意JDK版本,在JDK1.8开始就没有永久代,使用的是元空间代替永久代(元空间相关后,请看后面题目)。
CMS GC时出现promotion failed和concurrent mode failure 对
于采用CMS进行旧生代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能会触发Full GC。 promotionfailed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。 应对措施为:增大survivorspace、旧生代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕后很久才触发sweeping动作。对于这种状况,可通过设置-XX:CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。
统计得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间
这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。 例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。 当新生代采用PSGC时,方式稍有不同,PS GC是在MinorGC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。 除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- javaDsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+DisableExplicitGC来禁止RMI调用System.gc。
Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。 Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
平台无关关键在于需要在每个平台安装对应的的JDK版本。
JVM类加载分为5个过程:加载,验证,准备,解析,初始化,使用,卸载
加载
加载主要是将.class文件(并不一定是.class。可以是ZIP包,网络中获取)中的二进制字节流读入到JVM中。 在加载阶段,JVM需要完成3件事: 1)通过类的全限定名获取该类的二进制字节流; 2)将字节流所代表的静态存储结构转化为方法区的运行时数据结构; 3)在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
验证是连接阶段的第一步,主要确保加载进来的字节流符合JVM规范。 验证阶段会完成以下4个阶段的检验动作: 1)文件格式验证 2)元数据验证(是否符合Java语言规范) 3)字节码验证(确定程序语义合法,符合逻辑) 4)符号引用验证(确保下一步的解析能正常执行)
准备
主要为静态变量在方法区分配内存,并设置默认初始值。
解析
是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化
初始化阶段是类加载过程的最后一步,主要是根据程序中的赋值语句主动为类变量赋值。 注: 1)当有父类且父类为初始化的时候,先去初始化父类; 2)再进行子类初始化语句。
类加载器是一个用来加载类文件的类。Java 源代码通过 javac 编译器编译成类文件。然后 JVM 来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件。
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。
主要有一下四种类加载器:
在介绍双亲委派模型之前先说下类加载器。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。
类加载器分类:
双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
当一个类收到了类加载请求时,不会自己先去加载这个类,先查询是否已经加载过,没有加载过就将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
如果不想打破双亲委派模型,就重写ClassLoader类中的fifindClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。而如果想打破双亲委派模型则需要重写loadClass()方法(当然其中的坑也不会少)。典型的打破双亲委派模型的框架和中间件有tomcat与osgi 。
用户根据需求自己定义的。需要继承自 ClassLoader ,重写方法 findClass() 。
如果想要编写自己的类加载器,只需要两步:
继承 ClassLoader 类
覆盖 findClass(String className) 方法
ClassLoader 超类的 loadClass 方法用于将类的加载操作委托给其父类加载器去进行,只有当该类尚未加载并且父类加载器也无法加载该类时,才调用 findClass 方法。 如果要实现该方法,必须做到以下几点:
1.为来自本地文件系统或者其他来源的类加载其字节码。
2.调用 ClassLoader 超类的 defineClass方法,向虚拟机提供字节码。
最大的好处就是解决了碎片化。也就是说为什么一个Survivor区不行?第一部分中,我们知道了必须设置Survivor区。假设 现在只有一个Survivor区,我们来模拟一下流程:刚刚新建的对象在Eden中,一旦Eden满了,触发一次Minor GC,Eden中的存活对象就会被移动到Survivor区。这样继续循 环下去,下一次Eden满了的时候,问题来了,此时进行Minor GC,Eden和Survivor各有一些存活对象,如果此时把Eden区的 存活对象硬放到Survivor区,很明显这两部分对象所占有的内存是不连续的,也就导致了内存碎片化。永远有一个Survivor space是空的,另一个非空的Survivor space无碎片。
新生代中的可用内存:复制算法用来担保的内存为9:1,所以只会造成 10% 的空间浪费。 可用内存中 Eden:S1 区为8:1 即新生代中 Eden:S1:S2 = 8:1:1这个比例,是由参数 -XX:SurvivorRatio 进行配置的(默认为 8)。
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法去空间不足
(4)通过Minor GC后进入老年代的平均大小 > 老年代的可用内存
(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。即老年代无法存放下新年代过度到老年代的对象的时候,会触发Full GC。
这个方法就有点类似于某个人被拍了死刑,但是不一定会死。
即使在可达性分析算法中不可达的对象,也并非一定是“非死不可”的,这时候他们暂时处于“缓刑”阶段,真正宣告一个对象死亡至少要经历两个阶段:
Java对象由三个部分组成:对象头、实例数据、对齐填充。
对象头由两部分组成,第一部分存储对象自身的运行时数据:哈希码、GC分代年龄、锁标识状态、线程持有的锁、偏向线程ID(一般占32/64 bit)。第二部分是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。实例数据用来存储对象真正的有效信息(包括父类继承下来的和自己定义的)
对齐填充:JVM要求对象起始地址必须是8字节的整数倍(8字节对齐)
垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(FullGC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。请参考下Java8:从永久代到元数据区 (注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区)
GC最基础的算法有三种: 标记 -清除算法、复制算法、标记-压缩算法,我们常用的垃圾回收器一般都采用分代收集算法。
从上面这些出发点来看,我们平常的 Web 服务器,都是对响应性要求非常高的。选择性其实就集中在 CMS 、 G1 、 ZGC 上。而对于某些定时任务,使用并行收集器,是一个比较好的选择。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。