JVM面试题

JVM

原文链接:https://blog.csdn.net/mshootingstar/article/details/44783227\

​ JVM是虚拟机,也是一种规范,他遵循着冯·诺依曼体系结构的设计原理。冯·诺依曼体系结构中,指出计算机处理的数据和指令都是二进制数,采用存储程序方式不加区分的存储在同一个存储器里,并且顺序执行,指令由操作码地址码组成,操作码决定了操作类型和所操作的数的数字类型,地址码则指出地址码和操作数。从dos到windows 8,从unix到ubuntu和CentOS,还有MAC\OS等等,不同的操作系统指令集以及数据结构都有着差异,而JVM通过在操作系统上建立虚拟机,自己定义出来的一套统一的数据结构和操作指令,把同一套语言翻译给各大主流的操作系统,实现了跨平台运行,可以说JVM是java的核心,是java可以一次编译到处运行的本质所在。

Oracle的HotSpot JVM
	区别于IBM,HP等厂商开发的JVM。Java HotSpot Client VM和Java HotSpot Server VM是JDK关于JVM的两种不同的实现,前者可以减少启动时间和内存占用,而后者则提供更加优秀的程序运行速度

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E7IyHISs-1603252392621)(F:\develop\Z_JavaEE\JVM.assets\1592705091604.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pqx9YBzn-1603252392628)(F:\develop\Z_JavaEE\JVM.assets\1592705279025.png)]

ClassLoader ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。

Execution Engine 执行引擎,也叫 Interpreter。Class文件被加载后,会把指令和数据信息放入内存中,Execution Engine则负责把这些命令解释给操作系统。

Native Interface 负责调用本地接口。调用不同语言的接口给JAVA用,他会在Native Method Stack中记录对应的本地方法,然后调用该方法时就通过Execution Engine加载对应的本地lib。

Runtime Data Area
	Stack 是java栈内存,它等价于C语言中的栈,栈的内存地址是不连续的,每个线程都拥有自己的栈。栈里面存储着的是StackFrame,栈帧。 StackFrame包含三类信息:局部变量,执行环境,操作数栈。局部变量用来存储一个类的方法中所用到的局部变量。执行环境用于保存解析器对于java字节码进行解释过程中需要的信息,包括:上次调用的方法、局部变量指针和操作数栈的栈顶和栈底指针。操作数栈用于存储运算所需要的操作数和结果。StackFrame在方法被调用时创建,在某个线程中,某个时间点上,只有一个框架是活跃的,该框架被称为Current Frame,而框架中的方法被称为Current Method,其中定义的类为Current Class。局部变量和操作数栈上的操作总是引用当前框架。当Stack Frame中方法被执行完之后,或者调用别的StackFrame中的方法时,则当前栈变为另外一个StackFrame。Stack的大小是由两种类型,固定和动态的,动态类型的栈可以按照线程的需要分配。

	Heap 是用来存放对象信息的,和Stack不同,Stack代表着一种运行时的状态。换句话说,栈是运行时单位,解决程序该如何执行的问题,而堆是存储的单位,解决数据存储的问题。Heap是伴随着JVM的启动而创建,负责存储所有对象实例和数组的。堆的存储空间和栈一样是不需要连续的,它分为Young Generation和Old Generation(也叫Tenured Generation)两大部分。Young Generation分为Eden和Survivor,Survivor又分为From Space和 ToSpace。
    和Heap经常一起提及的概念是PermanentSpace,它是用来加载类对象的专门的内存区,是非堆内存,和Heap一起组成JAVA内存,它包含MethodArea区(在没有CodeCache的HotSpotJVM实现里,则MethodArea就相当于GenerationSpace)。在JVM初始化的时候,我们可以通过参数来分别指定,PermanentSpace的大小、堆的大小、以及Young Generation和Old Generation的比值、Eden区和From Space的比值,从而来细粒度的适应不同JAVA应用的内存需求。

	Method Area 在HotSpot JVM的实现中属于非堆区,非堆区包括两部分:Permanent Generation和Code Cache,而Method Area属于Permanent Generation的一部分。Permanent Generation用来存储类信息,比如说:class definitions,structures,methods,field,method (data and code) 和 constants。Code Cache用来存储Compiled Code,即编译好的本地代码,在HotSpot JVM中通过JIT(Just In Time) Compiler生成,JIT是即时编译器,他是为了提高指令的执行效率,把字节码文件编译成本地机器代码

	PC Register	程序计数寄存器,每个JAVA线程都有一个单独的PC Register,他是一个指针,由Execution Engine读取下一条指令。如果该线程正在执行java方法,则PC Register存储的是 正在被执行的指令的地址,如果是本地方法,PC Register的值没有定义。PC寄存器非常小,只占用一个字宽,可以持有一个returnAdress或者特定平台的一个指针。
 
	Native Method Stack是供本地方法(非java)使用的栈。每个线程持有一个Native Method Stack。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YyQBEsB3-1603252392647)(F:\develop\Z_JavaEE\JVM.assets\1592705511407.png)]

Jvm 内存管理

​ JVM中的内存管理主要是指JVM对于Heap的管理,这是因为Stack,PC Register和Native Method Stack都是和线程一样的生命周期,在线程结束时自然可以被再次使用。

栈的管理

JVM允许栈的大小是固定的或者是动态变化的。Oracle 是通过-Xss 来设置其大小的。

我们一般通过减少常量,参数的个数来减少栈的增长,在程序设计时,我们把一些常量定义到一个对象中,然后来引用他们可以体现这一点。另外,少用递归调用也可以减少栈的占用。

栈是不需要垃圾回收的,尽管说垃圾回收是java内存管理的一个很热的话题,栈中的对象如果用垃圾回收的观点来看,他永远是live状态,是可以reachable的,所以也不需要回收,他占有的空间随着Thread的结束而释放。

关于栈一般会发生以下两种异常:

​ 1.当线程中的计算所需要的栈超过所允许大小时,会抛出StackOverflowError。

​ 2.当Java栈试图扩展时,没有足够的存储器来实现扩展,JVM会报OutOfMemoryError。

释放本地方法调用申请的内存

另外栈上有一点得注意的是,对于本地代码调用,可能会在栈中申请内存,比如C调用malloc(),而这种情况下,GC是管不着的,需要我们在程序中,手动管理栈内存,使用free()方法释放内存。

堆的管理
	其中Eden区里面存着是新生的对象,From Space和To Space中存放着是每次垃圾回收后存活下来的对象,所以每次垃圾回收后,Eden区会被清空。存活下来的对象先是放到From Space,当From Space满了之后移动到To Space。当To Space满了之后移动到Old Space。Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor复制过来的对象。而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。
    Old Space中则存放生命周期比较长的对象,而且有些比较大的新生对象也放在Old Space中。
    堆的大小通过-Xms和-Xmx来指定最小值和最大值,通过-Xmn来指定Young Generation的大小(一些老版本也用-XX:NewSize指定),即Eden加FromSpace和ToSpace的总大小。然后通过-XX:NewRatio来指定Eden区的大小,在Xms和Xmx相等的情况下,该参数不需要设置。通过-XX:SurvivorRatio来设置Eden和一个Survivor区的比值。
     堆异常分为两种,一种是Out of Memory(OOM),一种是Memory Leak(ML)。Memory Leak最终将导致OOM。实际应用中表现为:从Console看,内存监控曲线一直在顶部,程序响应慢,从线程看,大部分的线程在进行GC,占用比较多的CPU,最终程序异常终止,报OOM。OOM发生的时间不定,有短的一个小时,有长的10天一个月的。关于异常的处理,确定OOM/ML异常后,一定要注意保护现场,可以dump heap,如果没有现场则开启GCFlag 收集垃圾回收日志,然后进行分析,确定问题所在。如果问题不是ML的话,一般通过增加Heap,增加物理内存来解决问题,是ML的话,就修改程序逻辑。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M1EY2DNv-1603252392664)(F:\develop\Z_JavaEE\JVM.assets\1592707783330.png)]

垃圾回收

JVM中会在以下情况触发回收:

  • 对象没有被引用

  • 作用域发生未捕捉异常,程序正常执行完毕

  • 程序执行了System.exit()

  • 程序发生意外终止。

    JVM中标记垃圾使用的算法是一种根搜索算法(可达性算法)。简单的说,就是从一个叫GC Roots的对象开始,向下搜索,如果一个对象不能达到GC Roots对象的时候,说明它可以被回收了。这种算法比一种叫做引用计数法垃圾标记算法要好,因为它避免了当两个对象啊互相引用时无法被回收的现象。
    JVM中对于被标记为垃圾的对象进行回收时又分为了以下3种算法:
    1.标记-清除算法,该算法是从根集合扫描整个空间,标记存活的对象,然后在扫描整个空间对没有被标记的对象进行回收,这种算法在存活对象较多时比较高效,但会产生内存碎片。
    2.复制算法,该算法是从根集合扫描,并将存活的对象复制到新的空间,这种算法在存活对象少时比较高效。
    3.标记-整理算法,标记整理算法和标记清除算法一样都会扫描并标记存活对象,在回收未标记对象的同时会整理被标记的对象,解决了内存碎片的问题。
    JVM中,不同的内存区域作用和性质不一样,使用的垃圾回收算法也不一样,所以JVM中又定义了几种不同的垃圾回收器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jpofdXWG-1603252392666)(F:\develop\Z_JavaEE\JVM.assets\1592707928809.png)]

	1.Serial GC。从名字上看,串行GC意味着是一种单线程的,所以它要求收集的时候所有的线程暂停。这对于高性能的应用是不合理的,所以串行GC一般用于Client模式的JVM中。
    2.ParNew GC。是在SerialGC的基础上,增加了多线程机制。但是如果机器是单CPU的,这种收集器是比SerialGC效率低的。
    3.Parrallel Scavenge GC。这种收集器又叫吞吐量优先收集器,而吞吐量=程序运行时间/(JVM执行回收的时间+程序运行时间),假设程序运行了100分钟,JVM的垃圾回收占用1分钟,那么吞吐量就是99%。Parallel Scavenge GC由于可以提供比较不错的吞吐量,所以被作为了server模式JVM的默认配置。
    4.ParallelOld是老生代并行收集器的一种,使用了标记整理算法,是JDK1.6中引进的,在之前老生代只能使用串行回收收集器。
    5.Serial Old是老生代client模式下的默认收集器,单线程执行,同时也作为CMS收集器失败后的备用收集器。
    6.CMS又称 响应时间优先回收器,使用标记清除算法。他的回收线程数为(CPU核心数+3)/4,所以当CPU核心数为2时比较高效些。CMS分为4个过程:初始标记、并发标记、重新标记、并发清除。
    7.GarbageFirst(G1)。比较特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation。它是在JDK6的某个版本中才引入的,性能比较高,同时注意了吞吐量和响应时间。
GC中有一种情况叫做Full GC,以下几种情况会触发Full GC也叫MajorGC:
    1.Tenured Space空间不足以创建大的对象或者数组,会执行FullGC,并且当FullGC之后空间如果还不够,那么会OOM: java heap space。
    2.Permanet Generation的大小不足,存放了太多的类信息,在非CMS情况下回触发FullGC。如果之后空间还不够,会OOM:PermGen space。
    3.CMS GC时出现promotion failed和concurrent mode failure时,也会触发FullGC。promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrent mode failure是在执行CMS GC的过程中同时有对象要放入旧生代,而此时旧生代空间不足造成的。
    4.判断MinorGC后,要晋升到TenuredSpace的对象大小大于TenuredSpace的大小,也会触发FullGC。
    可以看出,当FullGC频繁发生时,一定是内存出问题了。

JVM数据格式规范和Class文件

数据类型规范
 依据冯诺依曼的计算机理论,计算机最后处理的都是二进制的数,而JVM是怎么把java文件最后转化成了各个平台都可以识别的二进制呢?JVM自己定义了一个抽象的存储数据单位,叫做Word。一个字足够大以持有byte、char、short、int、float、reference或者returnAdress的一个值,两个字则足够持有更大的类型long、double。它通常是主机平台一个指针的大小,如32位的平台上,字是32位。
    同时JVM中定义了它所支持的基本数据类型,包括两部分:数值类型和returnAddress类型。数值类型分为整形和浮点型。
returnAddress类型的值是Java虚拟机指令的操作码的指针。
    对比java的基本数据类型,jvm的规范中没有boolean类型。这是因为jvm中堆boolean的操作是通过int类型来进行处理的,而boolean数组则是通过byte数组来进行处理。
    至于String,我们知道它存储在常量池中,但他不是基本数据类型,之所以可以存在常量池中,是因为这是JVM的一种规定。如果查看String源码,我们就会发现,String其实就是一个基于基本数据类型char的数组。
字节码文件ByteCode
magic:
    魔数,魔数的唯一作用是确定这个文件是否为一个能被虚拟机所接受的Class文件。魔数值固定为0xCAFEBABE,不会改变。
minor_version、major_version:
    分别为Class文件的副版本和主版本。它们共同构成了Class文件的格式版本号。不同版本的虚拟机实现支持的Class文件版本号也相应不同,高版本号的虚拟机可以支持低版本的Class文件,反之则不成立。
constant_pool_count:
    常量池计数器,constant_pool_count的值等于constant_pool表中的成员数加1。
constant_pool[]:
    常量池,constant_pool是一种表结构,它包含Class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其它常量。常量池不同于其他,索引从1开始到constant_pool_count -1。
access_flags:
    访问标志,access_flags是一种掩码标志,用于表示某个类或者接口的访问权限及基础属性。
this_class:
    类索引,this_class的值必须是对constant_pool表中项目的一个有效索引值。constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类或接口。
super_class:
    父类索引,对于类来说,super_class的值必须为0或者是对constant_pool表中项目的一个有效索引值。如果它的值不为0,那constant_pool表在这个索引处的项必须为CONSTANT_Class_info类型常量,表示这个Class文件所定义的类的直接父类。当然,如果某个类super_class的值是0,那么它必定是java.lang.Object类,因为只有它是没有父类的。
interfaces_count:
    接口计数器,interfaces_count的值表示当前类或接口的直接父接口数量。
interfaces[]:
    接口表,interfaces[]数组中的每个成员的值必须是一个对constant_pool表中项目的一个有效索引值,它的长度为interfaces_count。每个成员interfaces[i] 必须为CONSTANT_Class_info类型常量。
fields_count:
    字段计数器,fields_count的值表示当前Class文件fields[]数组的成员个数。
fields[]:
    字段表,fields[]数组中的每个成员都必须是一个fields_info结构的数据项,用于表示当前类或接口中某个字段的完整描述。
methods_count:
    方法计数器,methods_count的值表示当前Class文件methods[]数组的成员个数。
methods[]:
    方法表,methods[]数组中的每个成员都必须是一个method_info结构的数据项,用于表示当前类或接口中某个方法的完整描述。
attributes_count:
    属性计数器,attributes_count的值表示当前Class文件attributes表的成员个数。
attributes[]:
    属性表,attributes表的每个项的值必须是attribute_info结构。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KswR6S4q-1603252392671)(F:\develop\Z_JavaEE\JVM.assets\1592708140305.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMl5U6I7-1603252392677)(C:\Users\15753\AppData\Roaming\Typora\typora-user-images\1592547999924.png)]

类加载器

1. java中能不能自定义java.lang.Object类, 如果可以证明你使用的Object是你自定义的Object, 如果不可以请说明原因

全限定名相同的话是不可以自定义的,因为java.lang.Object已经被加载到内存中了,不可能同时存在两个,除非你把Object在同名目录替换掉。

2.默认类加载器有哪几个,它们的关系是什么; 默认父加载器能不能加载子类加载器的Class
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verrfication)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading) 7个阶段。其中验证、准备、解析3个部分统称为连接(Linking)。

在加载阶段,虚拟机需要完成以下3件事情:

1. 通过一个类的全限定名来获取定义此类的二进制字节流。
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据结构的访问入口。

就第一条来说,可以从zip包里读取,这是jar,war,ear等格式的基础;也可以从网络中获取,如applet;或者运行时计算生成,就是动态代理技术。下面也着重来讨论一下第一条。

类加载器:其实就是第一件事情(通过一个类的全限定名来获取定义此类的二进制字节流),实现这个动作的代码模块就称为类加载器。
可以用在:类层次划分、OSGi、热部署、代码加密等领域。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性。
比如自定义一个类加载器加载一个类,与系统加载器加载的类,即使是来自同一个Class文件,因为类加载器不同,依然是两个独立的类,instanceof结果是false;

从JAVA虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),属于虚拟机自身的一部分(HotSpot是用c++实现的);另一种就是所有其他的类加载器,这些类加载器都由Java语言实现,独立于虚拟机外部,并且全部都继承自抽象类java.lang.ClassLoader。

大部分的程序都会使用到以下三种类加载器:
	启动类加载器(Bootstrap ClassLoader):负责加载< JAVA_HOME >\lib目录中的或者-Xbootclasspath参数指定的路径中的虚拟机识别的类库 到内存中。(识别按文件名识别,比如rt.jar).
	扩展类加载器(Extension ClassLoader):负责< JAVA_HOME >\lib\ext目录中的或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用。
	应用程序类加载器(Application ClassLoader):也称为系统类加载器,可以由ClassLoader中的getSystemClassLoader()方法获得,负责加载ClassPath上所指定的类库,开发者可以直接使用,一般情况下是程序中默认的类加载器。

	双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

	当我们自己写了一个java.lang.Object类并放在ClassPath中,应用程序类加载器委托扩展类加载器,扩展类加载器再委托启动类加载器,因为后两个路径中都没有这个class,所以应用类加载器会去尝试加载这个类。但是,真正的java.lang.Object类已经被启动类加载器加载到了虚拟机内存中,如果应用类加载器也成功加载的话,那将会出现多个不同的Object类,程序就会变的一片混乱。

“如果我自己写的Object类放在rt.jar里并且就放在< JAVA_HOME >\bin”目录下会怎么样?“
jvm确实是只根据全限定名来识别,成功加载了我们的Object类。只不过由于缺少一些方法而报错了。

https://blog.csdn.net/wangccpal/article/details/78049988?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-1
3. java.sql中的类是由哪一个类加载器加载的

APPClassLoader

4. MySQL的驱动包是由谁加载的,这种加载是如何实现的
5. 类加载的过程有哪些步骤,这些步骤分别做了什么事
6.实现一个实体类文件生成工具类,要求传入数据库链接信息,包结构,最终实现每一张表对应一个java文件
https://blog.csdn.net/weixin_44544465/article/details/91347287
各种拼接,最后输出为java文件
涉及到数据库元数据

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值