程序计数器
保留jvm线程切换时,字节码行号数据。
因为Java的多线程也是依靠时间片轮转算法进行的,因此一个CPU同一时间也只会处理一个线程,当某个线程的时间片消耗完成后,会自动切换到下一个线程继续执行,而当前线程的执行位置会被保存到当前线程的程序计数器中,当下次轮转到此线程时,又继续根据之前的执行位置继续向下执行。
虚拟机栈
局部变量表
在class文件中就已经定义好
操作数栈
字节码执行时使用到的栈结构
动态链接
栈帧中保存了一个可以指向当前方法所在类的运行时常量池,目的是:当前方法中如果需要调用其他方法的时候,能够从运行时常量池中找到对应的符号引用,然后将符号引用转换为直接引用,然后就能直接调用对应方法。
方法出口
方法该如何结束,是抛出异常还是正常返回。
本地方法栈
本地方法栈与虚拟机栈作用差不多,但是它 备的,这里不多做介绍。
堆
堆是整个Java应用程序共享的区域,也是整个虚拟机最大的一块内存空间,而此区域的职责就是存放和管理对象和数组,而我们马上要提到的垃圾回收机制也是主要作用于这一部分内存区域
方法区
方法区也是整个Java应用程序共享的区域,它用于存储所有的类信息、常量、静态变量、动态编译缓存等数据,可以大致分为两个部分,一个是类信息表,一个是运行时常量池。方法区也是我们要重点介绍的部分。
OOM
堆溢出
堆内存溢出是一种 Java 虚拟机(JVM)抛出的错误,表示堆内存空间不足以分配新的对象。堆内存是用于存放 Java 对象的一块内存区域,它的大小可以通过 -Xmx 参数来指定。
导致堆内存溢出的原因可能有以下几种:
- 堆内存设置太小,无法满足应用程序的需求。
- 应用程序存在内存泄漏,导致大量的无用对象占用内存,无法被垃圾回收(GC)回收。
- 应用程序创建了过多的大对象,导致堆内存空间不连续,无法找到足够大的空闲空间来分配新的对象。
解决堆内存溢出的方法可能有以下几种:
- 增加堆内存的大小,使用 -Xmx 参数指定最大堆内存。
- 使用内存分析工具,如 Eclipse MAT,找出内存泄漏的原因并修复。
- 优化代码,减少大对象的创建,或者使用对象池,避免不必要的内存分配。
- 使用不同的 GC 算法,如 G1GC,调整 GC 的参数,如 -XX:MaxGCPauseMillis,以改善 GC 的性能。
元空间溢出
元空间内存溢出是一种 Java 虚拟机(JVM)抛出的错误,表示元空间(Metaspace)的大小超过了 JVM 的限制。元空间是 JDK8 后替代永久代(PermGen)的一种内存区域,用于存放类的元数据、常量池、静态变量等。
导致元空间内存溢出的原因可能有以下几种:
- 元空间的大小设置过小,无法满足类的加载需求。
- 代码中存在大量的反射操作,导致元空间中生成了大量的代理类。
- 应用程序长时间运行,没有重启,导致元空间中的类无法卸载。
解决元空间内存溢出的方法可能有以下几种:
- 增加元空间的大小,使用 -XX:MaxMetaspaceSize 参数指定最大元空间大小。
- 优化代码,减少反射操作,或者使用 Cglib 等框架提供的缓存机制,避免重复生成代理类。
- 定期重启应用程序,释放元空间中的类。
- 使用内存分析工具,如 Eclipse MAT,找出元空间中占用内存最多的类,检查是否存在内存泄漏或者类加载异常的问题。
Tips
为什么代理类会占用元空间 ?
代理类会占用元空间的原因是因为元空间是用于存放类的元数据信息的一种内存区域,包括类名、属性、方法、常量池等。每当一个代理类被生成时,它的元数据信息就会被加载到元空间中,占用一定的内存空间。如果生成了大量的代理类,那么元空间的占用就会增加,可能导致元空间内存溢出的错误
GC overhead limit exceeded
是一种 Java 虚拟机(JVM)抛出的错误,表示应用程序在垃圾回收(GC)上花费了太多时间,但回收的内存却很。这是一种保护机制,用于避免应用程序陷入频繁的 GC 但无法释放足够内存的恶性循环。
导致这个错误的原因可能有以下几种:
- 堆内存设置太小,无法满足应用程序的需求。
- 应用程序存在内存泄漏,导致大量的无用对象占用内存,无法被 GC 回收。
- 应用程序创建了过多的临时对象,导致 GC 频繁触发,但回收效果不佳。
解决这个错误的方法可能有以下几种:
- 增加堆内存的大小,使用 -Xmx 参数指定最大堆内存。
- 使用内存分析工具,如 Eclipse MAT,找出内存泄漏的原因并修复。
- 优化代码,减少临时对象的创建,或者重用对象,避免不必要的内存分配。
- 使用不同的 GC 算法,如 G1GC,调整 GC 的参数,如 -XX:MaxGCPauseMillis,以改善 GC 的性能。
回收器
服务器端的应用通常要求有高并发、高吞吐量、低停顿时间的性能,因此,适合选择一种能够并发执行、减少垃圾回收停顿时间的老年代回收器。目前,有两种老年代回收器可以满足这样的需求,分别是 CMS(Concurrent Mark-Sweep)和 G1(Garbage-First)。
CMS
是一种以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。它采用的是标记-清除算法,分为四个阶段:初始标记、并发标记、重新标记、并发清除。其中,只有初始标记和重新标记阶段需要暂停用户线程,而并发标记和并发清除阶段可以与用户线程同时进行。这样,就可以大大减少垃圾回收的停顿时间,提高服务器的响应速度。要使用 CMS 回收器,可以在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定。
G1
是一种面向服务端应用的垃圾回收器,它采用的是标记-整理算法,将堆内存划分为多个大小相等的区域,每个区域都可以作为新生代或者老年代。它的主要优点是可以预测垃圾回收的停顿时间,根据用户设定的停顿时间目标,优先回收垃圾最多的区域,从而实现高效的垃圾回收。要使用 G1 回收器,可以在启动 JVM 的参数加上“-XX:+UseG1GC”来指定。
G1 预测垃圾回收的停顿时间的原理是基于以下几个方面:
- G1 会记录每个区域的存活对象的数量和回收的时间,从而计算出每个区域的回收价值(回收的垃圾量除以回收的时间),并按照回收价值从高到低排序。
- G1 会根据用户设定的停顿时间目标(-XX:MaxGCPauseMillis),从回收价值最高的区域开始,依次选择要回收的区域,直到达到停顿时间目标或者没有更多的区域可以回收为止。
- G1 会根据历史数据和当前的堆状态,动态调整新生代的大小和回收的频率,以保证在满足停顿时间目标的同时,尽可能提高吞吐量和空间利用率。
知识点
逃逸分析
判断对象是否出了方法作用域在其他地方还有其他引用,开启逃逸分析之后符合条件的对象会在栈内创建对象,所以不受堆内存回收机制影响,而且速度很快。
6版本之后默认是开启的:-Xx:+DoEscapeAnalysis
结论:能用局部变量,就不要在外边创建。
- 栈上分配
- 同步消除
好比方法内部出现同步信号 - 标量替换
方法内部的变量使用对象传入传参,会优化成变量被直接赋值。
public class EscapeAnalysis {
public EscapeAnalysis e;
public EscapeAnalysis getInstance(){
return e == null? e = new EscapeAnalysis():e;
}
public void initObj(){
EscapeAnalysis e = getInstance();
}
public void refUse(EscapeAnalysis e){
//e
}
}
堆内存分配
- 为什么最大值和最小值一样?
为了避免当fullgc之后,重新计算对空间大小。
-
怎么估算堆内存大小?
Java Performance 推荐一般为fullgc的三倍到四倍,那么粗算就是old区存活对象的四倍,因为一般压测不一定触发fullgc,那么怎么来在压测的同时发生fullgc呢,使用jmap live 命令统计存活对象的信息就会触发一次fullgc,此时使用jmap heap 命令查看进程的old区存活对象内存占用,用此内存乘以4就是预估的堆内存大小。
-
新生代老年代比例
-XX:NewRatio=2 //默认新生代的大小约为整个堆内存的1/3,而老年代的大小约为整个堆内存的2/3。
-
伊甸区和存活区比例
-XX:SurvivorRatio=8 :伊甸园空间和幸存者空间的占比。 eden:s0:s1=8:1:1
-
默认垃圾回收器
在JDK 8中,默认的老年代垃圾回收器是Parallel Scavenge(并行Scavenge)。 -
开启其他垃圾回收器
Parallel Scavenge主要关注吞吐量,因此可能会导致较长的垃圾回收暂停时间,对响应时间要求较高的应用程序可能不太适合使用该回收器。如果需要更低的延迟,并且可以接受牺牲一些吞吐量,可以考虑使用其他的垃圾回收器,如CMS(Concurrent Mark Sweep)或G1(Garbage First)。- 开启G1垃圾回收器:
需要关注GC并发线程数对GC回收的影响,至少两个。 ConcGCThreads参数会被设置为处理器核心数的1/4。
-XX:+UseG1GC //不区新生代和老年代 -XX:ConcGCThreads:指定并发垃圾回收线程的数量。
- 开启CMS垃圾回收器:
-XX:+UseConcMarkSweepGC //会关闭useadaptivesizepolicy
- 开启G1垃圾回收器: