众所周知,jdk软件开发工具包,jre java类库,jvm java虚拟机,废话不多说,详细结构图如下:
深入学习JVM可以解决哪些问题?1)有效防止内存泄漏,内存溢出
2)优化线程锁的使用
3)科学进行垃圾回收
4)提高系统吞吐量
5)降低延迟,提高性能
JVM的工作方式在主流的jvm中,将字节码文件翻译成机器码提供解释执行和编译执行两种方式
1)热点代码: 频繁使用的方法
2)解释执行器: 逐条将字节码翻译成机器码执行
3)编译执行器: 即时编译(JIT),使用热点代码执行
如何理解JIT?
JIT(即时编译)用来提高java程序运行效率的一种技术,可以将字节码编译成相关的原生机器码,进行各个层次的优化,并将这些机器码缓存起来,以备下次使用
JVM中解释执行与编译执行并存解释器和编译器两者各有优势,当程序需要启动和执行时,解释器可以首先发挥作用,省去编译的时间,立即执行。程序运行后,随着时间的推移,即时编译发挥作用,对反复执行的热点代码以方法为单位即时编译,可以获取更高的执行效率。但是如果JIT对每条字节码都进行编译,缓存,会增加开销,只存一部分常用的字节码,不可能做到把全部的字节码都缓存,所以,可以使用解释器执行节省内存,使用编译执行提高效率。
JVM的运行模式JVM有两种运行模式,Server和Client,两种模式的区别在于,Client模式启动速度比较快,Server启动较慢,但是启动进入稳定期后,Server的运行速度要比Client快。这是因为Server模式采用的重量级虚拟机,对程序做了很多优化,而Client模式采用轻量级虚拟机,所以Server启动慢,但是稳定后速度要比Client快的多。
可以在终端使用java -version查看JVM的运行模式
JVM体系结构 JVM内存结构是怎样的?JVM启动运行字节码文件会对JVM内存进行划分,可分为线程共享区和线程独享区。
一旦发生内存溢出问题,只可能是堆,方法区,虚拟机栈出现的溢出
堆:创建的对象过多,内存不足
方法区:字节码类过多,内存不足
栈:递归循环调用方法,不断压栈,内存溢出
程序计数器:只做一些运算,相对来说,压力不是太大
虚拟机栈和本地方法栈的区别?
虚拟机栈服务的对象是JVM的java方法,而本地方法栈是JVM的native方法(C语言编写接口)
其运行时详细内存架构如下:
堆,栈,方法分别储存的内容
堆区:一切new出来的对象
栈区:对象引用,基本数据类型
方法区:类信息,class文件,static变量与方法
堆内存概要:
1)虚拟机启动时创建,被所有线程共享,用于存放所有java对象实例
2)分为年轻代和老年代
3)垃圾收集器管理的主要区域
4)堆中没有内存分配将会抛出OutOfMemoryError
对象内存分配:
对象的内存分配一般都分配在新生代,当对象比较大时,新生代没有足够的空间便直接分配到老年代。有时候为了减少GC的开销,对于小对象且没有逃逸的对象还可以直接在栈上分配。
什么是逃逸的对象?
对象在方法外声明,在调用局部方法之前,其中对象的引用提前创建好了,使用时直接调用该对象引用,就为逃逸对象,该对象会分配在堆内存中。
Object obj; public void method(){ obj = new Object(); }
对象在方法内声明,执行局部方法时调用才会创建对象,如果对象的开销小直接分配在栈中。
public void method(){ Object obj = new Object(); }方法区
概要:
1)同堆一样在线程共享区,但非堆内存,用于存储类的数据结构信息
2)方法区中包含的都是整个程序中永远唯一的元素,如class,static变量
3)jdk版本的不同,方法区的实现方式也不同
方法区的演进:
jdk7及以前,方法区也被称为永久代,jdk8开始,使用了元空间取代了永久代,jdk8中的方法区对应的是metapase。两者都是JVM规范中方法区的实现,两者最大的区别在于元空间不在虚拟机设置的内存中,而是使用本地内存。两者间不只是名字变了,内部结构也调整了,如果方法区无法满足新的内存分配需求时,将抛出OOM一场。
内存配置参数:
metaspaceSize:设置元数据区最小空间。(jdk8)
MaxmetaspaceSize:设置元数据区最大空间。(jdk8)
程序计数器1)线程启动时创建,线程私有。
2)用于记录当前正在执行的虚拟机字节码指令地址。
3)Java虚拟机中唯一一个没有内存溢出的区域。
虚拟机栈概要:
1)用于存储栈帧对象,保存方法的局部变量表, *** 作数栈,执行运行时常量池的引用和一些添加的附加信息。
2)一次方法调用都会创建一个新的栈帧,并压栈,当方法执行完毕后,便会将栈帧出栈。
3)栈上分配,对于几十byte的小对象,在没有逃逸的情况下,可以直接分配在栈上,可以自动回收,减少GC压力,大对象或是逃逸对象无法栈上分配
栈的空间大小
虚拟机规范允许栈的大小是动态的或是固定不变的,当栈的空间设的过小,会经常出现内存溢出的问题,当栈的空间设的过大,便没法让更多的线程去运行,毕竟 *** 作系统对进程中的线程数是有限制的,根据物理设备的性能去设置合适的空间大小。
1)如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建时独立选定,当线程请求分配的栈容量超出虚拟机栈的最大容量,会抛出StackoverflowError异常。
2)如果采用动态扩展的java虚拟机栈,当在尝试扩展时无法申请到足够的内存,或者在创建新线程时没有足够的内存去创建对应的虚拟机栈,那么将会抛出OutOfMemoryError异常。
本地方法栈本地方法栈和虚拟机栈类似,不过,本地方法栈服务的对象是JVM执行的native方法,就是java调用非java代码的接口,其目的就是与 *** 作系统和外部环境交互。
下图描绘了,当一个线程调用一个本地方法时,本地方法又回调到虚拟机的另一个java方法
该线程首先调用了两个Java方法,而第二个Java方法又调用了一个本地方法,这样导致虚拟机使用了一个本地方法栈。假设这是一个C语言栈,其间有两个C函数,第一个C函数被第二个Java方法当做本地方法调用,而这个C函数又调用了第二个C函数。之后第二个C函数又通过本地方法接口回调了一个Java方法(第三个Java方法),最终这个Java方法又调用了一个Java方法(它成为图中的当前方法)。
内存溢出分析堆内存溢出
如下代码,不断向list集合中存储新的对象,list集合底层不断进行扩容,当内存中没有足够的空间分配时,就会出现堆内存溢出
public static void main(String[] args) { long t1 = System.currentTimeMillis(); try{ Listlist = new ArrayList<>(); for (int i=0;i<25;i++){ list.add(new byte[1024*1024]); } }finally { long t2 = System.currentTimeMillis(); System.out.println("oom:(t2-t1)"); } }
程序中需要分配25个对象,但虚拟机参数中只给了堆内存25M的内存,一个对象占用一M的空间,显然内存不够,报堆内存溢出异常
元数据内存溢出
如下代码,当在有限的元数据内存区不断地加载新的类时会导致元数据区空间不足,从而出现内存溢出现象
public static void main(String[] args) { while (true){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ProxyObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("I am proxy"); return methodProxy.invokeSuper(o,objects); } }); ProxyObject proxy = (ProxyObject)enhancer.create(); proxy.greet(); } } static class ProxyObject{ public String greet(){ return "Thanks for you"; } }
虚拟机参数中定义为10M,但程序中while为真,就会不断加载类信息,必然会报元数据内存溢出异常
JVM命令行工具jps 虚拟机进程状况工具
jps -l 输出Java应用程序的main class的完整包
jps -q 仅显示pid,不显示其它任何相关信息
jps -m 输出传递给main方法的参数
jps -v 输出传递给JVM的参数。在诊断JVM相关问题的时候,这个参数可以查看JVM相关参数的设置
jstat 虚拟机统计信息监视工具
jstat –class pid 显示加载class的数量,及所占空间等信息
jstat -compiler pid 显示VM实时编译的数量等信息
jstat -gc pid 可以显示gc的信息,查看gc的次数,及时间
jstat -gccapacity 对象的使用和占用大小
jstat -gcutil pid 统计gc信息
jstat -gcnew pid 年轻代对象的信息
jstat -gcnewcapacity pid 年轻代对象的信息及其占用量
jstat -gcold pid old代对象的信息
stat -gcoldcapacity pid old代对象的信息及其占用量
jstat -gcpermcapacity pid perm对象的信息及其占用量
jstat -printcompilation pid 当前VM执行的信息
jinfo 实时查看,调整jvm参数
jinfo -flags PID 查看jvm参数
jinfo -flag name=value pid 修改指定参数的值
jmap 内存映射工具
jmap pid 查看进程内存映像信息
jmap -histo:live pid 统计堆中的对象信息
jmap -clstats pid 打印类加载信息
jmap -dump:format=b,file=heapdump.phrof pid 生成堆转储快照文件
jstack 堆栈跟踪工具
jstack -F pid 当线程无响应时,强制输出线程堆栈
jstack -l pid 显示线程堆栈及锁信息
jstack -m pid 若调用native方法,可以显示c/c++堆栈信息
JVM图形化监控工具1)Jsonsole
内置Java性能分析器,可以通过命令行jconsole或GUI shell中运行,Jconsole可用于jvm中内存,线程和类的监控,可以监控本地的jvm,远程的jvm,并且可以同时监控几个jvm。这款工具好处在于,占有资源少,结合Jstat,可以有效监控到java内存的变动情况,以及引起变动的原因。
2)VisualVM
一种集成了多个jdk命令行的可视化工具,提供强大的分析能力,可以通过命令行 jvisualvm 启动。在VisualVM的图形用户界面中,可以方便快捷的查看多个Java应用程序的相关信息。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)