zoukankan      html  css  js  c++  java
  • JVM

    Java虚拟机

    jvm之java类加载机制

    一、类加载过程

      当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化。

    二、类加载时机

    1创建类的实例,也就是new一个对象

    2访问某个类或接口的静态变量,或者对该静态变量赋值

    3调用类的静态方法

    4反射(Class.forName("com.lyj.load")

    5初始化一个类的子类(会首先初始化子类的父类)

    6JVM启动时标明的启动类,即文件名和类名相同的那个类 

      除此之外,下面几种情形需要特别指出:对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。反之,如果final类型的静态Field的值不能在编译时确定下来,则必须等到运行时才可以确定该变量的值,如果通过该类来访问它的静态变量,则会导致该类被初始化。

    三、类加载器

      类加载器负责加载所有的类,其为所有被载入内存中的类生成一个java.lang.Class实例对象。一旦一个类被加载如JVM中,同一个类就不会被再次载入了。正如一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识。在Java中,一个类用其全限定类名(包括包名和类名)作为标识;但在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。例如,如果在pg的包中有一个名为Person的类,被类加载器ClassLoader的实例kl负责加载,则该Person类对应的Class对象在JVM中表示为(Person.pg.kl)。这意味着两个类加载器加载的同名类:(Person.pg.kl)和(Person.pg.kl2)是不同的、它们所加载的类也是完全不同、互不兼容的。

     JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:

    1)根类加载器(bootstrap class loader:它用来加载 Java 的核心类,是用原生代码来实现的,并不继承自 java.lang.ClassLoader(负责加载$JAVA_HOMEjre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类)。

    2)扩展类加载器(extensions class loader):它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null

    3)系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader

    四、类加载机制

    1.JVM的类加载机制主要有如下3种。

    全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。

    双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。

    缓存机制。缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为很么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

    2.这里说明一下双亲委派机制:

    jvm之java内存模型

    一、JVM内存结构图(JDK1.6)

    多线程共享内存区域:方法区、堆。

    每一个线程独享内存:java栈、本地方法栈、程序计数器。

     

    1程序计数器:较小的内存空间,当前线程执行的字节码的行号指示器;各线程之间独立存储,互不影响;

    2java 栈:线程私有,生命周期和线程,每个方法在执行的同时都会创建一个 栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程;栈里面存放着各种基本数据类型和对象的引用;

    3本地方法栈:本地方法栈保存的是native方法的信息,当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法;

    4堆:Java堆是程序员需要重点关注的一块区域,因为涉及到内存的分配(new关键字,反射等)与回收(回收算法,收集器等)

    5方法区:也叫永久区,用于存储已经被虚拟机加载的类信息,常量("zdy","123"),静态变量(static变量)等数据。(jdk1.8已经将方法区去掉了,将方法区移动到直接内存)

    6、运行时常量池:运行时常量池是方法区的一部分,用于存放编译期生成的各种字面("zdy","123")和符号引用。

    7直接内存:不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;

      1)如果使用了NIO,这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;

      2) 这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;

    二、JVM内存中按照线程共享和线程私有划分结构图(JDK1.6)

     

    堆和栈的区别

    1)堆和栈功能上的区别

      以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(intshortlongbytefloatdoublebooleanchar等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;

    而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中;

    2)堆和栈在线程共享和线程私有区别

      栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。

    堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

    3)空间大小

      栈的内存要远远小于堆内存,栈的深度是有限制的,如果递归没有及时跳出,很可能发生StackOverFlowError问题。你可以通过-Xss选项设置栈内存的大小(这个参数是设定单个线程的栈空间)-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值

    三、JVM内存运行时结构图

    每一个线程独享的内存区域有:程序计数器、java栈、本地方法栈

    线程共享区域:堆内存、方法区(JDK1.8已经去掉了方法区)

    四、线程安全本质

      线程安全本质是由于多个线程对同一个堆内存中的Count变量操作的时候,每一个线程会在线程内部创建这个堆内存Count变量的副本,线程内所有的操作都是对这个Count副本进行操作。这时如果其他线程操作这个堆内存Count变量,改变了Count值对这个线程是不可见的。当前线程操作完Count变量将值从副本空间写到主内存(堆内存)的时候就会覆盖其他线程操作Count变量的结果,引发线程不安全问题。

    JDK1.6JDK1.7JDK1.8不同版本JVM内存模型区别

    相对于jdk1.6,jDK1.7将运行时常量池方法区移除到堆内存。

    相对于JDK1.6,JDK1.8直接将方法区去掉,在本地内存中新增元数据空间。运行时常量池仍然在堆中。元数据区存放类加载信息。

    JDK1.8为什么要移除方法区

    1)永久代来存储类信息、常量、静态变量等数据不是个好主意, 很容易遇到内存溢出的问题.JDK8的实现中将类的元数据放入 native memory, 将字符串池和类的静态变量放入java堆中. 可以使用MaxMetaspaceSize对元数据区大小进行调整;

    2)对永久代进行调优是很困难的,同时将元空间与堆的垃圾回收进行了隔离,避免永久代引发的Full GCOOM等问题;

    JVM内存参数设定

    -Xms 初始堆内存大小

    -Xmx 最大堆内存大小

    -Xss 单个线程栈大小

    -XX:NewSize 初始新生代堆大小

    -XX:MaxNewSize 生代最大堆大小

    -XX:PermSize 方法区初始大小(JDK1.7及以前)

    -XX:MaxPermSize 方法区最大大小(JDK1.7及以前)

    -XX:MetaspaceSize 元数据区初始值(JDK1.8)

    -XX:MaxMetaspaceSize 元数据区最大值(JDK1.8)

    jvm之垃圾回收

    1如何判断对象是否死亡(两种方法)。

    2简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。

    3如何判断一个常量是废弃常量

    4如何判断一个类是无用的类

    5垃圾收集有哪些算法,各自的特点?

    6HotSpot为什么要分为新生代和老年代?

    7常见的垃圾回收器有那些?

    8介绍一下CMS,G1收集器。

    9Minor GcFull GC 有什么不同呢?

    1 揭开JVM内存分配与回收的神秘面纱

    java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是堆内存中对象的分配与回收。

    JDK1.8之前的堆内存示意图:

    从上图可以看出堆内存的分为新生代、老年代和永久代。新生代又被进一步分为:Eden 区+Survior1 区+Survior2 区。值得注意的是,在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。

    1.1 对象优先在eden区分配

    1.2 大对象直接进入老年代

    大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。为什么要这样呢?为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。

    1.3 长期存活的对象将进入老年代

    1.4 动态对象年龄判定

    2 对象已经死亡?

    堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断那些对象已经死亡(即不能再被任何途径使用的对象)。

    2.1 引用计数法

    这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。

    2.2 可达性分析算法

    这个算法的基本思想就是通过一系列的称为 GC Roots 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。

    2.3 再谈引用

    JDK1.2以后,Java对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)

    1.强引用必不可少的生活用品

    2.软引用可有可的生活用品

    3.弱引用可有可的生活用品

    4.虚引用虚引用主要用来跟踪对象被垃圾回收的活动

    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。 虚引用必须和引用队列(ReferenceQueue)联合使用。

    2.4 不可达的对象并非“非死不可”

    即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。

    2.5 如何判断一个常量是废弃常量

    运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?

    假如在常量池中存在字符串abc”,如果当前没有任何String对象引用该字符串常量的话,就说明常量 “abc” 就是废弃常量,如果这时发生内存回收的话而且有必要的话,”abc” 就会被系统清理出常量池。

    JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。

    2.6 如何判断一个类是无用的类

    判定一个常量是否是“废弃常量”比较简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。类需要同时满足下面3个条件才能算是 “无用的类” :

    1、该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。

    2、加载该类的 ClassLoader 已经被回收。

    3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

    虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。

    3 垃圾收集算法

    4 垃圾收集器

     

    4.1 Serial(串行)收集器、新生代采用复制算法,老年代采用标记-整理算法。

    4.2 ParNew收集器

    ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。

    新生代采用复制算法,老年代采用标记-整理算法。

    4.3 Parallel Scavenge收集器

    Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。

    新生代采用复制算法,老年代采用标记-整理算法。

    4.4.Serial Old收集器

    4.5 Parallel Old收集器

    4.6 CMS收集器

    CMSConcurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。

    CMSConcurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

    CMS收集器是一种 “标记-清除”算法实现的整个过程分为四个步骤:

    1初始标记: 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ;

    2并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。

    3重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短

    4并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫。

    主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:对CPU资源敏感;无法处理浮动垃圾;它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。

    4.7 G1收集器

    G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.

    被视为JDK1.7HotSpot虚拟机的一个重要进化特征。它具备一下特点:

    1并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPUCPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

    2分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。

    3空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。

    4可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内。

    G1收集器的运作大致分为以下几个步骤:

    初始标记

    并发标记

    最终标记

    筛选回收

    G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

  • 相关阅读:
    【转】PHP error_reporting() 错误控制函数功能详解
    第八讲_图像问答Image Question Answering
    C中的继承和多态
    第七讲_图像描述(图说)Image Captioning
    TensorFlow 之 高层封装slim,tflearn,keras
    第六讲_图像分割Image Segmentation
    第五讲_图像识别之图像检测Image Detection
    Tensorflow 之 TensorBoard可视化Graph和Embeddings
    第四讲_图像识别之图像分类Image Classification
    理解Neural Style
  • 原文地址:https://www.cnblogs.com/guoziyi/p/12143857.html
Copyright © 2011-2022 走看看