本文共 4130 字,大约阅读时间需要 13 分钟。
Java内存模型和多线程JMM相关
Java内存结构和JVM虚拟机存储空间相关
方法区
什么是方法区:也称永久区,static关键词修饰、常量信息;当class文件被加载的时候,就会被初始化;
调优问题:
堆(参数调优重点关注)
什么是堆:new创建的对象、数组等都会存放在堆中,所有线程会被共享;堆内存中分配了两个区:新生代和老年代(分代的目的是为了垃圾回收机制)
新生代
刚创建的对象,先存放在新生代中,分为eden、s0、s1区
老年代
如果对象在频繁的使用,则放在老年代
堆的参数配置
-XX :+PrintGC 每次触发GC的时候打印相关日志
-XX:+UserSerialGC 串行回收
-XX:+PrintGCDetails 更详细的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值尽量减少垃圾回收次数
-Xmn 新生代堆最大可用值
-XX:SurvivorRatio 用来设置新生代中eden空间和from/to空间的比例为n/1
总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率
-XX:NewRatio=2 总结:不同的分布情况,对系统执行会产生一定的影响,在实际工作中,应根据系统的特点做出合理的配置,基本策略:尽可能将对象留在新生代,减少老年代的GC次数。除了可以设置新生代的绝对大小(-Xmn),可以使用(-XX:NewRatio)设置新生代和老年代的比例:-XX:NewRatio=老年代/新生代
栈
什么是栈:类的方法;局部变量,代码运行完毕自动释放,每个线程私有,互不共享,不会产生线程安全问题
本地方法栈
作用:主要调用c语言,android中会用到;
错误原因
java.lang.OutOfMemoryError : Java heap space 堆内存溢出
解决办法
设置堆内存大小 -Xms1m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
Tomcat 内存溢出在catalina.sh修改JVM堆内存大小
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m"
错误原因
java.lang.StackOverflowError 栈内存溢出,栈溢出产生于递归调用,循环遍历是不会的,但是循环方法里面产生的递归调用,也会发生栈溢出,
解决办法
设置线程最大调用深度
-Xss5m 设置最大调用深度
在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到的一些目标:
前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够小,必须保证一个更大的堆,我们只能取其平衡
垃圾回收机制不定时,向堆内存清理不可达对象。其简要过程如下:
不可达对象并不会马上被直接回收,而是至少要经过两次标记的过程。第一次被标记的对象,会检查该对象是否重写了finalize()方法。如果重写了该方法,则将其放入一个F-query队列中,否则,直接将对象加入“即将回收”集合。在第二次标记之前,F-Query队列中的所有对象会逐个执行finalize()方法,但是不保证该队列中所有对象的finalize()方法都能被 执行,这是因为JVM创建一个低优先级的线程去运行此队列中的方法,很可能在没有遍历完之前,就已经被剥夺了运行的权利。那么运行finalize()方法的意义何在呢?这是对象避免自己被清理的最后手段;如果在执行finalize()方法过程中,使得此对象重新与GC Roots引用链相连,则会在第二次标记过程中将此对象从F-Query队列中清除,避免在这次回收中被清除,恢复成一个“正常”的对象。但显然这种好事不能无限的发生,对于曾经执行过一次finalize()的对象来说,之后如果再被标记,则不会再执行finalize()方法,只能等待被清除的命运。
之后,GC将对F-Queue中的对象进行第二次小规模的标记,将队列中重新与GC Roots引用链恢复连接的对象清除出“即将回收”集合。所有此集合中的内容将被回收。
内存溢出
需要4g,只有3g
内存泄露
对象已经没有被应用程序使用,但是垃圾回收器没办法移除他们,因为还在被引用着。
标记清除法–碎片化(一般不用)
标记清除算法为每个对象做一个标记,0可达,1不可达,对象没有经常使用,将对象标记为1不可达,容易造成碎片化
标记压缩–老生代
先进行排序,再进行删除,不会产生碎片
引用计数法–新生代
每个对象都会有一个标记,默认是15,gc回收的时候,对象不可达则减一,可达则加一,直到机会为0进行回收,如果超过15,则进入s0区或者s1区;
优点:引用计数收集器可以很快的执行,交织在程序运行中,对程序需要不被长时间打断的实时环境比较有利。
缺点:无法检测出循环引用,如父对象有一个对子对象的应用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0,而且每次加非常浪费内存。
复制算法–新生代(老年代没有)
只有一个区域可以存活,只需要移动堆顶指针,按顺序分配内存即可
优点:连续性,不会产生碎片化,运行高效
缺点:课使用内存降为原来一半
分代算法–新生代和老年代
垃圾回收的时候会出现停顿现象,垃圾回收的任务是识别和回收垃圾对象进行内存清理,为了让垃圾回收器可以进行更高效的执行,大部分情况下,会要求系统进入一个停顿的状态。停顿的目的是为了终止所有的应用线程,只有这样的系统才不会有新垃圾的产生。同时停顿保证了系统状态在某一瞬间的一致性,也有利于更好的标记垃圾对象。因此在垃圾回收时,都会产生应用程序的停顿
单线程执行回收操作,回收期间暂停所有应用线程的执行,client模式下的默认回收器,通过-XX:+UseSerialGC命令行可选项强制执行
并行回收器在串行回收器的基础上做了改进,可以使用多个线程同时进行垃圾回收,对于计算能力强的计算机而言,可以有效缩短垃圾会后所需要的间际时间
CMS回收器(Concueernt Mark Sweep Collector)是回收停顿时间比较短、目前比较常用的垃圾回收器。它通过
四个步骤完成垃圾回收,第一和第三步还是会造成STW(Stop The World)即垃圾回收的某个阶段会暂停整个应用程序的执行,而第2、4步的并发标记和并发清除两个阶段可以和应用程序并发执行,也是比较耗时的操作,但并不影响整个程序的正常执行。
由于CMS采用的是标记清除算法,因此会产生大量的空间碎片,为了解决这个这个问题,CMS可以通过配置**-XX:+UserCMSCompactAtFullCollection参数,强制JVM在FGC完成后对老年代进行压缩,执行一次碎片化整理,但是空间碎片整理阶段也会引发STW。为了减少STW次数,CMS还可以通过配置-XX:CMSFullGCsBeforeCompaction=n**参数,在执行n次FGC后,JVM再在老年代执行空间碎片整理。
Hotspot在JDK7中推出了新一代G1垃圾回收,通过-XX:+UseG1GC参数启用。和CMS相比,G1具有压缩功能,能避免碎片化问题,G1的暂停时间更加可控
G1将Java堆空间分割为若干相同大小的区域,即region,包括Eden(年轻代)、Survivor(年轻代)、Old(老年嗲)、Humongous(巨型对象区域)四种类型。其中,Humongous是特殊的Old类型,专门放置大型对象。这样的划分方式意味着不需要一个连续的存储空间管理对象。G1将空间分为多个区域,优先回收垃圾最多的区域。G1采用的是“Mark-Copy(标记整理)”,有非常好的空间整合能力,不会产生大量的空间碎片。G1的一大优势在于可预测的停顿时间,能够尽可能快的在指定的时间内完成垃圾回收任务。在JDK11中,已经将G1设为默认垃圾回收器,通过jstat命令可以查看垃圾回收情况。
G1的整理标记和CMS一样,分为5个步骤
转载地址:http://cgcqb.baihongyu.com/