目录
1. 如何判断一个常量是废弃常量 ?
2. 程序计数器为什么是私有的?
3. JRE、JDK、JVM 及 JIT 之间有什么不同?
4. JVM调优命令有哪些?
5. 说一下 JVM 调优的工具
6. 介绍一下类文件结构吧
7. 如何判断一个类是无用的类
8. 如何判断对象已经死亡了呢
10. Minor Gc和Full GC 有什么不同呢
【写在前面】
此文题目和答案都非原创。
是搜集了网络上分享的关于Java面试常见问题汇总,然后又逐个搜寻了答案,整理于此。
供自学,感谢相关题目和答案的原创分享者。
1. 如何判断一个常量是废弃常量 ?
运行时常量池主要回收的是废弃的常量。
假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。
注:JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
2. 程序计数器为什么是私有的?
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
注:如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
3. JRE、JDK、JVM 及 JIT 之间有什么不同?
(1)java虚拟机(JVM)
使用java编程语言的主要优势就是平台的独立性。java实现平台的独立性就是通过虚拟机,它抽象化了硬件设备,开发者和他们的程序得以操作系统。虚拟机的职责就是处理和操作系统之间的交流。java不同的接口规范对任何平台都有良好的支持,因为jvm很好的实现了每个平台的规范。jvm可以理解伪代码字节码,在用户和操作系统之间建立了一层枢纽。
(2)java运行时环境(JRE)
java运行时环境是JVM的一个超集。JVM对于一个平台或者操作系统是明确的,而JRE是一个一般的概念,他代表了完整的运行时环境。我们在jre文件夹中看到的所有的jar文件和可执行文件都会变成运行时的一部分。事实上,运行时JRE变成了JVM。所以对于一般情况时候使用JRE,对于明确的操作系统来说使用JVM。当你下载了JRE的时候,也就自动下载了JVM。
(3)java开发工具箱(JDK)
java开发工具箱指的是编写一个java应用所需要的所有jar文件和可执行文件。事实上,JRE是JDK的一部分。如果下载了JDK,会看到一个名叫JRE的文件夹在里面。JDK中要被牢记的jar文件就是tools.jar,它包含了用于执行java文档的类还有用于类签名的jar包。
(4)即时编译器(JIT)
即时编译器是种特殊的编译器,它通过有效的把字节码变成机器码来提高JVM的效率。JIT这种功效很特殊,因为他把检测到的相似的字节码编译成单一运行的机器码,从而节省了CPU的使用。这和其他的字节码编译器不同,因为他是运行时(第一类执行的编译?) the firs of its kind to perform the compilation (从字节码到机器码)而不是在程序运行之前。正是因为这些,动态编译这个词汇才和JIT有那么紧密的关系。
4. JVM调优命令有哪些?
4.1 前置操作先定位pid、tid:
一般是运维团队首先受到报警信息(CPU Memory)
top命令观察到问题:内存不断增长 CPU占用率居高不下
top -Hp 观察进程中的线程,哪个线程CPU和内存占比高
4.2 常用调优命令
(1)jstack:导出java进程中各个栈内线程信息 eg:jstack -l pid >>pid.txt
(2)jinfo pid: 查看当前虚拟机的运行参数。
(3)jconsole/jvisualVM:图形化界面监测jvm进程中线程、cpu、内存、变量 如果连接远程机需要远程机上的项目启动时设置相关参数,本地线程无需设置,如何实现不配置远程主机的启动参数实现监测。
(4)jmap:是一个可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输出成文本。打印出某个java进程(使用pid)内存内的,所有‘对象’的情况(如:产生了哪些对象,及其数量,对象内部存值等)。 jmap可以主动导出jvm内存信息,也可以在项目启动时指定在程序内存溢出时自动导出内存信息。
(5)Memory Analyzer(MAT):分析堆内存DUMP 文件。
(6)jhat:是sun 1.6及以上版本中自带的一个用于分析JVM 堆DUMP 文件的工具,基于此工具可分析JVM HEAP 中对象的内存占用情况:jhat -J-Xmx1024M [file] 。
5. 说一下 JVM 调优的工具
《JVM性能调优》:JVM性能调优_白白胖胖充满希望-CSDN博客_jvm调优
《JVM系列(七):jvm调优-工具篇》:JVM系列(七):jvm调优-工具篇 - 简书
《10. JVM常用命令》:10. JVM常用命令_wwww641788662的博客-CSDN博客_jvm命令
《JVM调优之JConsole和JVisualVM工具使用》:JVM调优之JConsole和JVisualVM工具使用_Thancks-CSDN博客_jconsole
《jstack查找CPU高和死锁》:jstack_JustryDeng-CSDN博客_jstack
6. 介绍一下类文件结构吧
根据 Java 虚拟机规范,类文件由单个 ClassFile 结构组成:
7. 如何判断一个类是无用的类
方法区主要回收的是无用的类。
要判定一个类是否是“无用的类”需要同时满足下面3个条件才能算是 “无用的类” :
(1)这个类的实例被回收了,java堆中没有任何这个类的实例。
(2)这个类的ClassLoader被回收了。
(3)这个类的.class对象没有在任何地方被引用,无法在任何地方通过反射访问这个类的方法。
8. 如何判断对象已经死亡了呢
(1)引用计数法
程序给对象添加一个引用计数器,每有一个变量引用它时,计数器加1。当引用断开时,计数器减1。当计数器为0时,代表着没有任何变量引用它,该对象就是死亡状态,JVM需要对此类对象进行回收。
引用计数法的实现简单,效率也很高。但绝大数主流的虚拟机并没有采取此计数算法来管理内存,原因是此计数算法无法回收那些具有相互循环引用的对象,此类对象确实已经不再被使用,但由于互相引用着对方,导致各自的计数器都不为0,因此JVM无法回收它们。
(2)可达性分析法
程序创建一系列的GC Roots作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象与GC Roots没有任何引用链相连的话,即此对象到GC Roots不可达,则证明此对象是不可用的,JVM稍后将会对此类对象进行回收。
大多数主流的JVM都采用这样的算法来管理内存,它能够解决对象之间的循环引用的问题。对象与对象之间虽然有循环引用,当他们到GC Roots没有任何引用链,系统还是判定它们为可回收对象。
当通过这两种方式确定对象已经没有任何变量引用它们时,JVM将在合适的时机对此类对象进行回收。
9. Java会存在内存泄漏吗?请简单描述
9.1 内存泄露就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于Java使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要它们和根进程不可达的,那么GC也是可以回收它们的
9.2 java中的内存泄露的情况:
理论上Java因为有垃圾回收机制(GC)不会存在内存泄露问题(这也是Java被广泛使用于服务器端编程的一个重要原因);然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄露的发生。例如Hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空(flush)一级缓存就可能导致内存泄露。
(1)长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景,通俗地说,就是程序员可能创建了一个对象,以后一直不再使用这个对象,这个对象却一直被引用,即这个对象无用但是却无法被垃圾回收器回收的,这就是java中可能出现内存泄露的情况,例如,缓存系统,我们加载了一个对象放在缓存中(例如放在一个全局map对象中),然后一直不再使用它,这个对象一直被缓存引用,但却不再被使用。
检查java中的内存泄露,一定要让程序将各种分支情况都完整执行到程序结束,然后看某个对象是否被使用过,如果没有,则才能判定这个对象属于内存泄露。
(2)如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持久外部类的实例对象,这个外部类对象将不会被垃圾回收,这也会造成内存泄露。
(3)当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄露。
10. Minor Gc和Full GC 有什么不同呢
(1)新生代GC(Minor GC)
指发生在新生代的垃圾收集动作,因为Java对象大多都具有朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。
(2)老年代GC(Major GC/Full GC)
指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scanvenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
11. 堆内存中对象的分配的基本策略
(1)将新生代分为Eden区,From区,To区是基于其所用的垃圾回收决定的(标记复制算法)。这3个区的内存分配过程如下:
1)对象优先在Eden区分配,当进行YGC时,会将存活的对象放到From区,To区空着不用哈。
2)当第二次进行YGC时,会将From区和Eden区存活的对象复制到To区,此时Eden区和From区就为空哈。
3)当第三次进行YGC时,会将To区和Eden区存活的对象复制到From区,此时Eden区和To就为空。按照此规律,循环往复下去
(2)对象的分配策略:
对象优先在Eden区分配,如果能放下,就放在Eden区。如果放不下就会进行一次YGC(此时Eden区和一个Survivor区为空哈)。再次尝试在Eden区分配,如果能放下,就放在Eden区。
如果放不下,看Old区能否放得下。如果能放下,就放在Old区。如果放不下,则进行一次FGC,再次尝试在Old区分配,如果能放下,就放在Old区,否则报OOM
(3)当进行YGC时,可能会引起后续一系列的各种操作:
当老年代可用空间大于新生代大小时,直接触发YGC即可。否则判断老年代可用空间是否大于历次晋升到老年代对象的平均大小。是的话触发YGC,否则说明Old区有可能放不下新生代存活下来的对象,先触发一次FGC,再触发YGC
前面说过当发生YGC的时候,会将Eden区和其中一个Survivor区存活的对象移动到另一个Survivor区。当Survivor空间够时,将存活的对象放在Survivor区。
当Old区空间够时,将存活对象放在Old区。如果不够,触发FGC,再次判断是否能放下。是,则放在Old区,否则OOM
(4)内存分配策略:
对象优先在Eden分配
大对象直接进入老年代
长期存活的对象将进入老年代
动态对象年龄判定
空间分配担保
12. 对象的访问定位有哪几种方式
建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有:①使用句柄和②直接指针两种:
(1)句柄:如果使用句柄,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
(2)直接指针:如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference 中存储的直接就是对象的地址。
13. Java对象的创建过程
(1)类加载检查
(2)分配内存
(3)初始化零值
(4)设置对象头
(5)执行init方法
(参考文章:Java对象的创建过程_hash_Delhi的博客-CSDN博客_说一下java对象的创建过程)
14. Java 8 为什么要将永久代(PermGen)替换为元空间(metaSpace)呢?
参考文章:
JDK8-废弃永久代(PermGen)迎来元空间(metaspace) - 余磊 - 博客园 (cnblogs.com)
Davids原理探究:JDK8将永久代(PermGen)替换为元空间(metaSpace)的原因_Davids_的博客-CSDN博客



