内存结构
其中栈内存是每个线程私有的,存放自己的局部变量,在线程创建时被创建的,堆内存存放new关键字的对象,会被jvm的垃圾回收机制管理,是共享的,本地方法栈是用于调用c或者c++的驱动代码的,方法区用于存放常量、被加载的类的信息、静态变量等,是共享的
垃圾回收机制
- 不可达对象:对象没有被引用或没有存活
- jvm垃圾回收机制:jvm会不定时自动回收不可达对象的内存
- finalize:Object类中方法,在垃圾回收时会被调用
堆内存
堆内存结构
判断对象是否存活(可达)的算法
- 引用计数法:当对象被创建时,默认对象为0岁,gc线程会不定期进行自动回收,在回收时(包括Minor GC和Major GC)如果当前对象被引用就年龄+1,没有被引用就-1,年龄大于15放在老年代,小于15放在新生代,当年龄小于0就会认为对象不可达,被清理。但因为有循环引用的问题,所以已经被弃用
- gc根搜索算法(可达性分析算法):当通过方法区、栈出发,可以找到当前在堆内存中的对象,则认为这个对象是可达的,否则认为是不可达的,gc根搜索可以解决循环引用问题
根据清理不可达对象的垃圾回收算法
- 标记清除算法:在老年代中使用,0标记为存活,1标记为没有存活,使用这种方法时在进行垃圾清理时会出现内存碎片化的问题,造成内存浪费
- 复制算法:在新生代中使用,当eden区对象存活,java会将其移到s0或s1中,当eden满了后,就执行垃圾清理,将所有可达的对象复制到另一个s区中,然后直接抹掉当前的s区中的数据,优点是可以解决内存碎片化问题,执行效率高,缺点是s0和s1始终只能有一个区存放数据,另一个是用于下次内存清理时复制可达对象时使用的
- 标记压缩算法:在老年代中使用,在标记清除算法之上,会整理碎片化内存空间,缺点是堆内存中原对象的引用地址会发生改变
Minor GC和Full GC
- 新生代GC即Minor GC,新生代中的对象不稳定,所以GC速度快,频率高
- 老年代GC即Major GC,老年代对象稳定,GC速度比新生代GC慢很多,频率也更低
- 老年代GC一般会伴随一次新生代GC,所以老年代GC又叫Full GC
- 新生代中的eden满时,触发新生代GC,survivor满不会触发GC
- 老年代满时,触发老年代GC,即Full GC
垃圾回收
可以手动设置的参数
- xms:初始化堆内存
- xmx:最大堆内存
- xmn:新生代最大内存
- 新生代与老年代的比例
- 新生代中eden区和s区的比例
- 堆内存大小首先按照xms的值进行初始化,当接近xms值时触发gc,当gc后内存还是不够就在xms基础上增加一点内存,直到增加的内存值达到xmx后,再增加就会抛出异常,在设置时,如果要想程序gc次数尽可能少,就可以使xms = xmx,程序初始内存就是最大内存,就能避免扩大内存时进行的gc
堆内存回收流程
当堆内存不够时,先触发新生代gc,新生代gc后还不够用触发full gc,full gc还不够用就抛异常
栈内存溢出
产生于方法的递归调用,当递归次数超过栈内存的最大深度就会发生栈内存溢出,xss参数可以用于指定栈内存深度
元空间和永久代
jdk8中用元空间取代了永久代,永久代之前由jvm进行分配,经常会出现内存溢出问题,而元空间改为直接使用操作系统的本地内存
内存溢出和内存泄漏的区别
- 内存溢出:指在运行程序前向jvm申请内存,但申请的内存超出了jvm能分配的最大内存,于是就出现内存溢出
- 内存泄漏:指程序原本在正常运行,但运行过程中没有去回收运行时产生的垃圾,产生的这种没有回收的垃圾就是内存泄漏,一次内存泄漏不会产生很大影响,但如果内存泄漏越来越多,内存空间就会被垃圾占完,最终导致正常运行的程序抛出异常
强引用和弱引用(针对栈空间中存放的变量名)
- 大部分引用都是强引用,当内存不足时,jvm会直接抛出异常也不会回收强引用所对应的堆内存对象的空间,若想要取消某个强引用与堆内存中对象的关联,直接将这个引用指向null,jvm就会在之后自动回收堆内存的这个对象
- 弱引用(WeakReference)在被jvm垃圾回收器扫描到时就会直接回收,无论当前是否需要gc,在ThreadLocalMap中的Key值就属于弱引用
ThreadLocal内存泄漏
ThreadLocal的实现原理就是为每个线程维护一个独立的ThreadLocalMap,而map中的Key就是弱引用的ThreadLocal实例,value为线程变量副本
ThreadLocalMap的生命周期和Thread一样,当Thread死亡后,其对应的ThreadLocalMap内存也应该被释放,在ThreadLocalMap中的Key值是一个弱引用,弱引用会在gc垃圾回收器扫描到时自动回收,所以我们在使用ThreadLocal时不用去手动清除ThreadLocalMap的Key所引用的内存,但ThreadLocalMap的value会通过强引用指向其他的变量,如果我们不手动清除ThreadLocalMap中map的引用,就会导致线程结束后,其value引用的空间不能被释放,发生ThreadLocal的内存泄漏,所以ThreadLocal的正确使用方法是在其销毁的时候调用remove方法,remove方法就是用来回收ThreadLocalMap中value部分的强引用所指向的空间的
垃圾回收器
jvm中由多种垃圾收集器
-
串行回收器:使用单线程进行回收,采用复制算法进行回收,垃圾回收过程中所有其他线程暂停
-
ParallelNew回收器:针对串行回收的改进,采用复制算法进行回收,只是将串行回收中的单回收线程改成多回收线程,在回收时也会暂停其他线程,但用时大大降低。jdk8中默认就是这种回收方法,一般新生代并行复制回收、老年代串行标记压缩回收
-
G1回收器:jdk9中默认就是这种回收方法,G1中不再将内存从物理上划分为年轻代和老年代,g1会在运行时动态调整新生代和老年代的区域大小,g1的内存结构如图
当出现了巨大的对象时,会将其放入Humongous区,当一个Humongous区还是存不下就触发gc,合并多个Humongous区,g1中针对年轻代的回收仍然会采用暂停应用线程的方式,通过将存活对象拷贝到survivor或老年代中进行年轻代区域的内存清理使用g1时通过3步完成调优
1. 开启G1
2. 设置堆的最大内存
3. 设置最大停顿时间
jvm参数调优
主要调整的参数为年轻代、老年代、元空间及使用的垃圾回收器
主要结论:
- 在jdk8之后,在服务器性能较好的情况下优先使用g1垃圾回收器
- 每天如果出现超过1次的full gc说明有问题,先看是否是由于内存泄漏导致的,没有内存泄漏再调整jvm参数
- 让xms=xmx以减少gc的次数
- 使用并行垃圾回收器,并行比串行的回收效率更高
- 使用g1时通过3步完成调优
1. 开启G1
2. 设置堆的最大内存
3. 设置最大停顿时间
Tomcat参数调优
普通ssm项目直接在tomcat的Catalina.dat文件中设置jvm的参数信息,调优原理同上
如果是针对springboot项目进行调优,可以通过在使用java -jar命令启动时,通过命令的方式配置jvm参数,同时可以在yml或properties文件中配置tomcat的连接池参数