您的位置:首页 > 其它

jvm与调优

2015-06-06 15:00 232 查看
一,java1、java和c++的区别1)c++属于静态编译,运行前将源码编译为机器码;java先将源码编译为字节码,运行期解释和动态编译相结合。2)C++可以将对象分配在堆,栈,常量池中,堆中的对象,需要delete手动删除。java的对象不能在栈中,gc自重删除。3)利用多态时,C++需要对函数声明virtual进行动态绑定;java自动为动态绑定。4)C++的成员变量只能在对象生成后赋初值后使用;java可以随时赋值或者不赋值。
2、jdk和jre的不同jre为java运行时环境,由jvm和核心类库组成。jdk为java开发工具包,除了包含jre还有一些编译运行工具如javac编译器,rmi远程调用包。
3、Hotspot VM包括三大部分:垃圾收集器,JIT编译器,VM运行时(开关jvm,类加载,解释器)。
二、内存管理
1.java内存模型
1)栈(方法相关):方法执行时会产生一个栈帧,存放局部变量表,出口地址等。栈帧过多时(无限递归),抛出stackoverflowerror;无法申请到足够内存时,抛出outofmemoryerror.线程独有
2)堆(对象相关):线程共享,存放对象实例和对象中的非静态成员变量。无法申请到足够内存时,抛出outofmemoryerror.
3)常量区(hotspot中的永久带,类相关):线程共享,存放已加载的类信息,常量,静态变量,编译后的代码。
2,jvm参数eclipse.in设置1)堆内存:JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存 由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70% 时,JVM会减少堆直到-Xms的最小限制。因此一般设置-Xms、-Xmx相等以避免堆内存的自动扩展。2)非堆内存:JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。
3,JVM异常处理实战:OutOfMemoryError   
当出现此问题时,通过观察OutOfMemoryError 后面是哪一块的内存的溢出,分别采取措施。
1)堆溢出
通过设置-XX:+HeapDumpOnOutOfMemoryError 或者用jmap软件让JVM在发生内存溢出时自动的生成堆转储快照以便事后进行分析。
利用工具自动分析dump文件(堆转储快照:是一个用来检查Java内存中的对象和数据的文件),对问题定位并分析是由于内存泄露还是内存溢出导致。内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory。如果是内存溢出,参照物理内存适当调大堆参数或者从代码上调整某些对象的生存周期。内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。如果是内存泄露,查看GC Root引用链信息,掌握泄漏对象情况,对代码做出修改。2)栈溢出栈容量由-Xss设定,栈帧过多时(无限递归),抛出stackoverflowerror;无法申请到足够内存时,抛出outofmemoryerror.实验表明,单线程情况下,无论是栈帧太大还是-Xss太小,都只会有stackoverflowerror。多线程情况下的会产生outofmemoryerror,因为物理内存减去堆内存和常量区内存就是占内存,这种情况下只能通过减小堆和常量区的内存来解决。3)常量区溢出常量区溢出情况会出现在大量的jsp文件(因为jsp要被翻译为servlet类),或者spring的增强类。通过调整参数改变常量区大小。
4,jvm性能检测与故障处理工具(运行时)命令行工具:
jps:可以列出正在运行的虚拟机进程,显示虚拟机执行的主类名称和本地唯一ID。当有多可虚拟机进程时,只有此方法查看。
jstat:监视虚拟机运行状态信息,没有GUI,只有纯文本数据。包括类装载,内存容量,垃圾收集(如执行了多少次gc)等参数。
jinfo:实时查看和调整虚拟机的各项参数。
jmap:生成堆转储快照。jhat:分析堆转储快照。
jstack:生成当前线程快照,用以分析后查看各个线程的状态和死锁检测。
可视化工具:
JConsole[/b]:内存监控:相当于可视化的jstat;线程检测:相当于可视化的jstack;Mbean:可以使用代码,中间件控制台实现管理功能
VisualVM[/b]:功能更强大,除了Jconsile中的故障监视和处理外,还有生成和分析堆转储快照;分析程序性能(Profiling):CPU性能(方法执行的次数耗时)和内存分析(每个方法关联的对象和占用空间);

5,String的内存分布问题

String s5 = "a"+"b";常量区创建了三个对象,但“a”和“b”很快会被回收。 String s6 = s3+s4;堆里创建一个对象,相当于执行new。 

 
分析 String s = new String("abc");首先在堆(不是常量池)中创建一个包含指定内容的字符串对象,并将字符串引用指向该对象。例如上述代码中,使用new创建字符串s,其会直接在堆中创建一个内容为“abc”的字符串对对象,并将引用s指向该对象。
去字符串常量池中查看,是否有包含该内容的对象。
若有,则将new出来的字符串对象与字符串常量池中内容相同的对象联系起来。
若没有,则在字符串常量池再创建一个包含该内容的字符串对象,并将堆中的对象与字符串常量池中新创建出来的对象联系起来。

三,垃圾收集器
1、可达性分析算法GC ROOT :从栈中或者常量区中的引用变量触发,沿着引用链访问堆中的对象,访问到的做上标记。最后,没有被标记的就是垃圾。
2、System.gc()和Runtime.gc()会做什么事情?
显示调用这两个方法用来提示JVM要进行full gc。但是,立即开始还是延迟进行垃圾回收是取决于JVM的算法。这两个方法完全一样。
finalize()方法什么时候被调用?
在释放对象占用的内存之前,垃圾收集器会调用对象的finalize()方法。一般建议在该方法中释放对象持有的资源。该方法是对象逃逸的最后一次机会。

3、垃圾收集算法:



 
标记-清除(Mark-Sweep)
此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法会产生内存碎片。
 复制(Copying)
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把还存活对象复制到另外一个区域中。复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间,存活较多的话,复制的成本较高。
jvm的新生代用此算法,把新生代分为一块eden和2块survivor(默认为8:1,可以改变)。第一次gc之前,新产生的对象只存放于eden中,其中一块survivor存放上一次gc存活的对象,另一块survivor空着。第一次gc后,把eden和survivor中的对象复制到空着的survivor中,将eden和一块survivor空出来,如此循环。此方法解决的对半分空间浪费的问题,但survivor空间较小,需要老年代做空间担保。
标记-整理(Mark-Compact)
此算法结合了 “标记-清除”的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把存活对象移向内存的一端并按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
分代收集算法,新生代和老年代.根据存活周期划分(存活周期是否大于15)。新生代用复制算法。因为老年代中的对象存活率较高,采用标记-清除或者标记-整理。
4、.gc的分类
分为minor GC和full GC,前者只对新生代进行,后者对整个堆进行(其实包括新生代,老年代,永久带)。

5、垃圾收集的触发:1)隐式触发:当需要分配内存而没有足够的空间时,就会自动触发gc。新生代(其实是eden)满了触发minor GC;老年代或者永久代满了都触发full gc。指针碰撞技术检测空间是否满。2)显式触发:system.gc
3)安全点和主动式中断:但并不是触发垃圾收集就会立即gc,因为在gc root时会停顿所有用户线程(stop the world),所以程序必须执行到安全点时才能进行gc,安全点的选取标准为“是否具有让程序长时间执行的特征”,如方法调用,循环跳转;主动式中断:在每个安全点设置中断标志,每次到安全点用户线程会检测一下该标志。平常状态下中断标志为false,当要进行gc时,标志被设为true。4)安全区域:指一段代码段中,对象的引用关系不会发生变化。线程进入该区域时,标示自己为Safe;离开该区域的时候,线程会检测是否gc在发生,如果发生,线程停止等待。当gc要发生时,自动忽略这些处在safe状态的线程。此方式适用于gc触发时处于阻塞的线程,因为阻塞状态下,gc没有cpu时间,不会检测安全点的标志位。

6、.gc日志在eclipse.ini 中写入-XX:+PrintGCTimeStamps  //时间戳-XX:+PrintGCDetails    //详细信息-verbose:gc  -Xloggc:gc.log  //日志保存到指定的文件中。http://www.cnblogs.com/yydcdut/p/3959711.html

7、.对象内存的分配原则:先到eden中;大对象直接进老年代;内存担保(在一次复制算法后,存活的对象会被复制到另一块survior中,若survior放不开,就会将survivor中的所有对象提前晋升到老年代)。
8、垃圾收集器的分类:总体分为单线程,并行和并发。单线程和并行方式都会使用户线程完全停止,不同在于垃圾收集会以单线程还是多线程方式执行。并发会使用户与gc同进行,这样降低了停顿时间,对应用来说十分重要。但是两个标记过程仍会使用户线程短暂停顿,而且在并发收集中无法空出足够的空间时会出现Concurrent Mode Failure,此时会触发一次stop the world的full gc。可以在eclipse.ini指定所用的垃圾收集器的类型。http://blog.csdn.net/china_wanglong/article/details/38816575

四,编译和运行java------(javac)-----.class-------(jvm)-----机器码
1、.class文件中的主要内容:
1)常量池:字面量(字符串和final的常量),符号引用(类,字段,方法的名字)。后面的部分需要的字符串全部引用 常量池的内容。
2)访问标志:类或接口的类型(public?)
3)继承关系
4)字段表集合和方法标集合
5)属性表集合:类,方法,字段都有自己的属性表。其中方法属性中的code区域存放方法中的代码。方法中的代码会 被编译为字节码指令。
2、在任何非静态方法中都有一个隐藏参数,就是this,他表示该方法所属的对象,而不是类。
3、.类构造器和实例构造器:实例构造器就是类的构造函数,某一个类被实例化时默认首先调用该函数,有继承的话首先调用其父类的构造函数。类构造器在类被加载时默认执行,执行所有被static修饰的变量和语句快。static变量在类加载时就被赋值而final则是在编译时就被赋值。4、.类加载(运行期边运行边加载)1)触发:new,调用类的static成员;反射;继承,父类先于子类;主类,main();
当类加载后,会在常量区建立一个java.lang.Class对象,作为访问这个类的外部接口。
2)类加载器与双亲委派模型


 http://wangwengcn.iteye.com/blog/1618337

5、反射的原理:就是在运行期间,遇到反射包中的方法时,Java虚拟机(JVM)会检查该类型的Class对象是否已被加载。如果没有被加载,JVM会根据类的名称找到.class文件并加载它。一旦某个类型的Class对象已被加载到内存,就可以用它来产生该类型的所有对象。

通过反射可以在运行期访问编译器并不知道的类,比较灵活,想加载哪些类在配置文件中配好就可以了,不用修改源代码。

6、在Class文件中所存放的均为符号引用(就是一组符号描述目标),类加载时将相应符号引用变为直接引用。

7、Human man = new Man();   Human为静态类型,在编译时可知;Man为实际类型,在运行时可知。方法的重载根据编译期静态类型选择,方法的重写根据运行期实际类型。

8,解释器与JIT1)解释器:一条一条的将字节码翻译为机器码。2)运行时编译器(JIT):通过计数器发现热点代码(多次被调用的方法或者循环),将热点代码所在方法的字节码编译为机器码并进行优化,并提供今后多次使用。JIT有client和server模式,client模式编译速度快,但只关注局部优化;server模式变异速度慢,但代码优化程度很高。

正常情况下,jvm一条一条的解释字节码到机器码,但碰到热点代码时jvm用即时编译器(JIT)把整个热点代码编译为机器码。但被JIT编译后的代码如果遇到加载新类后继承关系变化或者“罕见陷阱”时,会将这段代码退回到解释状态,这叫逆优化。所以,运行期通常是解释器与JIT配合工作。


9,语法糖:编译期,编译为.class后被擦出改为传统语法的实现。
泛型,自动装箱,拆箱,循环,集合的变长参数。

五,JVM性能调优(GC调优)
1、JVM性能指标
1)内存占用:应用程序所占用的各部分堆内存(新生代,老年代,永久带)尽可能合理。
2)延迟:JVM最大的延迟是GC,延迟是衡量GC停顿的重要指标。
3)吞吐量:除掉GC所用的时间后,真正执行程序所在总时间的百分比
2、应用程序的部署
单JVM部署:管理开销,内存占用小,但不保证程序的可用性。
多JVM部署:将某个应用程序部署在多个JVM同时运行。
一般情况下,在保证JVM可用性的同时,尽量减少JVM的数量。
3、JVM运行模式
1)Client和Server:参考JIT
2)32/64位JVM:64bit支持更大的内存空间。
注:当以下GC调优后均达不到用户要求的指标时,可以通过改变2,3中的措施,但改变后需要从新调优。

4,GC调优:主要依据于JVM的三个性能指标,根据gc日志信息。
总体原则:1)使Minor GC尽可能多的回收对象,尽量减少full GC。
2)物理内存许可情况下,尽可能增大堆内存。
3)三个性能指标兼顾两个。

5,堆初始内存配置
1)首先利用JVM自动配置的内存大小将应用程序推进到稳定态。此过程可能会发生OutOfMemoryError,解决见2-3
2)活跃数据:应用程序运行于稳定态时,并在多次full GC后,仍存活的对象所占用堆各部分的空间大小。
3)可以通过新生代,老年代,永久带中的活跃数据确定内存初始大小。



6,GC进一步的优化1)新生代大小的优化
主要依据于从gc日志中算出的minor GC的平均时间和频率。其中增大新生代大小会减小gc频率,但增大了gc的时间;减小则反之。
改变新生代大小时,尽量保持老年代大小恒定。
2)老年代大小优化
主要依据于从gc日志中算出的full GC的最大延迟时间和频率。其余与新生代相同。
如果传统的老年代收集器无法满足full GC时的指标要求,则使用并行的CMS。
CMS的优化注意CMS的启动时间,根据gc日志判断,如果CMS启动太晚,会触发Stop the world的full GC;如果启动的太早,没有太多垃圾可回收,消耗了系统的资源。
3)Survivor的空间和晋升年龄值优化
老年代是survivor的担保,如果Survivor太小,则会使Survivor溢出直接晋升到老年代,而使得full GC压力变大,因此应合理设置Survivor大小。另外在调整Survivor大小时,应尽量保证eden的值不变,否则会影响minor GC。
晋升阈值是指达到晋升阈值的新生代对象的年龄会被晋升到老年代,可以通过监控晋升中存活对象大小来设定Survivor的大小来防止对象的逸出或者改变晋升阈值来控制新生代对象的晋升。

如图所示的晋升监控,系统自动计算的Survivor为desired Survivor。当前晋升阈值为15;图中的三列包括对象年龄,该年龄的存活对象大小,总存活对象大小。由图可知并没有发生Survivor逸出,可以根据此数据调优。调优:我们可以将每块Survivor空间设置为所有年龄存活对象总数大小的2倍,以保证不发生逸出;把晋升年龄设置为4,因为age=4之后存活的对象比较少,而且每一代晋升的对象大小基本恒定,以保证老年代不会频繁GC。使用ParallelGC的自适应调优也可以。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: