JVMJREJDK的关系
JVM(java虚拟机),将.class文件中的字节码指令进行识别并调用操作系统向上的API完成动作。JVM不仅可以运行java程序,只要是能编译成.class的文件都能运行。JRE(Java运行时环境),包含了jvm和corelib。JDK(Java开发工具包),它集成了jre和一些工具。比如javac.exe,java.exe,jar.exe等。大家都知道,要想执行java程序,需要安装jdk。JVM初识
JVM其实是一种规范,它提供可以执行Java字节码的运行时环境。不同的供应商提供这种规范的不同实现。常见的JVM实现有
Hotspotoracle官方提供TaobaoVM阿里对hotspot深底定制版J9ibm实现Jrockit号称是世界上最快的JVMopenJDKazulzingLiquidVm直接针对硬件MicrosoftJVM等JVM的内存模型
虚拟机在执行文件的时候将内存分为不同的区域,它们各司其职。
程序计数器java虚拟机栈栈堆方法区本地方法栈程序计数器/行号指示器
可以看作是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,任何一个确定的时刻,一个处理器都只执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储。所以这类内存区域为“线程私有”的内存。---《深入理解java虚拟机》它是一块较小的空间,也是唯一一个在java虚拟机规范中没有定义任何OOM的区域。正在执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Natice方法,则为空。
java虚拟机栈
也为线程私有,生命周期与线程相同,它描述的是Java方法执行的内存模型。每个方法在执行的时候都会创建一个栈,每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。它存储局部变量表、操作数栈,方法出口等信息。局部变量表的大小在编辑期间完成,所以进入执行方法时,栈的大小是确定的。
如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
更多信息可参考Java虚拟机运行时栈帧结构
**本地方法栈**和java虚拟机栈类似,只不过它表示的是Native方法。
堆
这是java虚拟机中最大的一块内存,是被所有线程共享的一块内存区域,在虚拟机启动的时候被创建。几乎所有的对象实例的内存都在这里,这也是它存在目的。Java堆还可以细分为新生代和老年代。新生代有可以分为eden伊甸区、fromservivor,toservivor。
根据虚拟机规范,Java堆可以存在物理上不连续的内存空间,就像磁盘空间只要逻辑是连续的即可。它的内存大小可以设为固定大小,也可以扩展。当前主流的虚拟机如HotPot都能按扩展实现(通过设置-Xmx和-Xms),如果堆中没有内存内存完成实例分配,而且堆无法扩展将报OOM错误。**方法区**
这也是一块共享区。存储了已被虚拟机加载的类信息、常量、静态变量、即使编辑器编辑后的代码等数据。在老版jdk,方法区也被称为永久代「HotSpot虚拟机以永久代来实现方法区」。jdk8真正开始废弃永久代,而使用元空间(Metaspace)。
**当然上面的区分是JVM规范,每个虚拟机实现可能有不同的划分。有时候,我们可以粗略的把区域分为堆区和栈区。这也是程序员最关心的2个部分。**
java内存模型,JMM(javamemorymodel)
JMM作用Java虚拟机规范中定义了Java内存模型(JavaMemoryModel,JMM),用于屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发效果,JMM规范了Java虚拟机与计算机内存是如何协同工作的:规定了一个线程如何和何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。详情参考Java内存模型(JMM)总结
**JVMGC**
**如何寻找到垃圾**
主要有引用计数和根可达分析法。
引用计数给对象中添加引用计数器,每次被引用都+1,当引用失效就-1,当gc的时候,引用为0,就会被当作垃圾回收掉。
优点:实现简单,判断效率高。
缺点:很难解决对象之间循环引用的问题。也就是因为这个缺点,主流的jvm不选用这种计数法。
根可达rootsearching就是从“GCRoots”对象作为起点开始往下搜索,搜索所有的“叶子”。那些没有根的叶子就是垃圾。GCRoots对象包括以下几种:
虚拟机栈中引用的对象方法区中静态属性和常量引用的对象本地方法栈中引用的对象找到垃圾如何清理?也就是垃圾回收的算法
标记-清除(Mark-Sweep)就如同它的名字,分为2个阶段,先标记后清除。这也是最基础的算法,其他算法都在它的基础上优化而来。
优点:
1.存活对象比较多的时候效率高(老生代)。2.算法简单。
缺点:
2遍扫描,效率偏低。第1遍标记有用的,第2遍清除没用的。容易产生碎片。
复制(Copying)为了提高效率,复制算法出现了。它将内存分为2块,每次只使用其中的一块,这块用完了,就把其中存活的对象复制到另一块。然后把这块内存的对象全部清掉。优点:
适用于存活对象比较少的情况(新生代),只扫描一次,效率提高,没有碎片。
缺点:
1.空间浪费;2.移动复制对象,需要调整对象引用。
标记-整理算法(Mark-Compact)如果存活对象比较多,移动对象效率变低。为了不浪费另一块内存。程序在运行过程中,很难有%对象存活的极端情况。引出了标记整理算法。就是在标记-清除算法的基础上多了一步整理。先把垃圾对象标记出来,然后把所有存活的对象移到内存中一块连续的内存,然后把这块内存以外的部分清除掉。
优点:
不会产生碎片,方便对象分配,不会产生内存减半。
缺点:
扫描2次,需要移动对象。
有哪些垃圾回收器
上面介绍了垃圾回收的算法理论,垃圾回收器就是它们的具体实现。java虚拟机规范中对垃圾回收器如何实现并没有任何规定。所以不同厂商,不同版本的JVM提供的垃圾回收器也不相同。它们会提供参数供用户自定义自己的垃圾回收器组合。下面是HotSpot虚拟机的垃圾回收器。来源于「深入理解java虚拟机」
3-5图中,如果俩个虚拟机之间有连线,说明它们可以搭配使用。没有最好的垃圾回收器,只有最适合自己的垃圾回收器。JDK1.8默认垃圾回收ParallelScavenge+ParallelOld
Serial单线程执行,只会使用一个CPU或一条收集线程区完成垃圾收集工作。它执行垃圾回收的时候,必须暂停其他工作线程,直到收集结束。其使用的是复制算法。内存要求几十兆。应用场景:不需要线程交互的开销,可以获得最高效的收集效率,适用于限定单个CPU的环境。显式的使用设置参数:-XX:+UseSerialGCParNew是Serial的多线程版,除了使用的是多线程外,其他和Serial基本一样。这2种垃圾回收器也共用了大量代码。应用场景:在Server模式下,ParNew收集器是一个非常重要的收集器,因为除Serial外,目前只有它能与CMS收集器配合工作;设置参数-XX:+UseParNewGC:强制指定使用ParNew;-XX:ParallelGCThreads:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;-XX:+UseConcMarkSweepGC:指定使用CMS后,会默认使用ParNew作为新生代收集器;ParallelScavenge也是一个新生代收集器,用的复制算法。其他回收器