**JVM: **java虚拟机,java的核心与基础,用来运行java的程序
1、java程序的运行过程
java源文件被编译器编译成.class文件
class文件被类加载器加载至内存//--------类加载器:将.class文件加载至内存并且生成相应的class对象
执行
2、JVM程序执行的顺序
加载.class文件>>>>管理分配内存>>>>执行垃圾收集
java语言的特点就是平台的无关性:
java虚拟机在执行字节码时,把字节码解释成具体平台的机器指令,这也就是java能够“一次编译,到处运行”的原因
JVM调优
先看下面这张图,然后我来简单的解释一下。
code cache不再本文讨论范围之内,请暂时无视这块区域
备注:下图为JVM7的内存结构,JVM8的perm空间做了如下修改:
Java7→Java8
Permanent→物理内存的Metaspace
后续讨论将基于JVM7的内存结构展开。
JVM为Java运行时环境,也就是Java程序运行时的数据都由JVM来管理,如何管理好这批数据就是我们日常所说的JVM调优。
可以将JVM的存储区域作如下划分(只列举了我们要分析的区域,其他区域请自行查阅资料)
我们来看一下下面的代码
我用颜色表示了上面代码在内存中存储情况。
也就是局部变量存在于栈区;
new的对象存在于堆区;
类的定义文件存在于方法区;
栈区的特点是快,所以局部变量存在于这块区域,供CPU在执行指令的时候直接操作。
采用入栈出栈的方式来进行空间的分配与释放,遇到左括号变量进栈,遇到右括号变量出栈。
因为变量出栈后,这块区域就被释放了,所以这个局部变量在右括号外也就无法使用了。这也解释了变量的作用域问题。
我们看到的StackOverFlow异常就是这块区域空间已满,由于遇到右括号就会释放空间,所以这块区域很少会因为单纯的一个方法内变量多导致这个异常。
一般是因为方法的递归调用层次太深,导致所有的局部变量都没有释放,继而出现StackOverFlow异常。
我们可以通过设置-Xss参数来制定这块空间的大小。
接下来我们重点讨论堆内存和方法区的调优
Java程序中通过new指令在堆内存分配一块空间来存储对象数据,但是并没有把这块数据删除的可编程指令,也就是我们写程序的时候只管new对象。
如果没有任何程序来清除对象的话,那么将会很快的把堆内存耗尽,报出OutOfMemoryException:heap space。
既然Java语言让我们省去了以编程的方式来管理内存可能带来的某些内存未被释放的危险,那么Java在运行时就要提供一套对不再使用的内存空间回收的方法。
我们称这个为Java的垃圾回收机制。
JVM的垃圾回收由垃圾回收器完成,可以制定不同的垃圾回收器来按不同的规则进行垃圾回收,我们可以根据需要指定最适合我们的垃圾回收器。
我们可以通过-XX参数来指定垃圾回收器。
本文中我们介绍常用分代回收算法。
把开始的图再贴一下:
我们来看最上边的导航,Heap Memory就是我们说的堆内存,存储对象,我们首先把讨论范围限定在这块区域。在此我向大家推荐一个架构学习交流圈。交流学习指导伪鑫:1253431195(里面有大量的面试题及答案)里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多
一个对象从创建到消亡的过程如下:
1. 我们用new关键字创建的对象,首先出生在伊甸园,即图中的绿色部分(eden),由于他刚出生,所以他属于新生代(Young Generation)。
2. 随着程序的运行,伊甸园中的对象将会急速增长,很快这块区域将被填满,这时要保证程序继续运行,就必须清除不再使用的对象。
也就是要进行一次新生代的垃圾回收(Young GC),首先计算那些对象(假设eden和to区)不再被使用(常用的有标记清除法,火车算法等),然后还在使用中的对象我们成为幸存者。
在新生代提供了两块幸存区域(survior spaces)供幸存者使用:from和to,假设首先这部分幸存者被安排在from区,然后eden和to区的所有对象被无情的清除,伊甸园开始继续接收新生对象。
3. 马上又到了不得不YoungGC的时间,此次检查eden和from区,幸存的对象被放入to区。
4. 不断重复2,3的过程期间一些对象经历了多次幸存游戏,我们称这些对象已经成熟,将会被移动到老年区(Old)
5. 逐渐老年区的对象也被填满,我们系统将对整个堆内存进行一次垃圾回收(full GC),这个时间明显比YoungGC的时间要长,发生的频率要小。
Java虚拟机就是不停的重复上面1,2,3,4,5的过程来维持Java世界的环境。
我们通过上面的描述可以看到,想提高性能,就要尽量减少fullGC发生的频率
上图的reserved区域代表预留区域,我们先来看如何制定这几块区域的大小:
从上往下看
-Xmx可以指定整个堆内存的最大值。
-Xms可以指定整个堆内存的最小值,也就是初始化值,系统启动时按初始化值分配堆内存空间,内存使用超过初始值会启动两边的reserve区域,超过最大值汇报OutOfMemory:heap space异常
-XX:NewSize可以指定新生代的最小值。
-XX:MaxNewSize可以指定新生代的最大值。
老年代的最小值可以用Xms-XX:NewSize计算得出,所以不需要进行特别指定。
老年代的最大值可以用Xms-XX:MaxNewSize计算得出,所以不需要进行特别指定。
接下来我们来看方法区(Perm Space)
这部分主要存储的类的定义,只有在进行类加载的时候才会往这个区域内增加内容,所以这个区域基本上不涉及到太多的变化因素。
在系统启动时和动态加载jar文件,class文件时,会增加这块区域的数据,如果Jar包过多,撑破这块区域则汇报OutOfMemory:perm space异常。
我们可以通过-XX:PermSize和-XX:MaxPermSize来指定这块区域的最小,最大值。
小编最近将收集的Java程序员的面试资料做了一些整理(如下图),免费分享给每一位学习Java的朋友。