调整JVM堆内存
在确定JVM堆内存大小时,需要考虑以下因素:
- 应用程序的内存需求。
- 操作系统和其他应用程序所需的内存。
- JVM的运行参数和GC算法。
根据通常的经验,可以将JVM最大堆内存设置为操作系统可用内存的约70%。也就是说,在16GB的机器上,建议将JVM堆内存设置为11GB左右。这样做可以确保一定的空闲内存供操作系统和其他应用程序使用,并且给JVM留出足够的空间来处理应用程序的内存需求。
当然,实际的情况可能因应用程序的特殊性质而异。如果应用程序需要更多的内存,那么可以相应地增加JVM堆内存大小。但是,要注意不要设置过大,否则可能会导致频繁的Full GC和长时间的停顿。
jvm的运行参数有哪些,如何调整
JVM的运行参数根据作用可以分为以下几类:
-
堆内存相关参数:包括-Xmx、-Xms、-XX:NewRatio、-XX:SurvivorRatio等,用于设置JVM堆内存大小以及新生代和老年代之间的比例等。
-
GC相关参数:包括-XX:+UseG1GC、-XX:+UseConcMarkSweepGC、-XX:+UseParallelGC等,用于选择垃圾回收算法以及调整垃圾回收的行为。
-
线程相关参数:包括-XX:ParallelThreads、-XX:ConcGCThreads等,用于设置并发线程数以及并发GC的线程数。
-
类加载相关参数:包括-XX:PermSize、-XX:MaxPermSize、-XX:MetaspaceSize、-XX:MaxMetaspaceSize等,用于设置永久代或元空间的大小。
可以使用命令行方式或者配置文件方式来设置JVM运行参数。常用的命令行参数有:
- -Xmx:设置JVM最大堆内存大小。
- -Xms:设置JVM初始堆内存大小。
- -XX:PermSize:设置永久代大小。
- -XX:MaxPermSize:设置最大永久代大小。
- -XX:+UseG1GC:启用G1垃圾回收器。
- -XX:+PrintGCDetails:输出详细的GC信息。
- -XX:ThreadStackSize:设置线程栈大小。
命令行:
java -Xmx4g -Xms2g -jar myapp.jar
对于较为复杂的应用程序,可以将JVM运行参数写入配置文件中,例如在Tomcat服务器中,可以在catalina.sh或catalina.bat脚本中添加以下内容:
JAVA_OPTS="-server -Xms2g -Xmx4g -XX:PermSize=256m -XX:MaxPermSize=512m"
export JAVA_OPTS
什么时候需要设置线程栈的大小
Java虚拟机中的每个线程都有一个私有的线程栈,用于存储本地方法栈、操作数栈、局部变量表等信息。默认情况下,每个线程栈的大小为1MB,可以通过设置命令行参数或者JVM选项来调整。
通常需要调整线程栈的大小是在以下场景:
-
栈溢出:如果线程使用的栈空间不足,就会抛出StackOverflowError异常。这通常发生在递归调用或者方法调用深度较大的情况下。可以通过增加线程栈的大小来避免栈溢出的问题。
-
内存占用:线程栈的大小也会影响应用程序的内存占用。如果创建了大量的线程,那么默认的线程栈大小可能会导致过多的内存占用。可以通过减小线程栈的大小来降低内存占用。
-
系统资源:线程栈的大小还会影响系统资源的使用。如果创建了过多的线程,并且每个线程都使用较大的栈空间,这可能会导致系统资源的浪费和效率下降。可以通过调整线程栈的大小来优化系统资源的使用。
需要注意的是,调整线程栈的大小可能会影响应用程序的性能和稳定性。如果线程栈的大小设置过小,可能会导致栈溢出和应用程序崩溃;如果线程栈的大小设置过大,可能会消耗过多的内存和系统资源。因此,在调整线程栈大小时需要综合考虑应用程序的需求和系统资源的情况,并进行测试和优化。
G1垃圾回收器限制
- 物理内存限制:G1垃圾回收器需要足够的物理内存才能发挥最佳性能。如果可用内存不足,则可能导致频繁的Full GC和长时间的停顿。
例如,在一个运行大型应用程序的服务器上,如果分配给Java进程的内存较小,那么G1垃圾回收器可能无法高效地工作,因为它需要足够的物理内存来执行标记、整理和压缩等操作。
- 启动时间延迟:G1垃圾回收器需要在启动时建立全局的Heap区域,这会导致较长的启动时间延迟。
例如,在一个需要快速启动的应用程序中,G1垃圾回收器可能不是最佳选择,因为它需要较长的启动时间来建立全局的Heap区域。
- 堆大小限制:G1垃圾回收器对堆内存的大小有一定的限制。通常建议将堆内存设置在4GB到32GB之间。如果堆内存过小,则可能导致频繁的Full GC;如果堆内存过大,则可能导致G1垃圾回收器无法高效地工作。
例如,在一个需要处理大规模数据集的应用程序中,如果堆内存设置过小,那么G1垃圾回收器可能无法有效地回收垃圾,导致频繁的Full GC和较长的暂停时间。
- 执行时间限制:G1垃圾回收器采用分代垃圾回收算法,因此在执行时会受到年轻代和老年代的大小限制。如果年轻代和老年代的大小比例不合适,则可能导致G1垃圾回收器效率下降。
例如,在一个需要处理大量短期请求的Web服务器上,如果设置了较小的年轻代空间,那么可能会导致G1垃圾回收器无法有效地回收短期对象,从而影响应用程序的性能。
- CPU资源限制:G1垃圾回收器需要占用一定的CPU资源来执行标记、整理和压缩等操作。如果CPU资源不足,则可能导致G1垃圾回收器无法高效地工作。
例如,在一个CPU资源有限的环境中,如果多个Java进程同时运行,并且使用了G1垃圾回收器,那么可能会导致CPU资源竞争和效率下降。
需要根据具体的应用情况来选择合适的垃圾回收算法以及调整相关的运行参数,以达到最佳的性能和可靠性。
为什么内存过小,会导致G1频繁的Full GC
在使用G1垃圾收集器时,内存过小可能会导致频繁的Full GC。G1垃圾收集器的主要任务是将堆空间划分为多个小块(region),并在每个小块中进行垃圾回收。Full GC是指在整个堆空间中进行垃圾回收的过程,而不仅仅是在某些小块中进行。
当内存过小时,G1垃圾收集器可能无法为对象保留足够的空间,因此需要更频繁地进行Full GC以便回收更多的空间。这会导致应用程序暂停,并且会降低应用程序的性能。
因此,在使用G1垃圾收集器时,请确保为其分配足够的内存,以避免频繁的Full GC。
g1和cms有什么区别
G1和CMS都是JVM中的垃圾回收器,用于回收不再使用的Java对象。它们之间的主要区别如下:
-
算法原理:G1采用的是基于区域的垃圾回收算法,而CMS则采用基于标记-清除的算法。
-
分代方式:G1将堆内存划分成多个大小相等的区域(Region),并根据每个区域的垃圾数量来决定回收顺序。CMS仅对老年代进行垃圾回收。
-
可预测性:G1垃圾回收器可以设置最大停顿时间,因此可以更好地控制垃圾回收期间的暂停时间。而CMS无法保证停顿时间。
-
内存占用:G1垃圾回收器可以动态调整堆内存大小,并且通常比CMS占用更少的内存。
-
GC效率:G1垃圾回收器可以在多个CPU核心上并行执行标记、整理和压缩等操作,因此通常比CMS具有更高的GC效率。
总体而言,G1适用于需要高吞吐量和可预测性的应用程序,而CMS适用于需要快速响应时间和较低的暂停时间的应用程序。但是需要注意的是,对于特定的应用程序来说,并没有一种垃圾回收器可以适用于所有情况。因此,开发人员需要根据具体的应用场景和性能需求选择最合适的垃圾回收器。
从下图可以看出来,G1会做stop the world来进行筛选回收,但这个时间长短可以指定
G1原理图
CMS原理图
jvm会在什么时候gc?什么时候full gc?
JVM(Java Virtual Machine)的垃圾回收机制是自动的,通常在以下情况下会进行垃圾回收:
-
空间规划阈值到达。JVM 在启动时会为 Java 堆分配一定大小的内存空间,当 Java 对象数量增加到一定程度时,就会触发 JVM 进行垃圾回收。
-
内存不足时。当 JVM 检测到 Java 堆没有足够的内存供应用程序使用时,就会启动垃圾回收机制。
-
调用 System.gc() 方法。虽然调用该方法并不能保证 JVM 立即执行垃圾回收,但它确实会增加 JVM 执行垃圾回收的可能性。
-
发生 OutOfMemoryError 错误。如果 JVM 检测到内存不足而且无法再分配更多内存时,就会抛出 OutOfMemoryError,此时 JVM 会启动垃圾回收机制。
Full GC 是一种特殊的垃圾回收机制,它会回收整个 Java 堆,包括 Eden 区、Survivor 区和老年代。Full GC 通常在以下情况下发生:
-
对象进入老年代时。当年轻代的 Eden 区、Survivor 区满了之后,对象会进入老年代,此时会触发 Full GC。
-
显式调用 System.gc() 方法。尽管显式调用 System.gc() 并不保证 JVM 立即执行 Full GC,但它确实会增加 JVM 执行 Full GC 的可能性。
需要注意的是,Full GC 是非常耗费资源的操作,因此建议在应用程序低峰期或者系统资源充足的情况下进行 Full GC。
类的加载过程:
1、当需要一个java类时,会通过classname,从classpath中查找到对应的class文件(加载过程通过双亲委派机制,直到顶层父加载器也找不到,就交由启动类加载器从磁盘里找到对应的class文件,并加载它),把class文件加载到方法区内,
2、class文件加载到内存后,jvm会对该类进行验证:检查正确性;准备:静态变量分配内存并设置默认值;解析:将符号引用变成直接引用(具体是指:常量池)
符号引用和直接引用
是常量池中的两种引用类型,符号是指具体描述符、直接引用是指通过符号可以直接定位到具体值
永久代、元空间、方法区:
永久代、元空间是实现方法区的一种方式,元空间再jdk8开始替换永久代,可以动态扩容
方法区主要存储类加载过程中的 类信息、常量、静态变量、静态方法。
static应用的对象,生命周期 等于 整个应用程序的生命周期
jvm堆内存
年轻代
老年代
如何判断对象是否可回收?
1、引用计数法,弊端 解决不了循环引用
2、可达性分析算法:通过虚拟机栈、本地方法区,方法去静态变量确定所有根节点,以这一组根节点作为起点,标记所有可达对象,没有被标记的就是可回收对象;
注意可达性分析算法,计算可达遇到循环依赖依旧是通过计数法来解决循环标记的
垃圾回收算法
标记--清除 : 会造成内存碎片
复制算法:只利用了一般的内存空间,浪费资源
标记-整理:相对复制算法,比较耗时
在上面的基础上,诞生了分代回收法,根据对象的生命周期和特性,将堆内存分为新生代和老年代
新生代采用复制算法:8:1:1 S0 + 2个S1;老年代可以使用清楚或整理
有哪些垃圾回收器:
Serial收集器:单线程,stw、新生代:S0+2个S1/ 老年代也支持
Parallel收集器:stop the world:新生代老年代
CMS收集器: 并发,stop the world时间短。老年代:标记清除、内存碎片
G1收集器:分块,没有年轻代和年老代之分,stw可配置,对内存要求高
jvm本地方法区:java的本地方法
jvm栈:虚拟机栈
栈帧是一种数据结构,即也就是队列,在虚拟机栈中
jvm为新线程创建虚拟机栈,执行方法时,jvm会会这个方法生成一个栈帧,将其推入前一个栈顶,开始执行这个方法的栈帧。jvm开始逐行执行这个方法,
执行完毕后,jvm弹出这个栈帧,返回到上一个栈帧继续执行,返回的值压入上一层的操作数栈中,当栈帧弹出后,虚拟机栈顶指向下一个栈帧,但线程执行完毕后,jvm会回收这个线程,并回收所创建的空间。
程序计数器
cpu有程序计数器,记录这上下指令的位置,记录这下一条执行的位置。
jvm线程也有程序计数器,
当一个方法被调用时,JVM 会将当前线程的程序计数器指向该方法的第一条字节码指令,并将该方法的栈帧推入虚拟机栈中。随着方法的执行,程序计数器会自动增加,指向下一条字节码指令,同时栈帧中的局部变量和操作数栈也会随之改变。当方法执行完毕后,该方法的栈帧从虚拟机栈中弹出,并且程序计数器会回到原来的值,继续执行调用该方法的方法。
就是说 jvm创建线程后,为其创建虚拟机栈和程序计数器,程序计数器记录着当前指令字节码的位置,并将方法压入栈顶,开始执行,执行过程中 程序计数器会不停的增加。
结合以上:new一个对象的过程
1、去方法区找class对象,如果没有双亲委派机制加载(从磁盘加载class文件字节流,load到方法区,开始 验证-准备(初始化静态)-解析(常量池符号引用-》直接引用:会加载所需依赖class))
2、找到之后,在堆中分配空间内存,创建对象
3、很大直接到老年代,不大的话就到S0,之后到S1,S2之后到到年老代
结合以上:方法是如何调用的
jvm创建线程后,为其创建虚拟机栈和程序计数器,程序计数器记录着当前指令字节码的位置,并将方法压入栈顶,开始执行,执行过程中 程序计数器会不停的增加。
JVM调优经验:
1、发现问题:访问很慢,经常堆溢出、栈溢出
2、堆溢出 需要考虑是否是大对象、内存空间分配不合理,对象产生较快
3、栈溢出 需要考虑是否死循环,导致调用递归过深,超过了jvm允许的最大深度,局部变量过多,超过了限制(1M)
4、借助一些监控工具,分析问题,适当调整相关内存大小,选择使用合适的回收器