什么是本地方法栈?
Native Method Stacks 翻译过来就是本地方法栈,与Java虚拟机栈一样,但这里的栈是针对native修饰的方法的,比如System、Unsafe、Object类中的相关native方法。
public class Object {
//native修饰的方法
private static native void registerNatives();
public final native Class<?> getClass();
public native int hashCode();
protected native Object clone() throws CloneNotSupportedException;
public final native void notify();
//.......
}
public final class System {
//native修饰的方法
private static native void registerNatives();
static {
registerNatives();
}
public static native long currentTimeMillis();
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
//.....
}
public final class Unsafe {
//native修饰的方法
private static native void registerNatives();
public native int getInt(Object var1, long var2);
public native void putInt(Object var1, long var2, int var4);
public native Object getObject(Object var1, long var2);
public native void putObject(Object var1, long var2, Object var4);
public native boolean getBoolean(Object var1, long var2);
//...
}
面试常问:JVM运行时区那些和线程有直接的关系和间接的关系,哪些区会发生OOM?
每个区域是否为线程共享,是否会发生OOM

如何判断对象是垃圾对象?
引用计数法
给对象添加一个引用计数器,每当一个地方引用它object时技术加1,引用失去以后就减1,计数为0说明不再引用。
优点:实现简单,判定效率高 缺点:无法解决对象相互循环引用的问题,对象A中引用了对象B,对象B中引用对象A。

public class A {
public B b;
}
public class B {
public C c;
}
public class C {
public A a;
}
public class Test{
private void test(){
A a = new A();
B b = new B();
C c = new C();
a.b=b;
b.c=c;
c.a=a;
}
}
可达性分析算法
当一个对象到GC Roots没有引用链相连,即就是GC Roots到这个对象不可达时,证明对象不可用。

GC Roots种类
Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等。也就是与我们栈帧相关的各种引用。
所有当前被加载的 Java 类。
Java 类的引用类型静态变量。
运行时常量池里的引用类型常量(String 或 Class 类型)。
JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类。
用于同步的监控对象,比如调用了对象的 wait() 方法。
public class Test{
private void test(C c){
A a = new A();
B b = new B();
a.b=b;
//这里的a/b/c都是GC Root;
}
}
对象的引用类型有哪些?
强引用:User user=new User();我们开发中使用最多的对象引用方式。特点:我们平常典型编码Object obj = new Object()中的obj就是强引用。通过关键字new创建的对象所关联的引用就是强引用。当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。 软引用:SoftReference object=new SoftReference(new Object()); 特点:软引用通过SoftReference类实现。软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。 弱引用:WeakReference object=new WeakReference (new Object();ThreadLocal中有使用. 弱引用通过WeakReference类实现。弱引用的生命周期比软引用短。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。应用场景:弱应用同样可用于内存敏感的缓存。 虚引用:几乎没见过使用, ReferenceQueue 、PhantomReference 缺点:标记和清除效率不高,标记和清除之后会产生大量不连续的内存碎片
finalize()方法有什么作用?
这个方法就有点类似,某个人被拍了死刑,但是不一定会死。
即使在可达性分析算法中不可达的对象,也并非一定是“非死不可”的,这时候他们暂时处于“缓刑”阶段,真正宣告一个对象死亡至少要经历两个阶段:
1、如果对象在可达性分析算法中不可达,那么它会被第一次标记并进行一次刷选,刷选的条件是是否需要执行finalize()方法(当对象没有覆盖finalize()或者finalize()方法已经执行过了(对象的此方法只会执行一次)),虚拟机将这两种情况都会视为没有必要执行)。
2、如果这个对象有必要执行finalize()方法会将其放入F-Queue队列中,稍后GC将对F-Queue队列进行第二次标记,如果在重写finalize()方法中将对象自己赋值给某个类变量或者对象的成员变量,那么第二次标记时候就会将它移出“即将回收”的集合。
垃圾回收算法有哪些?
标记-清除
第一步:就是找出活跃的对象。我们反复强调 GC 过程是逆向的, 根据 GC Roots 遍历所有的可达对象,这个过程,就叫作标记。
第二部:除了上面标记出来的对象以外,其余的都清楚掉。

缺点:对象存活率高时,复制效率会较低,浪费内存。
复制
新生代使用,新生代分中Eden:S0:S1= 8:1:1,其中后面的1:1就是用来复制的。
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次
清除掉。
一般对象分配都是进入新生代的eden区,如果Minor GC还存活则进入S0区,S0和S1不断对象进行复制。对象存活年龄最大默认是15,大对象进来可能因为新生代不存在连续空间,所以会直接接入老年代。任何使用都有新生代的10%是空着的。

标记整理
它的主要思路,就是移动所有存活的对象,且按照内存地址顺序依次排列,然后将末端内存地址以后的内存全部回收。 但是需要注意,这只是一个理想状态。对象的引用关系一般都是非常复杂的,我们这里不对具体的算法进行描述。我们只需要了解,从效率上来说,一般整理算法是要低于复制算法的。这个算法是规避了内存碎片和内存浪费。
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

从上面的三个算法来看,其实没有绝对最好的回收算法,只有最适合的算法。
垃圾收集器
垃圾收集器就是垃圾回收算法的实现,下面就来聊聊现目前有哪些垃圾收集器。
新生代有哪些垃圾收集器
serial
Serial收集器是最基本、发展历史最悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。
它是一种单线程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。
优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
应用:Client模式下的默认新生代收集器
收集过程:

ParNew
可以把这个收集器理解为Serial收集器的多线程版本。
优点:在多CPU时,比Serial效率高。
缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差。
算法:复制算法
应用:运行在Server模式下的虚拟机中首选的新生代收集器
收集过程:

Parallel Scanvenge
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集
器,看上去和ParNew一样,但是Parallel Scanvenge更关注 系统的吞吐量 ;
吞吐量 = 运行用户代码的时间 / (运行用户代码的时间 + 垃圾收集时间)
比如虚拟机总共运行了120秒,垃圾收集时间用了1秒,吞吐量=(120-1)/120=99.167%。
若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。
可设置参数:
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间,
-XX:GC Time Ratio直接设置吞吐量的大小。
老年代有哪些垃圾收集器
CMS=Concurrent Mark Sweep
特点:最短回收停顿时间,
回收算法:标记-清除
回收步骤:
初始标记:标记GC Roots直接关联的对象,速度快 并发标记:GC Roots Tracing过程,耗时长,与用户进程并发工作 重新标记:修正并发标记期间用户进程运行而产生变化的标记,好事比初始标记长,但是远远小于并发标记 表发清除:清除标记的对象
缺点:对CPU资源非常敏感,CPU少于4个时,CMS岁用户程序的影响可能变得很大,有此虚拟机提供了“增量式并发收集器”;无法回收浮动垃圾;采用标记清除算法会产生内存碎片,不过可以通过参数开启内存碎片的合并整理。
收集过程:

serial old
Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算
法",运行过程和Serial收集器一样。
适用场景:JDK1.5前与Parallel Scanvenge配合使用,作为CMS的后备预案;
收集过程:

Parallel old
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和"标记-整理算法"进行垃圾
回收,吞吐量优先;
回收算法:标记-整理
适用场景:为了替代serial old与Parallel Scanvenge配合使用
收集过程:

G1 收集器
G1(Garbage first) 收集器是 jdk1.7 才正式引用的商用收集器,现在已经成为JDK9 默认的收集器。前面几款收集器收集的范围都是新生代或者老年代,G1 进行垃圾收集的范围是整个堆内存,它采用 “ 化整为零 ” 的思路,把整个堆内存划分为多个大小相等的独立区域(Region),在 G1 收集器中还保留着新生代和老年代的概念,它们分别都是一部分 Region,如下图:

每一个方块就是一个区域,每个区域可能是 Eden、Survivor、老年代,每种区域的数量也不一定。JVM 启动时会自动设置每个区域的大小(1M ~ 32M,必须是 2 的次幂),最多可以设置 2048 个区域(即支持的最大堆内存为 32M*2048 = 64G),假如设置 -Xmx8g -Xms8g,则每个区域大小为 8g/2048=4M。
为了在 GC Roots Tracing 的时候避免扫描全堆,在每个 Region 中,都有一个 Remembered Set 来实时记录该区域内的引用类型数据与其他区域数据的引用关系(在前面的几款分代收集中,新生代、老年代中也有一个 Remembered Set 来实时记录与其他区域的引用关系),在标记时直接参考这些引用关系就可以知道这些对象是否应该被清除,而不用扫描全堆的数据。
G1 收集器可以 “ 建立可预测的停顿时间模型 ”,它维护了一个列表用于记录每个 Region 回收的价值大小(回收后获得的空间大小以及回收所需时间的经验值),这样可以保证 G1 收集器在有限的时间内可以获得最大的回收效率。
如下图所示,G1 收集器收集器收集过程有初始标记、并发标记、最终标记、筛选回收,和 CMS 收集器前几步的收集过程很相似:

第一个:ZGC 使用了着色指针技术,我们知道 64 位平台上,一个指针的可用位是 64 位,ZGC 限制最大支持 4TB 的堆,这样寻址只需要使用 42 位,那么剩下 22 位就可以用来保存额外的信息,着色指针技术就是利用指针的额外信息位,在指针上对对象做着色标记。 第二个:使用读屏障,ZGC 使用读屏障来解决 GC 线程和应用线程可能并发修改对象状态的问题,而不是简单粗暴的通过 STW 来进行全局的锁定。使用读屏障只会在单个对象的处理上有概率被减速。 第三个:由于读屏障的作用,进行垃圾回收的大部分时候都是不需要 STW 的,因此 ZGC 的大部分时间都是并发处理,也就是 ZGC 的第三个特点。 第四个:基于 Region,这与 G1 算法一样,不过虽然也分了 Region,但是并没有进行分代。ZGC 的 Region 不像 G1 那样是固定大小,而是动态地决定 Region 的大小,Region 可以动态创建和销毁。这样可以更好的对大对象进行分配管理。 第五个:压缩整理。CMS 算法清理对象时原地回收,会存在内存碎片问题。ZGC 和 G1 一样,也会在回收后对 Region 中的对象进行移动合并,解决了碎片问题。
① 初始标记:标记出 GC Roots 直接关联的对象,这个阶段速度较快,需要停止用户线程,单线程执行。
② 并发标记:从 GC Root 开始对堆中的对象进行可达新分析,找出存活对象,这个阶段耗时较长,但可以和用户线程并发执行。
③ 最终标记:修正在并发标记阶段引用户程序执行而产生变动的标记记录。
④ 筛选回收:筛选回收阶段会对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来指定回收计划(用最少的时间来回收包含垃圾最多的区域,这就是 Garbage First 的由来——第一时间清理垃圾最多的区块),这里为了提高回收效率,并没有采用和用户线程并发执行的方式,而是停顿用户线程。
适用场景:要求尽可能可控 GC 停顿时间;内存占用较大的应用。可以用 -XX:+UseG1GC 使用 G1 收集器,jdk9 默认使用
ZGC收集器
ZGC有什么特点?
ZGC 是最新的 JDK1.11 版本中提供的高效垃圾回收算法,ZGC 针对大堆内存设计可以支持 TB 级别的堆,ZGC 非常高效,能够做到 10ms 以下的回收停顿时间。
这么快的响应,ZGC 是如何做到的呢?这是由于 ZGC 具有以下特点。
虽然 ZGC 的大部分时间是并发进行的,但是还会有短暂的停顿。来看一下 ZGC 的回收过程。
ZGC 是如何进行垃圾收集的?
ZGC(Z Garbage Collector)是一款由Oracle公司研发的,以低延迟为首要目标的一款垃圾收集器。它是基于动态Region内存布局,(暂时)不设年龄分代,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的收集器。
初始状态时,整个堆空间被划分为大小不等的许多 Region,即图中绿色的方块。

开始进行回收时,ZGC 首先会进行一个短暂的 STW(Stop The world),来进行 roots 标记。这个步骤非常短,因为 roots 的总数通常比较小。
然后就开始进行并发标记,如上图所示,通过对对象指针进行着色来进行标记,结合读屏障解决单个对象的并发问题。其实,这个阶段在最后还是会有一个非常短的 STW 停顿,用来处理一些边缘情况,这个阶段绝大部分时间是并发进行的,所以没有明显标出这个停顿。
下一个是清理阶段,这个阶段会把标记为不在使用的对象进行回收,如上图所示,把橘色的不在使用的对象进行了回收。
最后一个阶段是重定位,重定位就是对 GC 后存活的对象进行移动,来释放大块的内存空间,解决碎片问题。
重定位最开始会有一个短暂的 STW,用来重定位集合中的 root 对象。暂停时间取决于 root 的数量、重定位集与对象的总活动集的比率。
最后是并发重定位,这个过程也是通过读屏障,与应用线程并发进行的。
性能调优
熟悉哪些JVM调优参数
X或者XX开头的都是非转标准化参数:

意思就是说准表化参数不会变,非标准化参数可能在每个JDK版本中有所变化,但是就目前来看X开头的非标准化的参数改变的也是非常少。
格式:-XX:[+-]<name> 表示启用或者禁用name属性。
例子:-XX:+UseG1GC(表示启用G1垃圾收集器)
堆设置
-Xms 初始堆大小,ms是memory start的简称 ,等价于-XX:InitialHeapSize
-Xmx 最大堆大小,mx是memory max的简称 ,等价于参数-XX:MaxHeapSize
注意:在通常情况下,服务器项目在运行过程中,堆空间会不断的收缩与扩张,势必会造成不必要的系统压力。所以在生产环境中,JVM的Xms和Xmx要设置成一样的,能够避免GC在调整堆大小带来的不必要的压力。
-XX:NewSize=n 设置年轻代大小
-XX:NewRatio=n 设置年轻代和年老代的比值。如:-XX:NewRatio=3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4,默认新生代和老年代的比例=1:2。
-XX:SurvivorRatio=n 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个,默认是8,表示
Eden:S0:S1=8:1:1
如:-XX:SurvivorRatio=3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5。
-XX:MaxPermSize=n 设置持久代大小
-XX:MetaspaceSize 设置元空间大小
收集器设置
-XX:+UseSerialGC 设置串行收集器
-XX:+UseParallelGC 设置并行收集器
-XX:+UseParalledlOldGC 设置并行年老代收集器
-XX:+UseConcMarkSweepGC 设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filenameGC日志输出到文件里filename,比如:-Xloggc:/gc.log
并行收集器设置
-XX:ParallelGCThreads=n 设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n 设置并行收集最大暂停时间
-XX:GCTimeRatio=n 设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
-XX:MaxGCPauseMillis=n设置并行收集最大暂停时间
并发收集器设置
-XX:+CMSIncrementalMode 设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n 设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
其他
-XX:+PrintCommandLineFlags查看当前JVM设置过的相关参数

-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath
Dump异常快照
堆内存出现OOM的概率是所有内存耗尽异常中最高的,出错时的堆内信息对解决问题非常有帮助,所以给JVM设置这个参数(-XX:+HeapDumpOnOutOfMemoryError),让JVM遇到OOM异常时能输出堆内信息,并通过(-XX:+HeapDumpPath)参数设置堆内存溢出快照输出的文件地址,这对于特别是对相隔数月才出现的OOM异常尤为重要。
-Xms10M -Xmx10M -Xmn2M -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=D:\study\log_hprof\gc.hprof
-XX:OnOutOfMemoryError
表示发生OOM后,运行jconsole.exe程序。这里可以不用加“”,因为jconsole.exe路径Program Files含有空格。利用这个参数,我们可以在系统OOM后,自定义一个脚本,可以用来发送邮件告警信息,可以用来重启系统等等。
-XX:OnOutOfMemoryError="C:\Program Files\Java\jdk1.8.0_151\bin\jconsole.exe"
内存占用:程序正常运行需要的内存大小。 延迟:由于垃圾收集而引起的程序停顿时间。 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。
JVM 调优常见目标
JVM 调优目标:使用较小的内存占用来获得较高的吞吐量或者较低的延迟。
程序在上线前的测试或运行中有时会出现一些大大小小的 JVM 问题,比如 cpu load 过高、请求延迟、tps 降低等,甚至出现内存泄漏(每次垃圾收集使用的时间越来越长,垃圾收集频率越来越高,每次垃圾收集清理掉的垃圾数据越来越少)、内存溢出导致系统崩溃,因此需要对 JVM 进行调优,使得程序在正常运行的前提下,获得更高的用户体验和运行效率。
这里有几个比较重要的指标:
当然,和 CAP 原则一样,同时满足一个程序内存占用小、延迟低、高吞吐量是不可能的,程序的目标不同,调优时所考虑的方向也不同,在调优之前,必须要结合实际场景,有明确的的优化目标,找到性能瓶颈,对瓶颈有针对性的优化,最后进行测试,通过各种监控工具确认调优后的结果是否符合目标。
有哪些调优工具?
JPS
用 jps(JVM process Status)可以查看虚拟机启动的所有进程、执行主类的全名、JVM启动参数,比如当执行了 JPSTest 类中的 main 方法后(main 方法持续执行),执行 jps -l可看到下面的JPSTest类的 pid 为 31354,加上 -v 参数还可以看到JVM启动参数。
jstat
用 jstat(JVM Statistics Monitoring Tool)监视虚拟机信息 jstat -gc pid 500 10:每 500 毫秒打印一次 Java 堆状况(各个区的容量、使用容量、gc 时间等信息),打印 10 次。jstat 还可以以其他角度监视各区内存大小、监视类装载信息等,具体可以 google jstat 的详细用法。
jmap
用 jmap(Memory Map for Java)查看堆内存信息 执行 jmap -histo pid 可以打印出当前堆中所有每个类的实例数量和内存占用,如下,class name 是每个类的类名([B 是 byte 类型,[C是 char 类型,[I 是 int 类型),bytes 是这个类的所有示例占用内存大小,instances 是这个类的实例数量。
执行 jmap -dump 可以转储堆内存快照到指定文件,比如执行:
jmap -dump:format=b,file=/data/jvm/dumpfile_jmap.hprof 3361
可以把当前堆内存的快照转储到 dumpfile_jmap.hprof 文件中,然后可以对内存快照进行分析。
jconsole、jvisualvm
利用 jconsole、jvisualvm 分析内存信息(各个区如 Eden、Survivor、Old 等内存变化情况),如果查看的是远程服务器的 JVM,程序启动需要加上如下参数:
"-Dcom.sun.management.jmxremote=true"
"-Djava.rmi.server.hostname=12.34.56.78"
"-Dcom.sun.management.jmxremote.port=18181"
"-Dcom.sun.management.jmxremote.authenticate=false"
"-Dcom.sun.management.jmxremote.ssl=false"
下图是 jconsole 界面,概览选项可以观测堆内存使用量、线程数、类加载数和 CPU 占用率;内存选项可以查看堆中各个区域的内存使用量和左下角的详细描述(内存大小、GC 情况等);线程选项可以查看当前 JVM 加载的线程,查看每个线程的堆栈信息,还可以检测死锁;VM 概要描述了虚拟机的各种详细参数。
第三方工具
MAT、GChisto、GCViewer、JProfiler、arthas、async-profile。
JVM 调优经验总结
JVM 配置方面,一般情况可以先用默认配置(基本的一些初始参数可以保证一般的应用跑的比较稳定了),在测试中根据系统运行状况(会话并发情况、会话时间等),结合 gc 日志、内存监控、使用的垃圾收集器等进行合理的调整,当老年代内存过小时可能引起频繁 Full GC,当内存过大时 Full GC 时间会特别长。
那么 JVM 的配置比如新生代、老年代应该配置多大最合适呢?答案是不一定,调优就是找答案的过程,物理内存一定的情况下,新生代设置越大,老年代就越小,Full GC 频率就越高,但 Full GC 时间越短;相反新生代设置越小,老年代就越大,Full GC 频率就越低,但每次 Full GC 消耗的时间越大。
建议如下:
-Xms 和 -Xmx 的值设置成相等,堆大小默认为 -Xms 指定的大小,默认空闲堆内存小于 40% 时,JVM 会扩大堆到 -Xmx 指定的大小;空闲堆内存大于 70% 时,JVM 会减小堆到 -Xms 指定的大小。如果在 Full GC 后满足不了内存需求会动态调整,这个阶段比较耗费资源。
新生代尽量设置大一些,让对象在新生代多存活一段时间,每次 Minor GC 都要尽可能多的收集垃圾对象,防止或延迟对象进入老年代的机会,以减少应用程序发生 Full GC 的频率。 老年代如果使用 CMS 收集器,新生代可以不用太大,因为 CMS 的并行收集速度也很快,收集过程比较耗时的并发标记和并发清除阶段都可以与用户线程并发执行。 方法区大小的设置,1.6 之前的需要考虑系统运行时动态增加的常量、静态变量等,1.7 只要差不多能装下启动时和后期动态加载的类信息就行。
代码实现方面,性能出现问题比如程序等待、内存泄漏除了 JVM 配置可能存在问题,代码实现上也有很大关系:
避免创建过大的对象及数组:过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代,如果是短命的大对象,会提前出发 Full GC。 避免同时加载大量数据,如一次从数据库中取出大量数据,或者一次从 Excel 中读取大量记录,可以分批读取,用完尽快清空引用。 当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代。 可以在合适的场景(如实现缓存)采用软引用、弱引用,比如用软引用来为 ObjectA 分配实例:SoftReferenceobjectA=new SoftReference(); 在发生内存溢出前,会将 objectA 列入回收范围进行二次回收,如果这次回收还没有足够内存,才会抛出内存溢出的异常。
避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满。
尽量避免长时间等待外部资源(数据库、网络、设备资源等)的情况,缩小对象的生命周期,避免进入老年代,如果不能及时返回结果可以适当采用异步处理的方式等。
总结
本文从认识JDK、JRE、JVM,到编译,类加载,初始化,垃圾回收,性能调优。可以算的是把JVM的整个流程给过了一遍。希望对你有所帮助。