一、Jvm参数配置
调优参数具体如下:
- Xms3072m - Xmx3072m - Xloggc 😕 var / logs / pousheng / gc -% t . log - XX :+ PrintGCDetails
- XX :+ PrintGCTimeStamps - XX :+ PrintGCCause - XX :+ PrintTenuringDistribution
- XX :+ UseGCLogFileRotation - XX : NumberOfGCLogFiles = 60 ****- XX : GCLogFileSize = 5 M
- XX :+ HeapDumpOnOutOfMemoryError - XX : HeapDumpPath =/ var / logs / pousheng / dump . hprof
常用配置参数
-XX:-OmitStackTraceInFastThrow | 禁用快抛,即展示完整异常 |
---|---|
-XX:+OmitStackTraceInFastThrow | 代表启用快抛,即会存在一些没有完整堆栈的异常 |
JVM参数配置 | |
---|---|
-Xss | 设置线程栈的大小 |
-Xms | 表示java虚拟机堆区内存初始内存分配的大小,物理内存的1/64 |
-Xmx | 表示java虚拟机堆区内存可被分配的最大上限,物理内存的1/4 |
-XX:newSize | 表示新生代初始内存的大小 |
-XX:MaxnewSize | 表示新生代可被分配的内存的最大上限 |
-Xmn | 至于这个参数则是对 -XX:newSize、-XX:MaxnewSize两个参数的同时 |
-XX:metaspaceSize | 元空间 |
-XX:MaxMetaspaceSize | 最大元空间 |
二、JVM内存模型
1、JVM内存模型图]![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/79a609cfb9efdf275f1960552cff86d2.png)
2、JVM 参数配置控制
Eden与survivor区默认8:1:1
注意:如果内存剩余不到40%,JVM就会增大堆到Xmx设置的值,内存剩余超过70%,JVM就会减小堆到Xms设置的值。所以服务器的Xmx和Xms设置一般应该设置相同避免每次GC后都要调整虚拟机堆的大小。
注意:其中java8 之后去掉了永久代
-XX:PermSize:表示非堆区初始内存分配大小,其缩写为permanent size(持久化内存)
-XX:MaxPermSize:表示对非堆区分配的内存的最大上限。这里面非常要注意的一点是:在配置之前一定要慎重的考虑一下自身软件所需要的非堆区内存大小,因为此处内存是不会被java垃圾回收机制进行处理的地方。并且更加要注意的是 最大堆内存与最大非堆内存的和绝对不能够超出操作系统的可用内存
3、GC日志(分析)
①、打印GC日志方法,在JVM参数里增加参数
- XX :+ PrintGCDetails - XX :+ PrintGCTimeStamps - XX :+ PrintGCDateStamps - Xloggc : D : \gc . log
②、Tomcat则直接加在JAVA_OPTS变量里
set “JAVA_OPTS=‐XX:+PrintGCDetails ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCDateStamps ‐Xloggc:./gc.log”
③、可以将文件 放到 GCeasy帮我们分析,可以查看吞吐量等
4、如何避免内存泄漏、溢出
①、尽早释放无用对象的引用: 好的办法是使用临时变量的时候,让引用变量在退出活动域后自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露。
②、程序进行字符串处理时,尽量避免使用String,而应使用StringBuffer。
③、尽量少用静态变量。因为静态变量是全局的,GC不会回收。
④、避免集中创建对象尤其是大对象,如果可以的话尽量使用流操作。
⑤、尽量运用对象池技术以提高系统性能: 生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。
⑥、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象: 可以适当的使用hashtable,vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次new之后又丢弃。
⑦、优化配置。
a、设置-Xms、-Xmx相等;
b、设置NewSize、MaxNewSize相等;
c、设置Heap size, PermGen space:
5、JVM内存分配机制
Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
Major GC/Full GC:一般会回收老年代,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢10倍以上。
JVM内存分配机制 | |
---|---|
对象优先存放在Eden区 | 大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。 |
大对象直接进入老年代 | 大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。 |
长期存活的对象进入老年代 | 对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就会被晋升到老年代中 |
对象动态年龄判断 | 当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的50%,那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了。对象动态年龄判断机制一般是在minor gc之后触发的 -XX:TargetSurvivorRatio 目标存活率,默认为50% |
Minor GC存活的survivor区放不下 | 把存活的对象部分挪到老年代,部分可能还会放在Survivor区 |
老年代空间分配机制 | 年轻代每次minor gc,之前JVM都会计算下老年代剩余可用空间如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象)就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minorgc后进入老年代的对象的平均大小。如果上一步结果是小于或者之前说的参数没有设置,那么就是触发一次Fullgc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生"OOM"当然,如果minor gc之后剩余的存活对象大小还是大于老年代可用空间,那么也会触发full gc。full gc完之后如果还是没用空间放minor gc之后的存活对象,则也会发生“OOM”。 |
位置 | 参数 | |
---|---|---|
大对象直接进入老年代(多大的对象) | -XX:PretenureSizeThreshold | |
长期存活的对象进入老年代年龄阈值 | -XX:MaxTenuringThreshold | 默认是15 |
老年代空间分配机制 | -XX:-HandlePromotionFailure | 默认开启(1.8) |
Eden与survivor区尽量8:1:1 | -XX:+UseAdaptiveSizePolicy |
6、内存对象的回收
内存对象回收 | ||
---|---|---|
1 | 引用计数法 | 给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。 |
2 | 可达性分析算法 | 这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象。GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等。不可达对象变为可回收对象至少要经过两次标记过程。 |
1、方法区中的静态属性(静态属性指向一个对象)
2、方法区的中的常量(常量指向一个对象)
3、虚拟机中的局部变量(变量指向一个对象)
4、本地方法栈中JNI(native修饰的方法指向的对象)
①、引用计数法存在问题
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用 的问题。 所谓对象之间的相互引用问题,如下面代码所示:除了对象objA 和 objB 相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。
②、常见引用类型
java的引用类型一般分为四种:强引用、软引用、弱引用、虚引用
强引用:普通的变量引用
软引用:将对象用SoftReference软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉。软引用可用来实现内存敏感的高速缓存
public static SoftReference user = new SoftReference(new User());
③、finalize()方法最终判定对象是否存活
即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。
第一次标记并进行一次筛选。筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize方法,对象将直接被回收。
第二次标记。如果这个对象覆盖了finalize方法,finalize方法是对象脱逃死亡命运的最后一次机会,如果对象要在finalize()中成功拯救自己,只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
④、如何判断一个类是无用的类
类需要同时满足下面3个条件才能算是 “无用的类” :
1、该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
2、加载该类的 ClassLoader 已经被回收。
3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。