String str1="hello"; String str2="he"+new String("llo"); System.out.println(str1==str2);//false
一、两个或者以上的字符串常量相加,【String str="s1"+"s2"】,在预编译的时候“+”会被优化,至关于把两个或者两个以上字符串常量自动合成一个字符串常量html
二、字符串的+操做本质上是new了StringBuilder对象进行append操做,拼接后调用toString()返回String对象(可经过javap -c xxx.class查看字节码指令)java
Code: 0: ldc #10 // String hello 2: astore_1 3: new #11 // class java/lang/StringBuilder 6: dup 7: invokespecial #12 // Method java/lang/StringBuilder."<init>":()V 10: ldc #13 // String he 12: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: new #15 // class java/lang/String 18: dup 19: ldc #16 // String llo 21: invokespecial #17 // Method java/lang/String."<init>":(Ljava/lang/String;)V 24: invokevirtual #14 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 27: invokevirtual #18 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
1、GC日志分析linux
为了在内存溢出时排查缘由,能够在JVM启动时加一些参数来控制,当JVM内存出问题时能够经过分析记录下来的GC日志,GC的频率和每次GC回收了哪些内存算法
GC的日志输入有如下参数sql
一、-verbose:gc 能够辅助输出一些详细的GC信息数组
二、-XX:+PrintGCDetails 输出GC的详细信息浏览器
三、-XX:+PrintGCApplicationStoppedTime 输出GC形成应用程序暂停的时间安全
四、-XX:+PrintGCDateStamps 输出GC发生的时间信息服务器
五、-XX:PrintHeapAtGC 在GC先后输出堆中各个区域的大小多线程
六、-Xloggc:[file] 将GC信息输出到单独的文件
每种GC方式输出日志的形式不一样,除CMS的日志和其余GC方式差别较大外,其他GC方式的日志能够抽象成以下方式
[GC [<collector>: <starting occupancy1> -> <ending occupancy1> (total size1) , <pause time1> secs]
<starting occupancy2> -> <ending occupancy2> (total size2) , <pause time2> secs] ]
说明以下
一、<collectot>GC 表示垃圾收集器的名称
二、<starting occupancy1> 表示Young区在GC前占用的内存
三、<ending occupancy1> 表示Young区在GC后占用的内存
四、(total size1) 表示Young区的总内存大小
五、<pause time1> 表示Young区局部收集时JVM暂停处理的时间 secs表示单位秒
六、<starting occupancy2> 表示Heap在GC前占用的内存
七、<ending occupancy2> 表示Heap在GC后占用的内存
八、(total size2) 表示Heap的总内存
九、<pause time2> 表示在GC过程当中JVM暂停处理的总时间
能够根据日志来判断是否存在内存泄漏的问题:
<starting occupancy1> - <ending occupancy1> 和 <starting occupancy2> - <ending occupancy2> 比较
一、若是前者差等于后者差,代表Young区GC 对象100%被回收,没有对象进入 Old区或者Perm区
二、若是前者大于后者,那么差值就是此次GC对象进入Old或者Perm区的大小
若是随着时间的的延长,<ending occupancy2>的大小一直在增加,并且Full GC很频繁,那么极可能就是内存泄漏致使的。
2、堆快照文件分析
一、经过命令 jmap -dump:format=b,file=[filename][pid] jmap(Memory Map for Java)
来记录下堆的内存快照,而后利用第三方工具如eclipse 插件MAT来分析整个Heap的对象关联状况。
若是内存耗尽可直接致使JVM退出,能够经过参数
-XX:+HeapDumpOnOutOfMemoryError 来配置当内存耗尽时记录下当时的内存快照
-XX:HeapDumpPath 指定内存快照文件的路径 文件快照的名称格式为 java_[pid].hprof
若是是OOM,可能有两方面的缘由
一、内存分配太小,不知足程序运行所须要的内存
二、内存泄漏(FullGC频繁,回收后Heap占用的内存不断增加)
3、JVM Crash 日志分析
TODO
垃圾收集器与内存分配策略
栈的内存随着方法的结束和线程结束自动回收,所以Java堆和方法区是垃圾收集器所关注的内存
判断对象是否能够回收
一、 引用计数法:给对象中添加一个引用计数器,当有一个地方被引用时加1,引用失效减1,计数器为0的就是能够回收的,可是会有互相引用的状况
二、可达性分析法 对象到一系列称为GC Roots的对象有没有引用链相连
即便在可达性分析法中不可达的对象,也至少要经历两次标记过程
第一次标记:可达性分析后无与GC Roots相连的引用链
第二次标记:第一次标记后筛选(finalize()方法没有被JVM调用过)后放置在F-Queue队列中,仍无引用链和GC Roots相连则进行第二次标记
方法区的收集:废弃常量和无用的类
废弃的常量:如常量池中的字符串常量“abc”,没有String对象引用常量池的这个“abc”常量,那么abc就是废弃常量能够移除常量池
无用的类:一、该类的实例都被回收 二、加载该类的ClassLoader已被回收 三、该类的Class对象没有在任何地方被引用,也就是没法经过反射访问该类的方法
垃圾收集算法
一、复制
将内存划分为大小相等的两块,每次只使用其中一块,当其中的一块用完了将其上面存活的对象复制到另外一块上面,而后把使用过的内存空间一次清理掉。缺点是将可用内存缩小为了原来的一半,对象存活率较高时不适合使用。
新生代中的对象98%都是朝生夕死的,所以新生代按照8:1:1的比例分为了eden,survivor from 和survivor to空间,每次回收将eden和survivor from中存活的对象复制到survivor to中,不够的话再放到old中,而后将eden,survivor from一次清除掉。
二、标记-清除
首先标记须要回收的对象,在标记完成后统一回收 问题一、效率问题:标记和清除效率都不高 二、空间问题:清除后会产生大量内存碎片,过多的话会致使之后分配大对象如数组找不到一块连续的内存而提早触发一次GC
三、标记-整理
首先标记须要回收的对象,而后将全部存活的对象向一侧移动与将要回收的对象分隔开,而后将要回收的对象一次清理掉,适合用再老年代上。
分代收集
新生代每次垃圾回收都有大量的对象死去少许存活,只需付出少许对象的复制成本便可完成收集。采用复制算法
老年代对象存活率高,没有额外的空间作担保, 只能采用标记-清除或者标记-整理算法
新生代垃圾收集器:Serial、ParNew、ParallelScavenge、G1
老年代垃圾收集器:CMS、Serial Old(MSC)、Parallel Old、G1
垃圾收集器的发展,使用户线程的停顿时间在不断缩短,可是仍没办法彻底消除,所以寻找更优秀的垃圾收集器仍在继续!
Serial收集器:单线程,采用复制算法,并且进行垃圾收集时,必须暂停JVM其余全部的工做进程,直到它收集结束。还是Client模式下虚拟机新生代默认收集器
ParNew收集器:Serial的多线程版本,采用复制算法,其余基本相同。是运行在server模式下的虚拟机首选的新生代收集器
Parallel Scavenge收集器:与其余收集器关注点在缩短用户线程停顿时间不一样,它关注点是达到一个可控制的吞吐量,吞吐量=运行用户代码时间/(运行代码时间+垃圾收集时间)如:JVM总运行100分钟,垃圾收集1分钟,那吞吐量=99%,若是新生代采用了此收集器,那老年代只能使用Serial Old收集器
Serial Old收集器:Serial收集器的老年代版本,一样单线程,采用标记-整理算法,存在乎义是给Client客户端JVM使用
Parallel Old收集器:Parallel Scavenge收集器的老年代版本,采用多线程和标记-整理算法
CMS收集器(Concurrent Mark Sweep):一种以获取最短回收停顿时间为目标的收集器,基于标记- 清除算法实现。(只会收集老年代和永久代,1.8后改成元空间(须要设置 CMSClassUnloadingEnabled)),不会收集年轻代
一、初始标记 标记GCRoots直接关联的对象
二、并发标记 往下跟踪标记全部与GCRoots有引用链可达的对象 (可与用户线程同时工做),就是进行GCROOTS Tracing的过程
三、从新标记 修正并发标记期间因用户线程运行而致使的标记变更的一部分对象
四、并发清除 清除未标记的对象 (可与用户线程同时工做)
缺点:
一、虽然在并发阶段可与用户线程同时工做,可是会占用CPU资源,致使应用程序变慢,总吞吐量会下降
二、没法处理浮动垃圾,即在并发清除阶段新产生的垃圾,只有留待下一次GC时再清理掉
三、使用标记-清除算法,会有大量内存碎片产生
G1收集器(Garbage-First):特色:
一、并行与并发:充分利用多CPU,多核环境的硬件优点,来缩短停顿时间,在GC期间可经过并发的方式让Java程序继续执行
二、分代收集:采用不一样的算法去收集刚建立的对象,存活了一段时间的对象和熬过屡次GC的对象,以获取更好的收集效果
三、空间整合:总体基于标记-整理算法,内部region之间采用复制算法,都不会产生内存空间碎片
四、可预测的停顿:除了追求短期停顿外,还创建了可预测停顿模型,使在M毫秒内,在垃圾收集上的时间不超过N毫秒
G1逻辑上将整个Java堆划分为多个大小相等的独立区域(region)。仍保留新生代和老年代的概念,但它们之间不是物理隔离了,新生代和老年代都是一部分region的集合了。G1之因此
能创建可预测停顿模型,由于它能够有计划的避免在整个Java堆中进行全区域的垃圾收集,G1跟踪各个Region里面的垃圾堆积的价值大小(回收所得到的空间大小以及回收所需时间的最优),在后台维护一个优先列表,每次根据容许的收集时间,优先回收价值最大的region,这也是Garbage-First的由来。这种使用region划份内存空间以及有优先级的区域回收方式保证了G1在有限的时间内可获取尽量高的收集效率。
在G1收集器中,Region之间的对象引用以及其余收集器中的新生代和老年代之间的对象引用都是使用Remembered Set来避免全堆扫描的。G1中每一个Region都有一个与之对应的Remembered Set,
虚拟机发现程序在对Reference类型的数据进行写操做时,会产生一个Write Barrier暂时中断写操做,检查Reference引用的对象是否处于不一样的Region之中(在分代的例子中就是检查是否老年代中的对象
引用了新生代中的对象),若是是,便经过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set之中。当进行内存回收时,在GC根节点的枚举范围中加入Remember Set便可保
证不对全堆扫描也不会有遗漏。
若是不计算维护Remembered Set的操做,G1收集器的运做大体可划分为如下几个步骤:
一、初始标记
二、并发标记
三、最终标记
四、筛选回收
内存分配与回收策略
Minor GC 新生代GC:通常比较频繁,回收速度也比较快
Major GC/Full GC 老年代GC:调用System.gc() 强制执行GC为Full GC
(Full GC停顿时间比Minor GC高几个量级,通常为50倍以上)
对象优先在Eden区上分配,Eden区上没有足够的空间分配时,触发一次Minor GC(新生代GC),将存活的对象复制进Survivor to区,若Survivor to区没有足够的空间存放,则经过分配担保机制将对象转移到老年代中。同时,通过一次Minor GC进入到survivor to区的对象,年龄计数器设为1,在Survivor from区的对象每通过一次Minor GC,年龄加1,当年龄增长到 -XX:MaxTenuringThreshold 设定的阀值(默认15)或者在Survivor区中有相同年龄的全部对象大小总和大于Survivor区大小的一半,那么大于这个年龄的对象,将会被移动到老年代中。
每进行Minor GC以前,在容许担保失败的状况下,JVM将查看老年代中最大可用连续空间是否大于历次minor GC晋升到老年代的对象的平均大小,若是大于,将进行一次Minor GC;若是minor GC后老年代空间不足,则紧接着触发Full GC,若是小于,则直接触发Full GC。(新生代和老年代的比例默认是1:2)
(HandlePromotionFailure设置是否容许担保失败(默认容许),若是不容许担保失败,那么每次Minor GC前JVM查看老年代中最大的连续空间是否大于新生代全部对象的大小总和,若是小于,则直接触发Full GC)
大的对象可能直接进入老年代,避免在Eden区和两个Survivor区之间发生大量的内存复制。典型的大对象是那种很长的字符串对象或者数组。超过 -XX:PretenureSizeThreshold参数配置的大小的对象直接在老年代分配内存。
JVM性能监控和故障处理
经过工具导出和处理分析 运行日志、异常堆栈、GC日志、线程快照(threaddump/javacore文件)、堆转储快照(headdump/hprof文件)等
jps:JVM process status tool,显示指定系统内全部的HotSpot虚拟机进程
jstat:JVM statistics Monitoring Tool,收集HotSpot虚拟机各方面的运行数据
jinfo:Configuration Info for java,显示虚拟机配置信息
jmap:Memory map for Java,生成虚拟机的内存转储快照(heapdump文件)
jhat:JVM Heap Dump Browser,用于分析heapdump文件,它会创建一个http/html服务器,让用户能够在浏览器上查看分析结果
jstack:Stack trace for Java,显示虚拟机的线程快照
jps:和linux中ps命令类似,可列出正在运行的虚拟机进程,并显示虚拟机执行主类和这些进程的惟一ID(Local Virtual Machine Identifier LVMID)
格式:jps [option] [hostid(主机名)]
参数:-l 输出主类的全路径 -v 输出虚拟机进程启动是的JVM参数
jstat:用于监视虚拟机各类运行状态信息的命令行工具,它能够显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、JIT编译等运行数据,是定位虚拟机性能问题的首选工具。
格式:jstat [ option vmid [interval] [count] ]
interval和count是查询间隔和次数,若是忽略这两个参数则只查询一次
选项:-class 监视类装载、卸载数量、总空间以及类装载消耗时间
-gc 监视Java堆情况,包含Eden区,两个Survivor区、老年代、永久代等的容量,已用空间,GC时间等信息。
jmap:Java内存映射工具,用于生成堆存储快照heapdump,生成堆快照还能够经过设置参数使在OOM异常以后自动生成堆dump文件。
jmap还可查询finalize执行队列、Java堆和永久代的详细信息(空间使用率,使用哪一种收集器等)。
格式:jmap [option] vmid
option参数
-dump:format=b,file=[filename] 生成堆转储快照
-heap 显示Java堆详细信息,如使用哪一种收集器、参数配置、分代情况等。
jhat:与jmap搭配使用,分析jmap生成的堆转储快照文件。通常不会直接使用jhat命令分析dump文件,一是不会直接在应用服务器上分析dump文件,由于分析耗时耗资源,二是jhat分析结果比较简陋,可用VisualVM,MAT等工具
jstack:Java堆栈跟踪工具,用户生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成快照的主要目的是定位线程出现长时间等待的缘由,如线程间死锁、死循环、请求外部资源(如sql)致使的长时间等待等。
线程出现停顿的时候经过jstack来查看各个线程的调用堆栈,就能够知道没有响应的线程到底在后台作什么事情或者等待什么资源
格式:jstack [option] vmid
option参数,-F:强制输出线程堆栈
-l:除堆栈外显示关于锁的附加信息
-m:可显示调用本地方法的堆栈
JDK1.5以后的Thread类新增了getAllStackTraces()方法用户获取虚拟机中全部线程的StackTraceElement对象。和jstack功能相似
public static Map<Thread,StackTraceElement[]> getAllStackTraces() 返回从 Thread 到 StackTraceElement 数组的一个 Map,表明相应线程的堆栈跟踪。 、
总结若是要定位OOM问题使用jps和jmap组合命令,先用jps或者linux的ps命令查看虚拟机进程的vmid,而后用jamp命令生成堆快照文件,最后使用工具分析dump文件定位问题。
若是要定位线程响应时间过长的问题,使用jps和jstack命令,先用jps或者linux的ps命令查看虚拟机进程的vmid,而后用jstack命令查看线程堆栈信息
JVM调优
JVM调优是经过分析GC日志等来分析java内存和垃圾回收的状况,来调整各内存区域内存占比和垃圾回收策略。充分使用系统资源,减小GC停顿时间和停顿次数,因为Full GC的停顿时间远比Minor GC的停顿时间长,所以要控制Full GC的频率。控制Full GC的频率的关键是看应用中的绝大多数对象是否符合“朝生夕灭”的原则,即大多数的对象的生存时间都不该太长,尤为是不能有成批量的、长时间存活的对象产生,这样这些对象在Minor GC就会被回收,不会进入老年代,这样才能保证老年代的稳定。好比对于十几小时乃至一天才出现一次Full GC的系统能够经过定时任务的方式在夜间触发Full GC。若是FullGC次数过多多是下面的缘由:一、内存占用高:代码中建立了大量的对象致使内存泄漏,不能回收内存,建立新对象致使空间不足触发fullGC二、内存占用不高:多是显示的调用System.gc()次数太多致使的fullGC,能够经过添加-XX:+DisableExplicitGC来禁用JVM对显式GC的响应