java 和 JVM

C++和Java的区别

  • 指针:java中不存在指针的概念,编程者没法直接经过指针来直接访问内存,有利于维护java程序的安全
  • 多重继承:C++支持多重继承,java不支持多重继承,可是容许一个类继承多个接口来实现多重继承的问题
  • 数据类型和类:java是彻底面向对象的语言,全部的函数和变量必须是类的一部分,而C++中容许将函数和变量设置为全局,兼具面向过程和面向对象的特色
  • 内存管理:Java中由系统进行自动的内存管理和回收,C++中须要程序员手动释放内存资源。当Java中的一个对象不会再用到的时候,无用内存会给它贴上标签以示删除,Java的GC过程是以线程的形式在后台运行,利用空闲时间工做
  • 操做符重载:Java不支持操做符重载,C++支持操做符重载,也是C++一个突出特征
  • 预处理功能:Java不支持预处理,C++有一个预编译阶段,Java没有预处理器,但它提供了import与C++预处理器具备相似功能
  • Java不支持缺省函数参数
  • 字符串:字符串变量
  • 类型转换:C++中有数据类型隐含转换的机制,Java中须要限时强制类型转换
  • 异常:Java中异常机制用于捕获例外事件,加强系统的容错能力java

  • Java为解释型语言,程序源代码经过Java编译器编译成字节码,而后由jvm解释执行,C++为编译型语言,源代码经过编译连接后直接生成可执行的二进制代码,可直接执行,所以java的执行速度比C++要慢,可是Java能够跨平台
  • Java具备平台无关性,对每种数据类型都分配了固定长度的空间,可是C++不一样,在不一样的平台上会分配不一样的字节数linux

Java跨平台的缘由

  • 第一点: 咱们一般将CPU处理器和操做系统的总体称之为平台,不一样的CPU中可能使用不一样的指令集(指令集是CPU中用于计算和控制计算机系统的一套指令的集合),而不一样操做系统支持不一样CPU的指令集
  • C语言的编译过程:windows下经过VS编译成exe文件,Linux下经过gcc编译成elf文件,可是window所编译的exe是不能在linux上运行的,所以结论是:编译器是与平台相关的,编译后的文件也是与平台相关的,咱们所说的跨平台是指编译后的文件跨平台,而不是源程序跨平台,这点要注意。
  • 对于Java而言,源程序为.java文件,经过与平台无关的编译器编译成与平台无关的中间码,也就是.class文件,中间码再由解释器(也就是jvm)解释执行,注意解释器是与平台相关的,也就是不一样平台须要不一样的解释器

JVM:Java虚拟机

http://www.javashuo.com/article/p-nhfhibef-cq.html程序员

1. 基本特性

  • JRE由Java api和JVM组成,JVM经过classloader来加载类应用,经过JavaAPI来执行
  • 基于栈结构的虚拟机
  • 符号引用:除了基本类型外全部的Java类型都是经过符号引用来取得关联,而不是经过显式内存地址的引用
  • 垃圾回收机制:显式建立,经过GC自动回收
  • 明确界定基本类型字节长度保证平台的无关性
  • 网络字节序: 基于大端的字节序

2. Java程序的执行过程


类加载将Java字节码载入到运行时数据区,执行引擎负责Java字节码执行;算法

3. 类加载

Java提供了动态加载的特性,只有在运行时第一次遇到类时才会去加载和连接,而非在编译时加载它。JVM的类加载器负责类的动态加载过程。Java类加载器的特色以下:编程

  • 层次结构:Java的类加载器按是父子关系的层次结构组织的。Boostrap类加载器处于层次结构的顶层,是全部类加载器的父类。segmentfault

  • 委派模式:基于类加载器的层次组织结构,类加载器之间是能够进行委派的。当一个类须要被加载,会先去请求父加载器判断该类是否已经被加载。若是父类加器已加载了该类,那它就能够直接使用而无需再次加载。若是还没有加载,才须要当前类加载器来加载此类。
  • 可见性限制:子类加载器能够从父类加载器中获取类,反之则不行。
  • 不能卸载: 类加载器能够载入类却不能卸载它。可是能够经过删除类加载器的方式卸载类。windows

每一个类加载器都有本身的空间,用于存储其加载的类信息。当类加载器须要加载一个类时,它经过FQCN)(Fully Quanlified Class Name: 全限定类名)的方式先在本身的存储空间中检测此类是否已存在。在JVM中,即使具备相同FQCN的类,若是出如今了两个不一样的类加载器空间中,它们也会被认为是不一样的。存在于不一样的空间意味着类是由不一样的加载器加载的。
279698375-5700c7e49cda5_articlex.pngapi

当JVM请示类加载器加载一个类时,加载器老是按照从类加载器缓存、父类加载器以及本身加载器的顺序查找和加载类。也就是说加载器会先从缓存中判断此类是否已存在,若是不存在就请示父类加载器判断是否存在,若是直到Bootstrap类加载器都不存在该类,那么当前类加载器就会从文件系统中找到类文件进行加载。数组

  • Bootstrap加载器:Bootstrap加载器在运行JVM时建立,用于加载Java APIs,包括Object类。不像其余的类加载器由Java代码实现,Bootstrap加载器是由native代码实现的。
  • 扩展加载器(Extension class loader):扩展加载器用于加载除基本Java APIs之外扩展类。也用于加载各类安全扩展功能。
  • 系统加载器(System class loader):若是说Bootstrap和Extension加载器用于加载JVM运行时组件,那么系统加载器加载的则是应用程序相关的类。它会加载用户指定的CLASSPATH里的类。
  • 用户自定义加载器:这个是由用户的程序代码建立的类加载器。

像Web应用服务器(WAS: Web Application Server)等框架经过使用用户自定义加载器使Web应用和企业级应用能够隔离开在各自的类加载空间独自运行。也就是说能够经过类加载器的委派模式来保证应用的独立性。不一样的WAS在自定义类加载器时会有略微不一样,但都不外乎使用加载器的层次结构原理。缓存

若是一个类加载器发现了一个未加载的类,则该类的加载和连接过程以下图

类加载过程.png

每一步的具体描述以下:

  • 加载(Loading): 从文件中获取类并载入到JVM内存空间。
  • 验证(Verifying): 验证载入的类是否符合Java语言规范和JVM规范。在类加载流程的测试过程当中,这一步是最为复杂且耗时最长的部分。大部分JVM TCK的测试用例都用于检测对于给定的错误的类文件是否能获得相应的验证错误信息。
  • 准备(Preparing): 根据内存需求准备相应的数据结构,并分别描述出类中定义的字段、方法以及实现的接口信息。
  • 解析(Resolving): 把类常量池中全部的符号引用转为直接引用。
  • 初始化(Initializing): 为类的变量初始化合适的值。执行静态初始化域,并为静态字段初始化相应的值。

4. 运行时数据区

运行时数据区.png

运行时数据区是JVM运行时操做系统分配的内存区域,运行时数据区可分为6部分,即:为每一个线程分别建立的PC寄存器JVM栈本地方法栈,和被全部线程共用的数据堆方法区,和运行时常量池

  • PC寄存器:每个线程都会有一个Program counter寄存器,随着线程启动而建立,其中存放要执行的JVM指令地址;
  • JVM栈:每个线程都会有一个JVM栈,随着线程启动而建立,其中存储的数据元素为栈帧,在JVM中一旦有方法执行,JVM都会为之建立一个栈帧,并添加到当前现成的JVM栈中,当方法运行结束后,栈帧也会随之移除。栈帧中保存着对本地变量数组,操做数栈和属于当前运行方法的运行时常量池的引用
  • 本地方法栈:为非Java编写的本地程序定义的栈空间,也就是说它基本上是用于经过JNI(Java Native Interface)方式调用和执行的C/C++代码。根据具体状况,C栈或C++栈将会被建立。

  • 方法区:方法区是被全部线程共用的内存空间,在JVM启动时建立。它存储了运行时常量池、字段和方法信息、静态变量以及被JVM载入的全部类和接口的方法的字节码。不一样的JVM提供者在实现方法区时会一般有不一样的形式。在Oracle的Hotspot JVM里方法区被称为Permanent Area(永久区)或Permanent Generation(PermGen, 永久代)。JVM规范并对方法区的垃圾回收未作强制限定,所以对于JVM实现者来讲,方法区的垃圾回收是可选操做。
  • 运行时常量池:一个存储了类文件格式中的常量池表的内存空间。这部分空间虽然存在于方法区内,但却在JVM操做中扮演着举足轻重的角色,所以JVM规范单独把这一部分拿出来描述。除了每一个类或接口中定义的常量,它还包含了全部对方法和字段的引用。所以当须要一个方法或字段时,JVM经过运行时常量池中的信息从内存空间中来查找其相应的实际地址。
  • 数据堆:堆中存储着全部的类实例或对象,而且也是垃圾回收的目标场所。当涉及到JVM性能优化时,一般也会说起到数据堆空间的大小设置。JVM提供者能够决定划分堆空间或者不执行垃圾回收。

5. 执行引擎

JVM经过类加载器把字节码载入运行时数据区是由执行引擎执行的。执行引擎以指令为单位读入Java字节码,就像CPU一个接一个的执行机器命令同样。每一个字节码命令包含一字节的操做码和可选的操做数。执行引擎读取一个指令并执行相应的操做数,而后去读取并执行下一条指令。

尽管如此,Java字节码仍是以一种能够理解的语言编写的,而不像那些机器直接执行的没法读懂的语言。因此JVM的执行引擎必需要把字节码转换为能被机器执行的语言指令。执行引擎有两种经常使用的方法来完成这一工做:

  • 解释器(Interpreter):读取、解释并逐一执行每一条字节码指令。由于解释器逐一解释和执行指令,所以它可以快速的解释每个字节码,但对解释结果的执行速度较慢。全部的解释性语言都有相似的缺点。叫作字节码的语言人本质上就像一个解释器同样运行。
  • 即时编译器(JIT: Just-In-Time):即时编译器的引入用来弥补解释器的不足。执行引擎先以解释器的方式运行,而后在合适的时机,即时编译器把整修字节码编译成本地代码。而后执行引擎就再也不解释方法的执行而是经过使用本地代码直接执行。执行本地代码较逐一解释执行每条指令在速度上有较大的提高,而且经过对本地代码的缓存,编译后的代码能具备更快的执行速度。

然而,即时编译器在编译代码时比逐一解释和执行每条指令更耗时,因此若是代码只会被执行一次,解释执行可能会具备更好的性能。因此JVM经过检查方法的执行频率,而后只对达到必定频率的方法才会作即时编译。

执行引擎.png

Java垃圾回收机制

垃圾回收算法和JVM垃圾回收综述

深刻解析Java垃圾回收机制

1. 哪些内存须要回收?

  • 引用计数法:判断是否还须要使用,最简单方法是经过目前是否有引用指向这个对象,若是没有说明这个对象就不会再使用了,这种经过引用是否存在的方法叫作引用计数法,可是存在一个问题没法解决就是对象循环引用问题
  • 可达性分析:这个算法的基本思路就是经过一系列的称为“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来讲,就是从GC Roots到这个对象不可达)时,则证实此对象是不可用的。

    在Java语言中,可做为GC Roots的对象包括下面几种:

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中常量引用的对象。
    • 本地方法栈中JNI(即通常说的Native方法)引用的对象。

2 如何回收?

  • step 1: marking 标记
    第一步是标记,也就是找到那些须要回收的对象和肯定哪些对象不须要回收,全部堆中的对象都会扫描一遍,这一般是一个很耗时的过程

Mark.PNG

  • step2 : normal deletion
    垃圾收集器清除掉标记出来的对象区域,简单的清除带来的问题是产生大量的不连续的内存碎片,空间碎片太多可能会致使在程序运行过程当中须要分配较大对象时,没法找到足够的连续内存而提早触发一次垃圾回收

  • step 2 :improve -- deletion with compacting 压缩整理
    因为简单的清除可能会存在碎片的问题,因此又出现了压缩清除的方法,也就是先清除须要回收的对象,而后再对内存进行压缩操做,将内存分红可用和不可用两大部分。
    compacting.PNG

3 分代回收

  • 为何须要分代回收:一个程序中大部分对象都是短命的,所以为了增大GC的效率,将JVM堆分代,分为新生代,老年代和永久代。

分代.PNG

  • 新生代:全部new出来的新对象都在新生代,新生代这部份内存满了以后,就会发起一次GC事件,这种发生在新生代的垃圾回收称为Minor collections,这种收集相对比较快。

  • 老年代:老年代来存储存活事件比较长的对象,通常来讲,咱们会给新生代的对象限定一个存活的时间,当达到这个时间尚未被收集的时候就会被移动到老年代中。老年代区域的垃圾收集叫作major garbage collection。一般Major garbage collection都相对比较慢,由于老年代的收集包括了对全部对象的收集,也就是同时须要收集新生代和老年代的对象。
  • 永久代:The Permanent generation contains metadata required by the JVM to describe the classes and methods used in the application. The permanent generation is populated by the JVM at runtime based on classes in use by the application. In addition, Java SE library classes and methods may be stored here.
    分代2.PNG

5 分代回收的过程

  • 第一步 全部new出来的对象都会最早分配到新生代区域中,两个survivor区域初始化是为空的

第1步.PNG

  • 第二步,当eden区域满了以后,就引起一次 minor garbage collection

第2步.PNG

  • 第三步,当在minor garbage collection,存活下来的对象就会被移动到S0survivor区域

第3步.PNG

  • 第四步,而后当eden区域又填满的时候,又会发生下一次的垃圾回收,存活的对象会被移动到survivor区域而未存活对象会被直接删除。可是,不一样的是,在此次的垃圾回收中,存活对象和以前的survivor中的对象都会被移动到s1中。一旦全部对象都被移动到s1中,那么s2中的对象就会被清除,仔细观察图中的对象,数字表示经历的垃圾收集的次数。目前咱们已经有不一样的年龄对象了。

第4步.PNG

  • 第五步,下一次垃圾回收的时候,又会重复上次的步骤,清除须要回收的对象,而且又切换一次survivor区域,全部存活的对象都被移动至s0。eden和s1区域被清除。

第5步.PNG

  • 第六步,重复以上步骤,并记录对象的年龄,当有对象的年龄到达必定的阈值的时候,就将新生代中的对象移动到老年代中。在本例中,这个阈值为8.

第6步.PNG

  • 第七步,接下来垃圾收集器就会重复以上步骤,不断的进行对象的清除和年代的移动

  • 最后,咱们观察上述过程能够发现,大部分的垃圾收集过程都是在新生代进行的,直到老年代中的内存不够用了才会发起一次 major GC,会进行标记和整理压缩。

    Java中对象的生命周期

  1. 建立阶段(created)
    为对象分配存储空间,开始构造对象,从父类到子类对static成员进行初始化,父类成员变量按照顺序初始化,递归调用父类的构造方法,子类成员变量按照顺序初始化,子类构造方法调用,一旦对象被建立,并有某个引用指向它,这个对象的状态就切换到了应用阶段(In Use)

  2. 应用阶段(in use)
    对象至少被一个强引用持有而且对象在做用域内
  3. 不可见阶段(Invisible)
    程序自己再也不持有该对象的任何强引用,可是这些引用可能还存在着;通常具体是指程序的执行已经超过该对象的做用域了
  4. 不可达阶段(Unreachable)
    对象处于不可达阶段是指该对象再也不被任何强引用所持有。
    与“不可见阶段”相比,“不可见阶段”是指程序再也不持有该对象的任何强引用,这种状况下,该对象仍可能被JVM等系统下的某些已装载的静态变量或线程或JNI等强引用持有着,这些特殊的强引用被称为”GC root”。存在着这些GC root会致使对象的内存泄露状况,没法被回收。
  5. 收集阶段(Collected)
    当垃圾回收器发现该对象已经处于“不可达阶段”而且垃圾回收器已经对该对象的内存空间从新分配作好准备时,则对象进入了“收集阶段”。若是该对象已经重写了finalize()方法,则会去执行该方法的终端操做。
    这里要特别说明一下:不要重载finazlie()方法!缘由有两点:
    • 会影响JVM的对象分配与回收速度
      在分配该对象时,JVM须要在垃圾回收器上注册该对象,以便在回收时可以执行该重载方法;在该方法的执行时须要消耗CPU时间且在执行完该方法后才会从新执行回收操做,即至少须要垃圾回收器对该对象执行两次GC。
    • 可能形成该对象的再次“复活”
      在finalize()方法中,若是有其它的强引用再次持有该对象,则会致使对象的状态由“收集阶段”又从新变为“应用阶段”。这个已经破坏了Java对象的生命周期进程,且“复活”的对象不利用后续的代码管理。
  6. 终结阶段
    当对象执行完finalize()方法后仍然处于不可达状态时,则该对象进入终结阶段。在该阶段是等待垃圾回收器对该对象空间进行回收。
  7. 对象空间的从新分配 垃圾回收器对该对象的所占用的内存空间进行回收或者再分配了,则该对象完全消失了,称之为“对象空间从新分配阶段”。

相关文章
相关标签/搜索