面试题-JVM
1. 说一下JVM的内存结构?
见复习笔记
2. 栈帧里面包含哪些东西?
-
答案
局部变量表、操作数栈、动态链接、返回值、一些附加信息等
3. 程序计数器有什么用?
-
答案
程序计数器是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器。这里面存的,就是当前线程执行的进度。
程序计数器还存储了当前正在运行的流程,包括正在执行的指令、跳转、分支、循环、异常处理等。
4. 字符串常量存放在哪个区域?
- 答案
- 字符串常量池,已经移动到堆上(jdk8之前是perm区),也就是执行intern方法后存的地方。
- 类文件常量池,constant_pool,是每个类每个接口所拥有的,这部分数据在方法区,也就是元数据区。而运行时常量池是在类加载后的一个内存区域,它们都在元空间。
5. 你熟悉哪些垃圾收集算法?
- 答案
- 标记清除(缺点是碎片化)
- 复制算法(缺点是需要双倍空间)
- 标记整理算法(效率比前两者差)
- 分代收集算法(老年代一般使用“标记-清除”、“标记-整理”算法,年轻代一般用复制算法)
6. Java里有哪些引用类型?
-
答案
-
强引用
这种引用属于最普通最强硬的一种存在,只有在和 GC Roots 断绝关系时,才会被消掉。 -
软引用
软引用用于维护一些可有可无的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。可以看到,这种特性非常适合用在缓存技术上。比如网页缓存、图片缓存等。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java 虚拟机就会把这个软引用加入到与之关联的引用队列中。
-
弱引用
弱引用对象相比较软引用,要更加无用一些,它拥有更短的生命周期。当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。弱引用拥有更短的生命周期,在 Java中,用 java.lang.ref.WeakReference 类来表示。它的应用场景和软引用类似,可以在一些对内存更加敏感的系统里采用。 -
虚引用
这是一种形同虚设的引用,在现实场景中用的不是很多。虚引用必须和引用队列(ReferenceQueue)联合使用。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。实际上,虚引用的 get,总是返回 null。
-
7. JVM怎么判断一个对象是不是要回收?
-
答案
引用计数法(缺点是对于相互引用的对象,无法进行清除)
可达性分析
8. GC Roots有哪些?
-
答案
GC Roots 是一组必须活跃的引用。用通俗的话来说,就是程序接下来通过直接引用或者间接引用,能够访问到的潜在被使用的对象。
GC Roots 包括:
Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用。-
所有当前被加载的 Java 类。
-
Java 类的引用类型静态变量。
-
运行时常量池里的引用类型常量(String 或 Class 类型)。
-
JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类。
-
用于同步的监控对象,比如调用了对象的 wait() 方法。
-
JNI handles,包括 global handles 和 local handles。
这些 GC Roots 大体可以分为三大类,下面这种说法更加好记一些:
-
活动线程相关的各种引用。
-
类的静态变量的引用。
-
JNI 引用。
有两个注意点:
我们这里说的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的。
GC 过程是找出所有活对象,并把其余空间认定为“无用”;而不是找出所有死掉的对象,并回收它们占用的空间。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也会非常快。 -
9. 你知道哪些GC类型?
- 答案
-
Minor GC:发生在年轻代的 GC。
-
Major GC:发生在老年代的 GC。
-
Full GC:全堆垃圾回收。比如 Metaspace 区引起年轻代和老年代的回收。
-
10. 对象都是优先分配在年轻代上的吗?
-
答案
不是。当新生代内存不够时,老年代分配担保。而大对象则是直接在老年代分配。
11. 你了解过哪些垃圾收集器?
-
答案
年轻代
-
Serial 垃圾收集器(单线程,通常用在客户端应用上。因为客户端应用不会频繁创建很多对象,用户也不会感觉出明显的卡顿。相反,它使用的资源更少,也更轻量级。)
-
ParNew 垃圾收集器(多线程,追求降低用户停顿时间,适合交互式应用。)
-
Parallel Scavenge 垃圾收集器(追求 CPU 吞吐量,能够在较短时间内完成指定任务,适合没有交互的后台计算。)
老年代
-
Serial Old 垃圾收集器
-
Parallel Old垃圾收集器
-
CMS 垃圾收集器(以获取最短 GC 停顿时间为目标的收集器,它在垃圾收集时使得用户线程和GC 线程能够并发执行,因此在垃圾收集过程中用户也不会感到明显的卡顿。)
-
12. 说说CMS垃圾收集器的工作原理
-
答案
Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间, 和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。
最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。
CMS 工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下 4 个阶段:-
初始标记
只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。 -
并发标记
进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。 -
重新标记
为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。 -
并发清除
清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户线程一起并发工作, 所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行。
-
13. 说说G1垃圾收集器的工作原理
-
答案
**优点:指定最大停顿时间、分Region的内存布局、按收益动态确定回收集。
**G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的,但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。
虽然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域(不需要连续)的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在有限的时间内获取尽可能高的收集效率。
G1收集器的运作过程大致可划分为以下四个步骤:
- 初始标记 (Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
- 并发标记 (Concurrent Marking****):从GC Root开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
- 最终标记 (Final Marking):对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
- 筛选回收 (Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。
从上述阶段的描述可以看出,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的
14. 说说类加载的过程
-
答案
加载
验证
准备(为一些类变量分配内存,并将其初始化为默认值)
解析(将符号引用替换为直接引用。类和接口、类方法、接口方法、字段等解析)
初始化
15. 说下有哪些类加载器?
- 答案
- Bootstrap ClassLoader(引导类加载器 / 启动类加载器)
- Extention ClassLoader(扩展类加载器)
- App ClassLoader(应用类加载器)
16. 什么是双亲委派机制?
-
答案
双亲委派机制的意思是除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会真正的加载。
17. 双亲委派机制可以被违背吗?请举例说明
-
答案
可以被违背。
打破双亲委派的例子:Tomcat对于一些需要加载的非基础类,会由一个叫作WebAppClassLoader的类加载器优先加载。等它加载不到的时候,再交给上层的ClassLoader进行加载。
这个加载器用来隔绝不同应用的 .class 文件,比如你的两个应用,可能会依赖同一个第三方的不同版本,它们是相互没有影响的。
18. Tomcat是怎么打破双亲委派机制的呢?
-
答案
是通过重写ClassLoader#loadClass和ClassLoader#findClass 实现的。可以看图中的WebAppClassLoader,它加载自己目录下的.class文件,并不会传递给父类的加载器。但是,它却可以使用 SharedClassLoader 所加载的类,实现了共享和分离的功能。
19. Java对象的布局了解过吗?
-
答案
对象头区域此处存储的信息包括两部分:
- 对象自身的运行时数据( MarkWord ),占8字节
存储 hashCode、GC 分代年龄、锁类型标记、偏向锁线程 ID 、 CAS 锁指向线程LockRecord 的指针等, synconized 锁的机制与这个部分( markwork )密切相关,用markword 中最低的三位代表锁的状态,其中一位是偏向锁位,另外两位是普通锁位。 - 对象类型指针( Class Pointer ),占4字节
对象指向它的类元数据的指针、 JVM 就是通过它来确定是哪个 Class 的实例。
实例数据区域
此处存储的是对象真正有效的信息,比如对象中所有字段的内容。对齐填充区域
JVM 的实现 HostSpot 规定对象的起始地址必须是 8 字节的整数倍,换句话来说,现在 64 位的 OS 往外读取数据的时候一次性读取 64bit 整数倍的数据,也就是 8 个字节,所以 HotSpot为了高效读取对象,就做了"对齐",如果一个对象实际占的内存大小不是 8byte 的整数倍时,就"补位"到 8byte 的整数倍。所以对齐填充区域的大小不是固定的。
- 对象自身的运行时数据( MarkWord ),占8字节
20. 什么情况下会发生栈内存溢出?
-
答案
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型。
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。
如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)。
21. JVM新生代中为什么要分为Eden和Survivor?
-
答案
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Major GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)。
22. JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代?
-
答案
当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。
大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。
23. 什么是指令重排序?
-
答案
在实际运行时,代码指令可能并不是严格按照代码语句顺序执行的。大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待。通过乱序执行的技术,处理器可以大大提高执行效率。而这就是指令重排。
24. 说说你知道的几种主要的JVM参数
- 答案
- 堆栈配置相关
-
- Xmx3550m:最大堆大小为3550m。
-
- Xms3550m:设置初始堆大小为3550m。
-
- Xmn2g:设置年轻代大小为2g。
-
- Xss128k:每个线程的堆栈大小为128k。
-
- XX:MaxPermSize:设置持久代大小为16m。
-
- XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与老年代的比值(除去持久代)。
-
- XX:SurvivorRatio=4:设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2 : 4,一个Survivor区占整个年轻代的1/6。
- -XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入老年代。
-
- 垃圾收集器相关
- -XX:+UseParallelGC:选择垃圾收集器为并行收集器。
- -XX:ParallelGCThreads=20:设置并行收集器的线程数。
- -XX:+UseConcMarkSweepGC:设置老年代为并发收集。
- -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
- -XX:+UseCMSCompactAtFullCollection:打开对老年代的压缩。可能会影响性能,但是可以消除碎片。
- 辅助信息相关
-
-XX:+PrintGC:输出垃圾回收相关信息:
[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K-10414K(130112K), 0.0650971 secs]
-
-XX:+PrintGCDetails:输出垃圾回收相关信息:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K),0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured:112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268secs
-
- 堆栈配置相关
25. 怎么打出线程栈信息?
-
答案
输入jps,获得进程号。
top -Hp pid 获取本进程中所有线程的CPU耗时性能
jstack pid命令查看当前java进程的堆栈状态
或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个txt文件。
可以使用fastthread 堆栈定位(fastthread.io)
26. 为什么需要双亲委派机制?
- 答案
- 避免类的重复加载
- 防止核心API被篡改
27. 打破双亲委派模型?
-
答案
打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。
28. 说一下堆和栈的区别
-
答案
1)物理地址
堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)
栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。2)内存分别
堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。
栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。3)存放的内容
堆存放的是对象的实例和数组。因此该区更关注的是数据的存储
栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。4)程序的可见度
堆对于整个应用程序都是共享、可见的。
栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
29. 说一下Java对象的创建过程
- 答案
-
类加载检查: 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
-
分配内存: 在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
选择以上2种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的。在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
- CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
- TLAB: 为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配。
-
**初始化零值: **内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
-
设置对象头: 初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
-
执行 init 方法: 在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,init 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 init 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
-
30. 说一下堆内存中对象的分配的基本策略
-
答案
eden区、s0区、s1区都属于新生代,tentired 区属于老年代。大部分情况,对象都会首先在Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden区->Survivor 区后对象的初始年龄变为1),当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数
-XX:MaxTenuringThreshold 来设置。
另外,大对象和长期存活的对象会直接进入老年代。
31. Java会存在内存泄漏吗?
-
答案
内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。
但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是java中内存泄露的发生场景。
32. 如何判断一个类是一个无用的类?
-
答案
方法区主要回收的是无用的类,判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是 “无用的类”:
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的** ClassLoader 已经被回收。**
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。
33. 介绍一下类文件结构吧
- 答案
-
魔数: 确定这个文件是否为一个能被虚拟机接收的 Class 文件。
-
Class 文件版本 :Class 文件的版本号,保证编译正常执行。
-
常量池 :常量池主要存放两大常量:字面量和符号引用。
-
访问标志 :标志用于识别一些类或者接口层次的访问信息,包括:这个 Class 是类还是接口,是否为 public 或者 abstract 类型,如果是类的话是否声明为 final 等等。
-
当前类索引,父类索引 :类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 java.lang.Object 之外,所有的 java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为0。
-
接口索引集合 :接口索引集合用来描述这个类实现了那些接口,这些被实现的接口将按implents(如果这个类本身是接口的话则是extends) 后的接口顺序从左到右排列在接口索引集合中。
-
字段表集合 :描述接口或类中声明的变量。字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
-
方法表集合 :类中的方法。
-
属性表集合 : 在 Class 文件,字段表,方法表中都可以携带自己的属性表集合。
-
34. JVM调优命令有哪些?
- 答案
-
jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。
-
jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
-
jmap,JVM Memory Map命令用于生成heap dump文件。
-
jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看。
-
jstack,用于生成java虚拟机当前时刻的线程快照。
-
jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。
-
35. 如何判断一个常量是废弃常量?
-
答案
运行时常量池主要回收的是废弃的常量。假如在常量池中存在字符串"abc",如果当时没有任何String对象引用该字符串常量的话,就说明常量"abc"就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc"就会被系统清理出常量池。