精华内容
下载资源
问答
  • 整理好了JVM虚拟机的原理,适合初学者理论,也适合找工作面试的。内附运行图,思维导图,文字详解,并有相关算法的详解的方式供大家理解,请大家多多支持!
  • JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟...下面通过本文给大家介绍jvm原理与调优相关知识,感兴趣的朋友一起学习吧
  • JVM原理详解

    千人学习 2018-12-20 18:34:03
    1,通俗讲解JVM运行原理,各组成部分的作用,包括栈、堆等 2,使用visio视图,直观介绍JVM组成 3,介绍线程安全产生的原因 4,面试题中存在的些许JVM题目讲解
  • JVM原理

    2018-12-31 19:01:32
    · JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。  JAVA之所以是跨平台的语言,就是因为JVM...

    ·  JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
      JAVA之所以是跨平台的语言,就是因为JVM。在不同的平台上有相应的JVM,这样我们写的java语言是相同的,在不同的操作系统中就可以游刃有余。实质上,JVM是JAVA跨平台的重要核心。

    一、JVM的概念

    ·  JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

    二、JVM的原理

    ·  JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。

    1.JVM生命周期

    ·  JVM伴随Java程序的开始而开始,程序的结束而停止。一个Java程序会开启一个JVM进程,一台计算机可以运行多个程序们也就可以运行多个JVM。
    JVM将线程分为两种,守护线程和普通线程。守护线程是JVM自己使用的线程。普通线程一般是java程序的线程,只要JVM中有普通线程在执行,那么JVM就不会停止。

    2.内存模型的组成
    ·  JVM内存模型主要有堆内存,方法区,程序计数器,虚拟机栈和本地方法栈组成。具体在这里插入图片描述
      
    ·  其中,堆和方法区是所有线程共有的,而虚拟机栈,本地方法栈和程序计数器则是线程私有的。
      下面,我们具体介绍这几个模块
      
      堆内存 
      被所有线程共享,在虚拟机启动时创建,用来存放对象实例,几乎所有的对象实例都在这里分配内存。对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。
      Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;新生代又有Eden空间、From Survivor空间、To Survivor空间三部分。
      Java 堆不需要连续内存,并且可以通过动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
      在这里插入图片描述
      方法区
      方法区和java堆一样,是各个线程共享的区域,他用于存储已被虚拟机加载的类信息,常量,静态常量,及时编译后的代码等数据。
      由于程序中所有的线程都共享一个方法区,所以访问方法区的信息必须确保线程是安全的。如果有两个线程同时去加载一个类,那么只有一个线程被允许去加载这个类,另一个必须等待。
      程序运行时,方法区的大小是可以改变的,程序在运行时可以扩展。同时方法去里面的对象可以被垃圾回收,但条件非常苛刻,必须在该类没有任何引用的情况下才能被GC回收。
      
      程序计数器
      程序计数器是可以当做java执行(一个线程)的指示器,执行下一条指令,选择哪一条路径,是否循环操作等都是依赖这个指示器来执行的。java虚拟机的多线程是通过线程。

    ·  轮流切换并分配处理器执行时间来实现的,在任何一个确定的时刻,一个处理器或内核都只会执行一条线程的指令,因此为了线程能够恢复到正确的执行位置,每条线程都要有一个独立的程序计数器,并且不同线程中的程序计数器互不影响,也就是说 程序计数器是线程私有的。

    ·  如果线程正在执行一个java方法 则这个程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果是本地方法(native)则计数器值为空。

    ·  程序计数器是唯一一个在虚拟机中没有内存溢出的区域。
      虚拟机栈
      每个java方法在执行时,会创建一个“栈帧(stack frame)”,栈帧的结构分为“局部变量表、操作数栈、动态链接、方法出口”几个部分(具体的作用会在字节码执行引擎章节中讲到,这里只需要了解栈帧是一个方法执行时所需要数据的结构)。我们常说的“堆内存、栈内存”中的“栈内存”指的便是虚拟机栈,确切地说,指的是虚拟机栈的栈帧中的局部变量表,因为这里存放了一个方法的所有局部变量。
      当Java虚拟机运行程序时。每当一个新的线程被创建时。Java 虚拟机都会分配一个虚拟机栈,Java虚拟机栈是以帧为单位来保存线程的运行状态。Java栈只会有两种操作:以帧为单位进行压栈跟出栈。 某个线程正在执行的方法称为当前方法,以此类推出当前类,当前常量池(每一个方法都有自己唯一的常量池)
      每当线程调用当前方法时,都会将,新栈压入,成为当前帧。jvm会使用它来存储我们的形参,局部变量,中间运行结果等。
    执行结束返回的方式会有两种,一种是 retuen正常返回,另外一种是通过异常来返回。无论哪种方式虚拟机都会释放弹出当前帧。这样上一个方法就成为了当前帧 。之前我们提过Java栈中的数据都是线程私有的,因此线程都不能访问另一个线程的数据。因此我们在此无需考虑多线程。
       
       本地方法栈
       JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。在JVM规范中,并没有对本地方法栈的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。
     
     3.类加载器
     JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。
      
      加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
      这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
      
      验证:确保被加载类的正确性,即确保被加载的类符合javac编译的规范
      
      准备:为类的静态变量分配内存,并初始化为默认值
      
      解析:将类中的符号引用转化为直接引用
      
      注:符号引用即一个Java源文件在被编译时,在不清楚被引用类实际内存地址的情况下,会使用能唯一识别并定位到目标的符号来代替。如A类引用了B类,编译时A并不知道B类实际的内存地址,故可以使用能唯一识别B的符号来代替。而当类加载时,编译后的.class文件实际已被调入内存,可知道A,B类的实际内存地址,当引用的目标已被加载入内存,则此时的引用为直接引用。

    ·  初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。
      初始化阶段是执行类构造器方法的过程。方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。虚拟机会保证方法执行之前,父类的方法已经执行完毕。p.s: 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。

    展开全文
  • JVM原理讲解和调优

    2018-04-02 17:24:35
    JVM原理讲解和调优 JVM原理讲解和调优 JVM原理讲解和调优
  • 浅谈jvm原理

    2017-12-08 14:50:28
    浅谈jvm原理(概念,运行机制,基本架构,运行时数据区,类加载系统,垃圾回收算法,垃圾回收策略,gc参数,调优策略)
  • jvm原理及调优

    2018-03-03 21:29:24
    一、JVM概述 二、JVM的体系结构 三、JVM运行时数据区 3.1 PC寄存器 3.2 JVM栈 3.3 堆(Heap) 3.4 方法区域 3.5 运行时常量池 3.6本地方法堆栈 四、Jvm堆 五、Jvm调优
  • 关于JVM虚拟机技术的一个非常不错视频教程,可以给想学习JVM原理、内存模型、性能调优等JVM技术的开发人员一个好学习指导。
  • 深入解析Java jvm原理

    2016-08-24 15:04:55
    深入解析jvm原理 Performance Engineering Performance Requirements Application Performance Analysis
  • JVM原理-超详细总结

    千次阅读 2020-11-29 16:52:57
    JVM概念 JVM是java的核心和基础在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。java编译器只要面向JVM,生成JVM能...

    JVM概念

    • JVM是java的核心和基础在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。

    JVM的生命周期:

    1. JVM的诞生:当启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void main(String[] args)函数的class都可以作为JVM实例运行的起点。
    2. JVM实例的运行:main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以标明自己创建的线程是守护线程。
    3. JVM实例的消亡:当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用java.lang.Runtime类或者java.lang.System.exit()来退出

    JVM运行内存模型

    概念:JVM把文件.class字节码加载到内存,对数据进行校验,转换和解析,并初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制
    以下是JVM的概念图
    在这里插入图片描述

    类装载器

    类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例

    在虚拟机提供了4种类加载器

    • 启动类加载器(Bootstrap ClassLoader)-----c++编写,加载java核心库 java.*
      ■这个类加载器负责放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库。用户无法直接使用。
      ■由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
    • 扩展类加载器(Extension ClassLoader)-----java编写,加载扩展库
      ■这个类加载器由sun.misc.Launcher$AppClassLoader实现。它负责<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。用户可以直接用。
    • 应用程序类加载器(Application ClassLoader)---------java编写,加载程序所在的目录
      ■这个类由sun.misc.Launcher$AppClassLoader实现。是ClassLoader中getSystemClassLoader()方法的返回值。它负责用户路径(ClassPath)所指定的类库。用户可以直接使用。如果用户没有自己定义类加载器,默认使用这个。
    • 自定义类加载器(User ClassLoader)
      ■用户自己定义的类加载器。

    ⚪原理

    • 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:
      ↓加载(Loading)
      ↓验证(Verification)
      ↓准备(Preparation)
      ↓解析(Resolution)
      ↓初始化(Initialization)
      ↓使用(Using)
      ↓卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称链接。

    在这里插入图片描述

    • ⚪加载:(重点)
      加载阶段是“类加载机制”中的一个阶段,这个阶段通常也被称作“装载”,主要完成:
      1.通过“类全名”来获取定义此类的二进制字节流
      2.将字节流所代表的静态存储结构转换为方法区的运行时数据结构----模板
      3.在java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
      解析:相对于类加载过程的其他阶段,加载阶段(准备地说,是加载阶段中获取类的二进制字节流的动作)是开发期可控性最强的阶段,因为加载阶段可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。
      加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式有虚拟机实现自行定义,虚拟机并未规定此区域的具体数据结构。然后在java堆中实例化一个java.lang.Class类的对象,这个对象作为程序访问方法区中的这些类型数据的外部接口。
    • ⚪验证:
      验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
      验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。
    • ⚪准备:
      准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的知识点,首先是这时候进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中。其次是这里所说的初始值“通常情况”下是数据类型的零值,假设一个类变量定义为:
      public static int value = 12;
      那么变量value在准备阶段过后的初始值为0而不是12,因为这时候尚未开始执行任何java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器()方法之中,所以把value赋值为12的动作将在初始化阶段才会被执行。
      上面所说的“通常情况”下初始值是零值,那相对于一些特殊的情况,如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,建设上面类变量value定义为:
      public static final int value = 123;
      编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value设置为123。
    • ⚪解析:
      解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。
      符号引用:符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
      直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
      解析的动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。分别对应编译后常量池内的CONSTANT_Class_Info、CONSTANT_Fieldref_Info、CONSTANT_Methodef_Info、CONSTANT_InterfaceMethoder_Info四种常量类型。
      1.类、接口的解析
      2.字段解析
      3.类方法解析
      4.接口方法解析
    • ⚪初始化:(了解)
      类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

    类装载器的内部机制----------------双亲委派机制(重点)

    原理:当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
    在这里插入图片描述

    个人理解:
    即每个儿子都不愿意干活,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这就是传说中的双亲委派模式

    双亲委派机制的作用

    • 1.保证安全性
      防止重复加载同一个class。通过委托去向上面问,加载过了就不用再加载一遍。保证数据安全。
    • 2.保证唯一性
      保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

    个人解释:
    试想,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性将无法保证,因为Object都各不相同那么程序运行启动就会出错,也保证了JVM能够正常的安全运行。

    运行时数据区

    • 方法区——线程共享
    • ——线程共享
    • Java栈(虚拟机栈)——非线程共享
    • 程序计数器——非线程共享
    • 本地方法栈——非线程共享

    (1)JVM运行时会分配好方法区和堆,而JVM每遇到一个线程,就为其分配一个程序计数器、Java栈、本地方法栈,当线程终止时,三者(程序计数器、Java栈、本地方法栈)所占用的内存空间也会释放掉。

    (2)程序计数器、Java栈、本地方法栈的生命周期与所属线程相同,而方法区和堆的生命周期与JAVA程序运行生命周期相同,所以gc只发生在线程共享的区域(大部分发生在Heap上)

    ⚪堆:(吃多了拉)——线程共享

    • Java中的堆是用来存储对象实例以及数组(当然,数组引用是存放在Java栈中的)。堆是被所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的。在JVM中只有一个堆。堆是Java垃圾收集器管理的主要区域,Java的垃圾回收机制会自动进行处理。
    • 堆空间分为老年代和年轻代。刚创建的对象存放在年轻代,而老年代中存放生命周期长久的实例对象。
    • 年轻代中又被分为Eden区和两个Survivor区新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次GC仍然存活的,就会被转移到老年代。 当一个对象大于eden区而小于old区(老年代)的时候会直接扔到old区。 而当对象大于old区时,会直接抛出OutOfMemoryError(OOM)

    ⚪元空间=永久代----方法区:----存储在物理硬盘(非堆)——线程共享

    • 有时候也称为永久代(Permanent Generation),在方法区中,存储了每个类的信息(包括类的名称、修饰符、方法信息、字段信息)、类中静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息以及编译器编译后的代码等

    • 当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,在这里进行的GC主要是方法区里的常量池和类型的卸载。当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。—OOM

    • 在方法区中有一个非常重要的部分就是运行时常量池用于存放静态编译产生的字面量和符号引用。运行时生成的常量也会存在这个常量池中,比如String的intern方法。它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。
      在这里插入图片描述
      为什么元空间又称为非堆: 因为在内存模型中他存储的数据和堆有些许不同,所以习惯性把他从堆中单独区分出来。 方法区属于非堆内存。它存储每个类结构,如运行时常数池、字段和方法数据,以及方法和构造方法的代码。它是在 Java 虚拟机启动时创建的。

    ⚪Java栈:(喝多了吐)——非线程共享

    • ♦Java栈也称作虚拟机栈(Java Vitual Machine Stack),也就是我们常常所说的栈。JVM栈是线程私有的,每个线程创建的同时都会创建自己的JVM栈,互不干扰。 Java栈是Java方法执行的内存模型。
    • ♦Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。
    • ♦当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。 当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。
    • ♦局部变量表: 用来存储方法中的局部变量,对于引用类型的变量,则存的是指向对象的引用,对于基本数据类型的变量,则直接存储它的值
    • ♦操作数栈: 栈最典型的一个应用就是用来对表达式求值。在一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。
    • ♦指向运行时常量池的引用: 因为在方法执行的过程中有可能需要用到类中的常量,所以必须要有一个引用指向运行时常量。
    • ♦方法返回地址: 当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址

    ⚪程序计数器:——非线程共享

    • ♦程序计数器(Program Counter Register),也有称作为PC寄存器。
      由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
    • ♦在JVM规范中规定,如果线程执行的是非native(本地)方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。
    • ♦由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。

    ⚪本地方法栈:——非线程共享

    JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。
    本地方法栈与Java栈的作用和原理非常相似。**区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。**在JVM规范中,并没有对本地方法栈的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。

    创建对象内存分析

    在这里插入图片描述

    个人理解:

    首先会在方法区加载Class模板,每个模板都有该类的属性以及方法和常量池,以及静态变量随着类加载而加载到静态方法区,并在java堆中生成一个代表这个类的Class对象的内存区域,为类变量分配内存并设置类变量初始值,例如String类型为null, 而后在栈中存储该对象的引用地址,地址指向堆中类的内存地址,最后初始化,例如构造方法,赋值给堆中的对象内存数据中。每一个线程都会给他分配指定的 java栈,程序计数器,本地方法栈,所以说这三个是线程独享,是线程安全,而堆和方法区大家一起使用的,因而是线程不安全

    ♦ JVM垃圾回收机制------GC

    GC的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停。
    个人理解:
    JVM的内存结构包括五大区域:程序计数器、虚拟机栈、本地方法栈、堆区、方法区。

    • 1.程序计数器、虚拟机栈、本地方法栈3个区域随线程而生、随线程而灭,因为方法结束或者线程结束时,内存自然就跟随着回收了。
    • 2.而Java堆区和方法区则不一样,这部分内存的分配和回收是动态,正是垃圾收集器所关注的部分。

    以下介绍GC回收机制的几种方式!

    • ♦ 引用计数法------过时

    概念: 引用计数是垃圾收集器中的早期策略。堆中每个对象实例都有一个引用计数。当一个对象被创建时,就将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1,引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。
    在这里插入图片描述
    缺点: 这样很浪费资源,要对所有对象引用计数,并且计数器本身也浪费资源

    • ♦ 标记-清除(Mark-Sweep)算法

    标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。(需要两次扫描)
    在这里插入图片描述
    主要缺点:
    1.一个是效率问题,标记和清除过程的效率都不高。
    2.另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致:当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前出发另一次垃圾收集动作。

    • *标记-整理(Mark-Compact)算法====标记-清除-整理

    为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。(需要三次扫描)

    在这里插入图片描述
    优点:减少了内存碎片
    缺点:更加降低了效率

    • ♦ 复制算法

    在这里插入图片描述

    • 幸存区分为了两块,一个是form,一个是to-------谁空谁to
      理解: 当新的对象存在Eden区时,进行GC时会将存活下来的复制幸存区的to区,并把原本为from区的也复制to区,这样原本为from区变成to区,to变成了from区,然后Eden区和to区每次清除都必定为空的,减少了内存碎片,适用于对象存活时间较短的项目中,而后在幸存from区超过15次都没有清除的就复制到老年区
      总结:
      这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。

    ♦分代收集(Generational Collection)算法------堆分区使用不同算法(重点)

    ⚪概念:
    分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和年轻代,在堆区之外还有一个代就是永久代,它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
    ⚪使用:

    • 老年代的特点是每次垃圾收集时只有少量对象需要被回收,-------- (生命周期长)标记-清除-整理
    • 而年轻代的特点是每次垃圾回收时都有大量的对象需要被回收,--------(生命周期短) 复制算法
      *那么就可以根据不同代的特点采取最适合的收集算法。
    • 老年代(Old Generation)的回收算法:标记-整理和标记清除一起使用
      老年代的特点是每次回收都只回收少量对象,一般使用的是标记-整理和标记清除一起使用。
    • a)在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
    • b)内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC或Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
      原因:因为老年代的对象通常生命周期大,只回收少量对象,使用标记清除算法被标记的数量较少,产生的内存碎片不会很多,可以多次标记清除后再进行标记整理内存碎片。

    ■年轻代中jvm使用的是:Mark-copy(标记-复制)算法

    • a)所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
    • b)年轻代分三个区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个 Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当另外一个Survivor区也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制到“年老区(Tenured)”。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor区过来的对象。而且,Survivor区总有一个是空的。
    • c)当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
    • d)新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。
      原因:因为年轻代的对象通常生命周期小,回收大量对象,使用复制算法效率高,并且不会有大量内存碎片,将对象直接批量删除,存活下来的全部复制到幸存区,很适合用复制算法

    ■永久代(Permanent Generation)的回收算法:

    永久代(permanent generation) 也称为“方法区(method area)”,他存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为major GC。

    在这里插入图片描述

    ♦Minor GC、Full GC触发条件

    Minor GC触发条件

    • Eden区域满了,或者新创建的对象大小 > Eden所剩空间
      CMS设置了CMSScavengeBeforeRemark参数,这样在CMS的Remark之前会先做一次Minor GC来清理新生代,加速之后的Remark的速度。这样整体的stop-the-world时间反而短
    • Full GC的时候会先触发Minor GC

    Full GC触发条件

    • Minor GC后存活的对象晋升到老年代时由于悲观策略的原因,有两种情况会触发Full GC,
      一种是之前每次晋升的对象的平均大小 > 老年代剩余空间;
      一种是Minor GC后存活的对象超过了老年代剩余空间。

      这两种情况都是因为老年代会为新生代对象的晋升提供担保,而每次晋升的对象的大小是无法预测的,所以只能基于统计,一个是基于历史平均水平,一个是基于下一次可能要晋升的最大水平。
      这两种情况都是属于promotion failure
    • CMS失败,发生concurrent mode failure会引起Full GC,这种情况下会使用Serial Old收集器,是单线程的,对GC的影响很大。concurrent mode failure产生的原因是老年代剩余的空间不够,导致了和gc线程并发执行的用户线程创建的大对象(由PretenureSizeThreshold控制新生代直接晋升老年代的对象size阀值)不能进入到老年代,只要stop the world来暂停用户线程,执行GC清理。可以通过设置CMSInitiatingOccupancyFraction预留合适的CMS执行时剩余的空间
    • 新生代直接晋升到老年代的大对象超过了老年代的剩余空间,引发Full GC。注意于promotion failure的区别,promotion failure指的是Minor GC后发生的担保失败
    • Perm永久代空间不足会触发Full GC, 可以让CMS清理永久代的空间。设置CMSClassUnloadingEnabled即可
    • System.gc()引起的Full GC, 可以设置DisableExplicitGC来禁止调用System.gc引发Full GC

    ♦OOM - Out of Memory,内存溢出
    软件所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。此时软件或游戏就运行不了,系统会提示内存溢出,有时候会自动关闭软件,重启电脑或者软件后释放掉一部分内存又可以正常运行该软件

    ♦JVM调优参数简介

    -XX 参数被称为不稳定参数,之所以这么叫是因为此类参数的设置很容易引起JVM 性能上的差异,使JVM 存在极大的不稳定性。如果此类参数设置合理将大大提高JVM 的性能及稳定性。

    不稳定参数语法规则:
    1.布尔类型参数值
            -XX:+<option> '+'表示启用该选项
            -XX:-<option> '-'表示关闭该选项
    2.数字类型参数值:
            -XX:<option>=<number> 给选项设置一个数字类型值,可跟随单位,例如:'m''M'表示兆字节;'k''K'千字节;'g''G'千兆字节。32K与32768是相同大小的。
    3.字符串类型参数值:
            -XX:<option>=<string> 给选项设置一个字符串类型值,通常用于指定一个文件、路径或一系列命令列表。
    例如:-XX:HeapDumpPath=./dump.core
    

    ♦JVM参数示例

    配置: -Xmx4g –Xms4g –Xmn1200m –Xss512k -XX:NewRatio=4 -XX:SurvivorRatio=8 -XX:PermSize=100m
    -XX:MaxPermSize=256m -XX:MaxTenuringThreshold=15
    解析:
    -Xmx4g:堆内存最大值为4GB。
    -Xms4g:初始化堆内存大小为4GB 。
    -Xmn1200m:设置年轻代大小为1200MB。增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8-Xss512k:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1MB,以前每个线程堆栈大小为256K。应根据应用线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
    -XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4,则年轻代与年老代所占比值为14,年轻代占整个堆栈的1/5
     -XX:SurvivorRatio=8:设置年轻代中Eden区与Survivor区的大小比值。设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
    -XX:PermSize=100m:初始化永久代大小为100MB。
    -XX:MaxPermSize=256m:设置持久代大小为256MB。
    -XX:MaxTenuringThreshold=15:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
    

    以上是我对JVM的个人理解以及引用了一些大佬的概念,希望大家一起学习探讨~有错误的地方希望能给我指出 0.0

    展开全文
  • JVM原理.pdf

    2019-07-24 11:05:34
    详细讲解了jvm 的运行原理,以及内存分配
  • 面试必问:常见JVM面试题一、详解JVM内存模型二、JVM中一次完整的GC流程是怎样的三、GC垃圾回收的算法有哪些四、简单说说你了解的类加载器五、双亲委派机制是什么,怎么打破六、说说你JVM调优的几种主要的JVM参数 ...

    在这里插入图片描述

    一、详解JVM内存模型

    JVM有本地方法栈、虚拟机栈、程序计数器、堆、方法区。
    JVM内存分为共享区(可以被所有方法(线程)直接访问)和私有区(对线程来说是私有的,其他线程无法直接访问)。
    在共享区里包含着堆和方法区,在私有区里包含着程序计数器、虚拟机栈和本地方法栈。

    程序计数器PC:是一个行号计数器,程序在进行跳转时,我们要记住跳转的行号,它方便我们的程序进行还原。
    虚拟机栈:包含了Java方法执行时的状态,每一个Java方法都会在虚拟机栈里面创建一个栈帧,里面存放局部变量表、操作数栈、动态链接、方法出口等。
    本地方法栈:跟虚拟机栈类型,在用于调用操作系统的底层方法时才会创建栈帧。
    堆:用来保存着Java程序运行时的变量,比如new的对象。
    方法区:则保存着静态的东西,比如静态变量、常量、类的信息、方法的申明等。

    面试题必问:Java虚拟机内存模型

    二、JVM中一次完整的GC流程是怎样的

    在这里插入图片描述
    Java堆 = 老年代 + 新生代
    新生代 = Eden + S0 + S1

    当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。
    大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;
    如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
    老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和老年代老年代。
    Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。

    三、GC垃圾回收的算法有哪些

    引用计数算法
    跟踪回收算法
    压缩回收算法
    复制回收算法
    按代回收算法
    浅析Java的垃圾回收机制(GC)和五种常用的垃圾回收算法

    四、简单说说你了解的类加载器

    类加载器 就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。

    启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定的路径中的类库加载到内存中。

    扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统变量指定的路径中的所有类库。

    应用程序类加载器(Application ClassLoader): 负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

    五、双亲委派机制是什么,有什么好处,怎么打破

    双亲委派机制:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
    在这里插入图片描述

    双亲委派模型的好处:
    ●主要是为了安全性,避免用户自己编写的类动态替换Java的一 些核心类,比如String。
    ●同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类。

    打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。

    六、说说你JVM调优的几种主要的JVM参数

    1)堆栈配置相关

    java -Xmx3550m -Xms3550m -Xmn2g -Xss128k 
    -XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0
    

    -Xmx3550m: 最大堆大小为3550m。

    -Xms3550m: 设置初始堆大小为3550m。

    -Xmn2g: 设置年轻代大小为2g。

    -Xss128k: 设置线程堆栈大小为128k。

    -XX:MaxPermSize: 设置持久代大小为16m

    -XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与老年代的比值(除去持久代)。

    -XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6

    -XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入老年代。

    2)垃圾收集器相关

    -XX:+UseParallelGC
    -XX:ParallelGCThreads=20
    -XX:+UseConcMarkSweepGC 
    -XX:CMSFullGCsBeforeCompaction=5
    -XX:+UseCMSCompactAtFullCollection:
    

    -XX:+UseParallelGC: 选择垃圾收集器为并行收集器。

    -XX:ParallelGCThreads=20: 配置并行收集器的线程数

    -XX:+UseConcMarkSweepGC: 设置老年代为并发收集。

    -XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
    -XX:+UseCMSCompactAtFullCollection: 打开对老年代的压缩。可能会影响性能,但是可以消除碎片

    3)辅助信息相关

    -XX:+PrintGC
    -XX:+PrintGCDetails
    

    -XX:+PrintGC 输出形式:

    [GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs]

    -XX:+PrintGCDetails 输出形式:

    [GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs

    七、JVM调优

    1.将堆的最大、最小设置为相同的值,目的是防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间。
    -Xmx3550m: 最大堆大小为3550m。
    -Xms3550m: 设置初始堆大小为3550m。

    2.在配置较好的机器上(比如多核、大内存),可以为老年代选择并行收集算法: -XX:+UseParallelOldGC 。

    3.年轻代和老年代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率来调整二者之间的大小,也可以针对回收代。

    比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小。

    4.年轻代和老年代设置多大才算合理

    1)更大的年轻代必然导致更小的老年代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的老年代会导致更频繁的Full GC

    2)更小的年轻代必然导致更大老年代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的老年代会减少Full GC的频率

    如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,老年代应该适当增大。但很多应用都没有这样明显的特性。

    在抉择时应该根 据以下两点:
    (1)本着Full GC尽量少的原则,让老年代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 。
    (2)通过观察应用一段时间,看其他在峰值时老年代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给老年代至少预留1/3的增长空间。

    八、类加载的机制及过程

    加载>>验证>>准备>>解析>>初始化

    (1)加载:在硬盘上查找并通过I0读入字节码文件,在堆内存中生成一个对象,作为方法区数据的访问入口
    (2)验证: 校验字节码文件的正确性
    (3)准备:给类的静态变量分配内存,并赋予默认值
    (4)解析(动态链接): 将静态方法的符号引用替换为直接引用(有对应的内存地址信息)
    (5)初始化: 给类的静态变量初始化为指定的值,执行静态代码块

    九、Jdk1.7到Jdk1.8 java虚拟机发⽣了什么变化?

    1.7中存在永久代,1.8中没有永久代,替换它的是元空间,元空间所占的内存不是在虚拟机内部,⽽是本地 内存空间,这么做的原因是,不管是永久代还是元空间,他们都是⽅法区的具体实现,之所以元空间所占 的内存改成本地内存,官⽅的说法是为了和JRockit统⼀,不过额外还有⼀些原因,⽐如⽅法区所存储的类 信息通常是⽐较难确定的,所以对于⽅法区的⼤⼩是⽐较难指定的,太⼩了容易出现⽅法区溢出,太⼤了 ⼜会占⽤了太多虚拟机的内存空间,⽽转移到本地内存后则不会影响虚拟机所占⽤的内存。

    十、你们项目如何排查JVM问题 ?

    分两种情况
    ①对于还在正常运⾏的系统:

    1. 可以使⽤jmap来查看JVM中各个区域的使⽤情况;
    2. 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁;
    3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏调优了 ;
    4. 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析 ;
    5. ⾸先,初步猜测频繁发送fullgc的原因,如果频繁发⽣fullgc但是⼜⼀直没有出现内存溢出,那么表示 fullgc实际上是回收了很多对象了,所以这些对象最好能在younggc过程中就直接回收掉,避免这些对 象进⼊到⽼年代,对于这种情况,就要考虑这些存活时间不⻓的对象是不是⽐较大,导致年轻代放不 下,直接进⼊到了⽼年代,尝试加⼤年轻代的⼤⼩,如果改完之后,fullgc减少,则证明修改有效;
    6. 同时,还可以找到占⽤CPU最多的线程,定位到具体的⽅法,优化这个⽅法的执⾏,看是否能避免某些 对象的创建,从⽽节省内存 。

    ② 对于已经发⽣了OOM的系统:
    1.⼀般⽣产系统中都会设置当系统发⽣了OOM时,⽣成当时的dump⽂件(- XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/base)
    2. 我们可以利⽤jsisualvm等⼯具来分析dump⽂件
    3. 根据dump⽂件找到异常的实例对象,和异常的线程(占⽤CPU⾼),定位到具体的代码
    4. 然后再进⾏详细的分析和调试

    十一、深拷贝和浅拷贝

    深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本數据类型, 一种是实例对象的引用。

    1.浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象, 也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象

    2深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的类执行指向的不是同一个对象

    十二、说⼀下JVM中,哪些可以作为GC root

    首先,GC root是根对象,JVM在进⾏垃圾回收时,需要找到“垃圾”对象,也就是没有被引⽤的对象,但是直接找“垃圾”对象是⽐较耗时的,所以反过来,先找“⾮垃圾”对象,也就是正常对象,那么就需要从某些“根”开始去找,根据这些“根”的引⽤路径找到正常对象,⽽这些“根”有⼀个特征,就是它只会引⽤其他对象,⽽不会被其他对象引⽤,例如:栈中的本地变量、⽅法区中的静态变量、本地⽅法栈中的变量、正在运⾏的线程等可以作为GC root。

    十三、JVM诊断工具有哪些?

    阿里的Arthas
    JDK自带JVM诊断工具:JAVA VisualVM

    Full GC把整个堆都回收
    OOM:内存溢出 Out of Memory Error

    十四、为什么要使用STW?

    STW:stop the word JAVA进行GC时会停止用户线程,对用户体验很不好

    反证法,如果没有STW,用户线程将一直执行。
    GC要进行垃圾回收,就要找所有垃圾和非垃圾,先找GC root,然后找引用对象。因为程序会在GC的过程中一直在执行,然后结束了,结束之后内存空间都被释放了,栈帧弹出,GC root已经没了,之前找的非垃圾已经全变成垃圾了。

    展开全文
  • jvm原理

    2017-06-28 21:18:09
    1. 什么是JVMJVM是Java VirtualMachine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码...
    1. 什么是JVM?

    JVM是Java VirtualMachine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。

    Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

    2. JRE/JDK/JVM是什么关系?

    JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
    JDK(Java DevelopmentKit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。
    JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。
    3. JVM原理

    JVM是java的核心和基础,在java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,可以在上面执行java的字节码程序。

     
    java编译器只要面向JVM,生成JVM能理解的代码或字节码文件。Java源文件经编译成字节码程序,通过JVM将每一条指令翻译成不同平台机器码,通过特定平台运行。
     
    4. JVM执行程序的过程
    1) 加载.class文件 2) 管理并分配内存 3) 执行垃圾收集
    JRE(java运行时环境)由JVM构造的java程序的运行环,也是Java程序运行的环境,但是他同时一个操作系统的一个应用程序一个进程,因此他也有他自己的运行的生命周期,也有自己的代码和数据空间。JVM在整个jdk中处于最底层,负责于操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机。操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境:1) 创建JVM装载环境和配置 2) 装载JVM.dll 3)初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例4) 调用JNIEnv实例装载并处理class类。
     
    5. JVM的生命周期
    1) JVM实例对应了一个独立运行的java程序它是进程级别
    a) 启动。启动一个Java程序时,一个JVM实例就产生了,任何一个拥有public static void
    main(String[] args)函数的class都可以作为JVM实例运行的起点
    b) 运行。main()作为该程序初始线程的起点,任何其他线程均由该线程启动。JVM内部有两种线程:守护线程和非守护线程,main()属于非守护线程,守护线程通常由JVM自己使用,java程序也可以表明自己创建的线程是守护线程
    c) 消亡。当程序中的所有非守护线程都终止时,JVM才退出;若安全管理器允许,程序也可以使用Runtime类或者System.exit()来退出
     
    2) JVM执行引擎实例则对应了属于用户运行程序的线程它是线程级别的
     
    6. JVM的体系结构

     
    类装载器(ClassLoader)(用来装载.class文件)
    执行引擎(执行字节码,或者执行本地方法)
    运行时数据区(方法区、堆、java栈、PC寄存器、本地方法栈)
    7. JVM运行时数据区



    第一块:PC寄存器

    PC寄存器是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息。

    第二块:JVM栈

    JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。

    第三块:堆(Heap)

    它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收。



    (1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的

    (2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配

    (3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。

    (4) 所有新创建的Object 都将会存储在新生代Yong Generation中。如果YoungGeneration的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。新的Object总是创建在EdenSpace。

    第四块:方法区域(Method Area)

    (1)在Sun JDK中这块区域对应的为PermanetGeneration,又称为持久代。

    (2)方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。

    第五块:运行时常量池(Runtime Constant Pool)

    存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。

    第六块:本地方法堆栈(Native Method Stacks)

    JVM采用本地方法堆栈来支持native方法的执行,此区域用于存储每个native方法调用的状态。

    8. JVM垃圾回收

    GC (GarbageCollection)的基本原理:将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、旧生代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停

    (1)对新生代的对象的收集称为minor GC;

    (2)对旧生代的对象的收集称为Full GC;

    (3)程序中主动调用System.gc()强制执行的GC为Full GC。

    不同的对象引用类型, GC会采用不同的方法进行回收,JVM对象的引用分为了四种类型:

    (1)强引用:默认情况下,对象采用的均为强引用(这个对象的实例没有其他对象引用,GC时才会被回收)

    (2)软引用:软引用是Java中提供的一种比较适合于缓存场景的应用(只有在内存不够用的情况下才会被GC)

    (3)弱引用:在GC时一定会被GC回收

    (4)虚引用:由于虚引用只是用来得知对象是否被GC
    展开全文
  • JVM原理学习总结

    千次阅读 2018-06-28 22:32:40
    JVM原理学习总结 这篇总结主要是基于我之前JVM系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍,可能会有一些错误,还望见谅和指点。谢谢 更多详细内容可以查看我的专栏文章:深入理解JVM虚拟机 ...
  • jvm原理之面试(一)---原理分析

    千次阅读 2019-04-16 16:36:37
    作为中高程序员的面试中,JVM是必问的基础知识,但很多童鞋会疑问,我完全不知道这些,也照样顺利完成好多项目,必须承认,在通常工作中,在小公司,如果只是根据要求写写代码,改改BUG,在是没有机会接触到JVM的,...
  • 另外,小编也有根据以下总结内容,录制了对JVM讲解视频。里面有个用visio画的JVM内部结构图,每部分的作用都有详细讲解,希望能有帮助。网址:https://edu.csdn.net/lecturer/board/10494 1.java自动管理堆(heap...
  • JVM原理之内存模型

    2020-12-22 11:04:40
    一、JVM是什么 JVM :Java Virtual Machine,就是我们耳熟能详的 Java 虚拟机。它只认识 .class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,JVM 是 ...
  • JVM原理和调优(读这一篇就够了)

    千次阅读 2020-06-11 12:19:13
    --------------------------------------------------------------- 一、JVM内存模型 堆:所有new对象都放在堆里,这也是JVM优化的重点。 栈:也叫线程栈,是每个线程独立分配的内存空间,存储的包括局部变量,操作...
  • JVM原理及性能调优

    千次阅读 2018-07-10 18:23:14
    好记性不如烂笔头呀~一、什么是JVM JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。...
  • 精通jvm,带你由浅入深的了解jvm原理及垃圾回收等
  • 这是一个jvm原理图的psd问价。直接上传psd文件是方便大家有了自己的理解后修改为自己的原理图方便。下载后直接打开修改即可。
  • 4. 运行时数据区 ...4.1 堆 (heap) 堆在虚拟机中是一块共享区域, 存放 对象实例 和数组; 堆在虚拟机启动的时候创建。 可调整堆的大小 堆内存不够的时候会发生OOM java.lang.OutOfMemoryError: Java heap space 代码...
  • 在如今这个时间和知识都是碎片化的时代,C站根据C1-C4认证的成长路径,进行知识细化整理,形成系统化的知识图谱。 通过调研一线互联网大厂的招聘JD,小编对标C站能力认证要求,为大家整理了系列技术... 【JVM原理和.

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 226,754
精华内容 90,701
关键字:

jvm原理