9.垃圾回收机制和JVM

9.垃圾回收机制和JVM

1.GC(Garbage Coolection)指垃圾回收机制。没有提供相关api,手动回收,所有的内存分配和回收权限都在jvm中

 

2.System.gc():呼叫java虚拟机的垃圾回收器运行回收内存的垃圾

 

3.finalize()方法

 

  当垃圾回收器认为一个对象没有存在意义时,会回收该对象的内存,会调用该对象的finalize()方法,释放该对象在堆中占用的内存(继承于Object类)

 

4.垃圾回收的优点和原理。并考虑2种回收机制
  Java语言中一个显著的特点就是引入了垃圾回收机制,使c++程序员最头疼的内存管理的问题迎刃而解,它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。

 

5.垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收
  对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时,GC就有责任回收这些内存空间。程序员可以手动执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。

 

6.Java中的异常处理机制的简单原理和应用
  当JAVA程序违反了JAVA的语义规则时,JAVA虚拟机就会将发生的错误表示为一个异常。违反语义规则包括2种情况。一种是JAVA类库内置的语义检查。例如数组下标越界,会引发IndexOutOfBoundsException;访问null的对象时会引发NullPointerException。另一种情况就是JAVA允许程序员扩展这种语义检查,程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。所有的异常都是java.lang.Thowable的子类。

 

7.GC 如何判断对象失去引用 

  GC 判断对象引用是否可达是从 Root 根目录开始判断的,在 JVM 中可以作为 gc root 的有:JVM 栈中对象引用(主要)、方法区中引用的对象、JNI 方法引用的对象、静态区域中引用的对象(一般不回收)。对于多层结构的,则已递归的方式查找,能到达的都是不会被 GC 的对象。

 

8.java内存泄漏的原因有哪些

  首先造成 Java JVM 泄露的主要原因:JVM 未及时的对垃圾进行回收造成的;当对象失去引用且持续占有内存或无用对象的内存得不到及时释放,从而造成内存  空间的浪费称为内存泄漏。造成这种对象无法及时释放导致内存泄露的原因,可以简单  的为归分两类。

  一是基于设计方面:

    1、对应用加载数据级别判断失误,从而导致 JVM 内存分配不合理(企业单机部署应用常见到)。

    2、应用请求的常连接设计,常连接会一直占用后台资源,不能及时释放。3、数据库操作时,存在很多耗时连接,导致大量资源不能释放。4、  大量的监听设计等。

  二是基于开发方面:

    1、大量静态变量的使用(静态变量的生成周期与应用一致),如果静态引用指向的是集合或者数据,会一直占用资源。

    2、不合理的方法使用,比如 jdk6 之前的 substring 就可能导致内存泄露。

    3、数据库连接未能及时关闭,刚工作不久的同学容易忽略。

    4、单例模式使用,单例通常用来加载资源信息,但如果加载信息里有大量的集合、数组等对象,这些资源会一直驻留内存中,不易释放。

    5、在循环中创建复杂对象、  一次性读取加载大量信息到内存中,都有可能造成内存泄露。

9.GC的工作原理

  现在市场上 JVM 的实现厂家很多,不同的厂家对垃圾回收的算法实现也不同,目前主流上常的算法有标记清除、分代、复制、引用记数、增量回收等,而目前最常用的 JVM 是 SUN 公司(现被 Oracle 收购)的 HotSport,它采用的算法为分代回收。

  HotSport 将 jvm 中堆空间划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation)。年轻代和年老代是存储动态产生的对象。永久带主要是存储的是 java 的类信息,包括解析得到的方法、属性、字段等等。永久带基本不参与垃圾回收。我们这里讨论的垃圾回收主要是针对年轻代和年老代。具体如下图(网络提供)

 

  年轻代又分成 3 个部分,一个 eden 区和两个相同的 survior 区。刚开始创建的对象都是放置在 eden 区的。分成这样 3 个部分,主要是为了生命周期短的对象尽量留在年轻代。当 eden 区申请不到空间的时候,进行 minorGC,把存活的对象拷贝到 survior。年老代主要存放生命周期比较长的对象,比如缓存对象。具体 jvm 内存回收过程描述如下(可以结合上图):

  1、对象在 Eden 区完成内存分配

  2、当 Eden 区满了,再创建对象,会因为申请不到空间,触发 minorGC,进行young(eden+1survivor)区的垃圾回收

  3、minorGC 时,Eden 不能被回收的对象被放入到空的 survivor(Eden 肯定会被清空),另一个 survivor 里不能被 GC 回收的对象也会被放入这个 survivor,始终保证一个 survivor 是空的

  4、当做第 3 步的时候,如果发现 survivor 满了,则这些对象被 copy 到 old 区,或者 survivor 并没有满,但是有些对象已经足够 Old ,也被放入 Old 区XX:MaxTenuringThreshold

  5、当 Old 区被放满的之后,进行 fullGC。由于 fullGC 会造成很大的系统资源开销,所以一般情况下通过代码规范和 JVM参数设置来减少 fullGC 的调用,提高性能。

  上面只是从算法实现上来说明的,目前主流的垃圾收集器主要有三种:串行收集器、并行收集器、并发收集器。 这里有兴趣的朋友可以查一下,绝对是加分点如果在串行的垃圾回收器进行 fullGC 会造成应用的短暂中断,这是一个很危险的事情,所以一定要了解清楚各种实现方式的区别。

10.JVM的体系结构和工作原理

  首先介绍一下Java虚拟机的生存周期,然后大致介绍JVM的体系结构,最后对体系结构中的各个部分进行详细介绍。 (  首先这里澄清两个概念:JVM实例和JVM执行引擎实例,JVM实例对应了一个独立运行的java程序,而JVM执行引擎实例则对应了属于用户运行程序的线程;也就是JVM实例是进程级别,而执行引擎是线程级别的。)

 

              一、 JVM的生命周期 JVM实例的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点,既然如此,那么JVM如何知道是运行class A的main而不是运行class B的main呢?这就需要显式的告诉JVM类名,也就是我们平时运行java程序命令的由来,如java classA hello world,这里java是告诉os运行Sun java 2 SDK的java虚拟机,而classA则指出了运行JVM所需要的类名。 JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。 JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出。

 

            二、JVM的体系结构  粗略分来,JVM的内部体系结构分为三部分,分别是:类装载器(ClassLoader)子系统,运行时数据区,和执行引擎。  下面将先介绍类装载器,然后是执行引擎,最后是运行时数据区

                 

 

              1,类装载器,顾名思义,就是用来装载.class文件的。JVM的两种类装载器包括:启动类装载器和用户自定义类装载器,启动类装载器是JVM实现的一部分,用户自定义类装载器则是Java程序的一部分,必须是ClassLoader类的子类。(下面所述情况是针对Sun JDK1.2) 动类装载器:只在系统类(java API的类文件)的安装路径查找要装入的类       用户自定义类装载器:  系统类装载器:在JVM启动时创建,用来在CLASSPATH目录下查找要装入的类 其他用户自定义类装载器:这里有必要先说一下ClassLoader类的几个方法,了解它们对于了解自定义类装载器如何装载.class文件至关重要。 protected final Class defineClass(String name, byte data[], int offset, int length)  ;protected final Class defineClass(String name, byte data[], int offset, int length, ProtectionDomain protectionDomain); protected final Class findSystemClass(String name) ;protected final void resolveClass(Class c) defineClass用来将二进制class文件(新类型)导入到方法区,也就是这里指的类是用户自定义的类(也就是负责装载类)       findSystemClass通过类型的全限定名,先通过系统类装载器或者启动类装载器来装载,并返回Class对象。 ResolveClass: 让类装载器进行连接动作(包括验证,分配内存初始化,将类型中的符号引用解析为直接引用),这里涉及到java命名空间的问题,JVM保证被一个类装载器装载的类所引用的所有类都被这个类装载器装载,同一个类装载器装载的类之间可以相互访问,但是不同类装载器装载的类看不见对方,从而实现了有效的屏蔽。

 

               2, 执行引擎:负责执行字节码。方法的字节码是由 Java 虚拟机的指令序列构成的。每一条指令包含一个单字节的操作码,后面跟随 0 个或多个操作数。执行引擎执行字节码时,首先取得一个操作码,如果操作码有操作数,取得它的操作数。它执行操作码和跟随的操作数规定的动作,然后再取得下一个操作码。这个执行字节码的过程在线程完成前将一直持续。

    (一)指令集以栈为设计中心,而非以寄存器为中心 这种指令集设计如何满足Java体系的要求: 平台无关性:以栈为中心使得在只有很少register的机器上实现java更便利 compiler一般采用stack向连接优化器传递编译的中间结果,若指令集以stack为基础,则有利于运行时进行的优化工作与执行即时编译或者自适应优化的执行引擎结合,通俗的说就是使编译和运行用的数据结构统一,更有利于优化的开展。 网络移动性:class文件的紧凑性。 安全性:指令集中绝大部分操作码都指明了操作的类型。(在装载的时候使用数据流分析期进行一次性验证,而非在执行每条指令的时候进行验证,有利于提高执行速度)。

    (二)执行技术 主要的执行技术有:解释,即时编译,自适应优化、芯片级直接执行 其中解释属于第一代JVM,即时编译JIT属于第二代JVM,自适应优化(目前Sun的HotspotJVM采用这种技术)则吸取第一代JVM和第二代JVM的经验,采用两者结合的方式 自适应优化:开始对所有的代码都采取解释执行的方式,并监视代码执行情况,然后对那些经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化。若方法不再频繁使用,则取消编译过的代码,仍对其进行解释执行。

 

    3,运行时数据区:主要包括:方法区,堆,java栈,PC寄存器,本地方法栈

    (1)方法区和堆由所有线程共享

      堆:存放所有程序在运行时创建的对象

      方法区:当JVM的类装载器加载.class文件,并进行解析,把解析的类型信息放入方法区。方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。由于所有线程都共享方法区,因此它们对方法区数据的访问必须被设计为是线程安全的。

 

    (2)Java栈和PC寄存器由线程独享

      java栈:Java 栈是线程私有的。每当启动一个新线程时,Java 虚拟机都会为它分配一个Java 栈。Java 栈以帧为单位保存线程的运行状态。虚拟机只会直接对 Java 栈执行两种操作:以帧为单位的压栈或出栈。当线程调用 java 方法时,虚拟机压入一个新的栈帧到该线程的 java 栈中。当方法返回时,这个栈帧被从 java 栈中弹出并抛弃。一个栈帧包含一个 java 方法的调用状态,它存储有局部变量表、操作栈、动态链接、方法出口等信息。

       pc寄存器:一个运行中的 Java 程序,每当启动一个新线程时,都会为这个新线程创建一个自己的 PC(程序计数器)寄存器。程序计数器的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行的是一个 Java 方法, 这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是

Natvie 方法,这个计数器值则为空(Undefined)。

 

    (3)本地方法栈: 存储本地方法调用的状态。

      本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。任何本地方法接口都会使用某种本地方法栈。当线程调用Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈。然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不再在线程的 Java 栈中压入新的帧,虚拟机只是简单地动态链接并直接调用指定的本地方法。如果某个虚拟机实现的本地方法接口是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。

原文地址:https://www.cnblogs.com/swifthua/p/7695296.html