关于java:熬夜给女朋友梳理了一份JVM知识点总结网友单身狗的我慕了

11次阅读

共计 9801 个字符,预计需要花费 25 分钟才能阅读完成。

该文章是分享 Java 和 JVM 的常识,是我在学习过程中的笔记以及总结,整顿记录的过程不能用单纯用脑袋记,定期设立一个指标,例如一周更新一篇,好忘性不如烂笔头,常常温习才是王道,切忌三天打鱼两天晒网。

接下来开始明天的分享:

java 简介
1、java 的三个体系
java 是面向对象程序语言 java 和 java 平台的总称,java 有三个体系:

JAVASE (java2 Platform Standard Editor) java 平台标准版

JAVAEE(java2 Platform Enterprise Editor) java 平台企业版

JAVAME(java2 Platform Micro Editor) java 平台标准版

JavaSE 是 JavaEE 和 JavaME 的根底。

JavaEE 是在 JavaSE 的根底上构建,用以开发 B / S 加工软件,即开发企业级利用。JavaEE 在 JAVASE 的根底上扩大,增加了许多便捷的框架,譬如咱们罕用的 Spring,Struts,Hibernate。JavaSE 也能够说既是框架也是标准,它蕴含了许多咱们开发时用到的组件如 Servlet,EJB,JSP,JSTL,同时 JAVAEE 提供了许多标准的接口却并不实现,使得即便不同厂商实现细节不同,展示给内部应用的却是对立标准的接口。

JavaME 则是一套专门为嵌入式设备设计的 api 接口标准,专门用于开发挪动设施软件和嵌入式设施软件。

注:之前有接触过 android studio,对于 android 软件的开发和 JavaME,我看到的说法是两者惟一的关系就是都是 java 语言实现的。android 软件不能运行在 javaME 的环境中,javaME 软件也不能运行在 android 环境中。

2、java 的次要个性
java 语言是简略的,因为它的语法与 C 和 C ++ 类似,然而又摈弃了一些 C ++ 中很少应用的、简单的个性,比方操作符重载,多继承,主动的强制类型转换,并且舍弃了指针的应用,提供了主动调配和回收内存的垃圾处理器。

java 语音是面向对象的,java 语音提供了类、接口和继承等面向对象的个性,不过 java 的类不反对多继承,接口反对多继承,java 提供了类于接口之间的实现机制,并且全面反对动静绑定。

Java 语言是分布式的,它反对 internet 利用开发,它的网络应用编程接口 (java net) 提供了撑持网络应用开发的类库,譬如 URL, URLconnection, Socket, ServerSocket 等,其中 java 的 RMI(近程办法激活)机制也是开发分布式应用的重要伎俩。

Java 语言式强壮的,java 的强类型机制、异样解决、垃圾的回收器是 java 程序健壮性的重要保障,java 抛弃指针机制是理智的,java 的安全检查机制使得 java 更具健壮性。

java 语言是平安的,它提供了一个平安机制以避免来自网络的恶意代码的攻打。java 对通过网络下载的类具备一个平安防备机制(类 ClassLoader),如调配不同的名字空间避免代替本地同名类、字节代码查看,并提供平安管理机制(类 SecurityManager)让 java 利用设置平安卫兵。

Java 语言是体系结构中立的,java 程序(.java)在 java 平台中被编译成体系结构中立的字节码格局(.class),而后能够在实现了 java 平台的任何零碎中运行,这种路径适宜于异构的网络环境和软件的散发。

java 语言是可移植的,这种可移植性来源于体系结构中立性。

Java 语言是解释型的,java 程序在 java 平台上被编译成可移植的字节码格局,在运行时,java 平台的 java 解释器对这些字节码进行解释执行,执行过程中须要的类在联结过程中被载入到运行环境中。

java 语言是高性能的,相比于解释型的高级脚本语言,java 是高性能的,随着 JIT(Just-In-Time)编译技术的倒退,java 的运行速度越来越靠近 C ++。

Java 语言是多线程的,java 反对多线程同时执行,并提供了多线程的同步机制。线程是 java 语言一种非凡对象,它必须由 Thread 类或者其子 (孙 l) 类来创立。

java 语言是动静的,java 语言的设计目标之一就是适应于动态变化的环境,java 程序须要的类可能动静地被载入到运行环境,也能够通过网络环境载入所须要的类,这有利于软件的降级。另外,java 中的类有一个运行时刻的示意,能进行运行时刻的类型查看。

其实 java 的这几个次要个性的形容曾经蕴含了 java 相干的许多根底的须要理解的知识点了。

3、JDK 与 JRE 的关系:
JDK: 反对 Java 程序开发的最小环境。JDK = Java 程序设计语言 +Java 虚拟机 +Java API 类库。

JRE: 反对 Java 程序运行的规范环境。JRE=Java 虚拟机 +Java API 类库中的 Java SE API 子集。

JVM
对于 JVM 到底要从什么中央开始写起来,我一度非常犹豫,或者说我到底想要从这次的常识整顿中构建起怎么样的概念呢?

java 技术的外围就是 java 虚拟机(JVM,Java Virtual Machine),所有的 java 程序都运行在 JVM 外部。然而 JVM 是跨语言的平台,它是语言无关的,它并不与 java 语言绑定,任何编程语言的编译后果满足并蕴含 JVM 的外部指令、符号表以及其余的辅助信息,它就是一个无效的字节码文件,可能被虚拟机辨认并装载运行。java 虚拟机在操作系统之上,并不与硬件间接交互。

JVM 的整体体系蕴含 JVM 内存区域,JVM 内存溢出,类加载,垃圾回收,性能优化。Java 虚拟机(JVM)你只有看这一篇就够了!这篇文章中有一个很欠缺的思维导图,我也应该会从 JVM 内存区域、类加载、垃圾回收进行切入,但不会太深刻,因为许多概念性的货色我在理解过程中并不能齐全吃透,还须要再积攒,无关 JVM 更加深刻的细节应该后续再进行理解。

1 JVM 内存区域和 JVM 内存溢出

如图所示,JVM 运行时的内存区域蕴含程序计数器,本地办法栈,虚拟机栈,办法区,堆,其中程序计数器,本地办法栈,虚拟机栈是线程隔离的,办法区和堆是线程共享的。

1.1 程序计数器(Program Counter Register)
内存空间小,线程公有。是以后线程执行字节码的行号指示器,分支、循环、跳转、异样解决、线程复原等根底性能都须要依赖计数器实现。若线程正在执行一个 java 办法,这个计数器记录的则是正在执行的虚拟机字节码指令的地址,若线程正在执行一个 native 办法,那么计数器的值则为 Undefined。程序计数器是惟一在 java 虚拟机标准中没有规定 OutOfMemoryError 状况的区域。

1.2 虚拟机栈 (VM Stack)
线程公有,生命周期和线程统一,形容的是 java 办法执行时的内存模型:每个办法执行时都会创立一个栈帧(Stack Frame),用于存储局部变量表,操作数栈,动静链接和办法进口等信息,每一个办法的调用到完结都对应着一个栈帧从虚拟机栈入栈到出栈的过程。

局部变量表:寄存了编译期可知的各种根本类型 (boolean,byte,short,char,int,float,long,double),对象援用(reference 类型) 和 returnAddress 类型

StackOverFlowError:线程申请栈深度大于虚拟机所容许的深度。

OutOfMemoryError:如果虚拟机栈能够动静扩大,而拓展时无奈申请到足够的内存。

1.3 本地办法栈 (Native Method Stack)
它与虚拟机栈的区别是,本地办法栈是为 native 办法服务的,虚拟机栈式为 java 办法服务的。

1.4 堆 (Heap)
在大部分利用中,这是 java 虚构器所治理的内存中最大的一块,线程共享,次要用来存储数组和对象的实例。在堆的外部会划分出多个线程公有的调配缓冲区(Thread Local Allocation Buffer,TLAB),这些区域能够位于物理上不间断的区域,但要求逻辑上间断。

OutOfMemoryError:如果堆没有内存实现实例调配,且堆无奈再拓展时抛出该异样。

1.5 办法区 (Method Area)
属于共享内存区域,存储已被虚拟机加载的类信息,常量,动态变量,即时编译器编译后的代码等数据。

运行时常量池:用于寄存编译期生成的各种字面量和符号援用。编译器和运行期(String 的 Intern())都可将常量放入,内存无限,无奈申请时抛出 OutOfMemoryError。

1.6 间接内存
非虚拟机运行时的局部数据区域

2 类加载
虚拟机类加载过程是指虚拟机把形容类的数据从.class 文件加载到内存,并对数据进行校验、装换解析和初始化,最终造成能够被虚拟机间接应用的 java 类型。在 java 中,类的加载、连贯和初始化过程都是在程序运行期间实现的。

2.1 类加载期间机

在类的生命周期中,类的加载、验证、筹备、初始化和卸载这几个阶段是固定的,解析阶段能够在初始化之后再开始(运行时绑定、动静绑定或早期绑定)。

那么什么状况会触发类的初始化(类加载)呢?

a) 在遇到 new,getstatic,putstatic 和 invokestatic 这 4 条字节码时类没有初始化则触发初始化。比如说,当咱们应用 new 创立某个类的实例,调用某个类的动态变量(在编译期曾经放入了运行时常量池的除外)和静态方法时须要先把这个类初始化。

b)应用 java.lang.reflect 包的办法对类进行反射调用的时候。

c)调用子类的初始化时,要求父类也曾经初始化

d)虚拟机启动时会先初始化用户指定的主类

e)当应用 JDK 1.7 的动静语言反对时,如果一个 java.lang.invoke.MethodHandle 实例最初的解析后果 REF_getStatic、REF_putStatic、REF_invokeStatic 的办法句柄,并且这个办法句柄所对应的类没有进行过初始化,则需先触发其初始化。

类初始化的非凡状况:

a) 当子类继承父类,而动态变量定义在父类中,通过子类名调用该动态变量,触发的是定义了该动态变量的父类。

b)若类的动态变量被 final 润饰是一个常量,调用时不会触发类的初始化

c)当创立某个类的数组,先创立的是数组类,并不会触发该类的初始化

2.2 类加载过程
2.2.1 加载
a) 找到须要加载的类:通过类的全限定名来获取定义此类的二进制流(zip 包,网络,运算生成,jsp 生成,数据库读取)。

b)将类放入办法区:将二进制流所代表的动态存储构造转化为办法区运行时的数据结构

c)创立拜访该类的入口:在内存中生成一个代表这个类的 java.lang.Class 对象,作为这个类各种数据结构的拜访入口。

数组类的特殊性:数组类不通过类加载器创立,而是由虚拟机间接创立,然而数组类的元素类型最终是由类加载器加载的。

a)如果数组类的元素是援用类型,则由递归采纳类加载加载

b)若数组类的元素是值类型,则虚构机会将数组标记为疏导类加载器关联

c)数组类的可见性与其元素可见性统一,若其元素是值类型,则数组类可见性默认是 public

加载阶段和连贯阶段是穿插进行的,只保障开始工夫的前后。

2.2.2 验证
验证是连贯的第一步,次要是确认字节码中蕴含的信息合乎以后虚拟机的要求,次要包含文件格式验证,元数据验证,字节码验证,符号援用验证。

文件格式验证:文件格式验证间接验证字节流,只有通过了文件格式验证的字节流才会存储进办法区,后续三个验证都是针对办法区存储构造,不再间接操作字节流。

元数据验证:进行的是语义校验,保障不存在不合乎 java 语言标准的元数据信息。譬如说这个类是否有父类,继承父类时是否有不符合规范的状况(继承 final 类、非抽象类未实现全副办法等等)

字节码验证:这是整个校验过程中最简单的局部,次要目标时通过数据流和控制流的剖析,确定程序语义是平安的,合乎逻辑的。这个阶段通过对办法体的校验剖析,保障类的办法在运行时不会做出危害虚拟机平安的事件。

符号援用验证:符号援用验证产生在将符号援用转变成间接援用的时候,即在连贯的第三阶段 – 解析的时候产生。次要是确保对类本身以外的信息进行匹配性校验。譬如说援用的类是否能通过全限定名找到,是否能在指定类外面找到指定的变量和办法,以及这些办法是否可拜访。如果这个阶段产生谬误,会抛出 java.lang.IncompatibleClass.changeError 异样的子类,如 java.lang.illegalAccessError,java.lang.NoSuchFiledError,java.lang.NoSuchMethodError 等。

2.2.3 筹备
这个阶段正式为类分配内存并设置变量初始值,这里所指的变量含 static 润饰的变量,其余类成员变量实例初始化阶段才会赋值。

然而这里的初始值是指数据类型的零值,而不是咱们常说的“对变量进行初始化”,如 public static int a = 12; 之中把 a 赋值为 12 的 putstatic 指令是程序编译后寄存于 clinit()办法中的,所以初始化阶段才会将 a 赋值为 12。

非凡状况:ConstantValue 属性的值。

static final 润饰的字段在编译时生成 ConstantValue 属性,在类加载的筹备阶段间接把 ConstantValue 的值赋给该字段,能够了解为编译期即把后果放进常量池。

public class Test{
    public static int a1 = 12;// 在类加载的连贯 - 筹备阶段,将 int 的零值 0 赋给 a1,在初始化阶段才将 12 赋给 a1
    public final int a2=13;//final 润饰的字段在运行时初始化,能够间接赋值也能够在实例结构器中赋值,赋值后不可更改。public static final int a3=14;// 在编译时生成 ConstantVlaue 属性的值 12,在类加载的连贯 - 筹备阶段间接把 ConstantValue 的值赋给 a3
}

2.2.4 解析
这个阶段是虚拟机将符号援用替换为间接援用的过程。

符号援用以一组符号来形容援用的指标,符号能够是任何模式的字面量。间接援用能够是间接指向指标的指针、绝对偏移量或是一个能间接定位到指标的句柄。间接援用和虚拟机的内存布局相干。

解析动作次要针对类或接口、字段、类办法、接口办法、办法类型、办法句柄和调用点限定符 7 类符号援用进行,别离对应常量池的 7 种常量类型。

2.2.5 初始化
后面过程都是以虚拟机主导,而初始化阶段开始执行类中的 java 代码。运行 clinit(),初始化类变量,动态代码块。

<clinit>()办法是由编译器主动收集类中的所有类变量的赋值动作和动态代码块 static{}中的语句合并产生的,编译器收集的程序是由语句呈现在源文件中的程序决定了,动态语句块只能拜访到定义在动态语句块之前的变量,定义在动态语句块之后的变量,在该动态语句块能够赋值,然而不能拜访

2.3 类加载器
通过类的全限定名来获取形容类的二进制字节流。

2.3.1 类加载器类型
对于 java 虚拟机来说,只存在两品种加载器,一种是启动类加载器(C++ 实现,是 java 虚拟机的一部分),另一种是其余类加载器(java 实现,独立于虚拟机内部,且都继承于 java.lang.ClassLoader)

a)启动类加载器

加载 lib 下或被 -Xbootclasspath 门路下的类

b)拓展类加载器

加载 lib/ext 或者被 java.ext.dirs 零碎变量所指定的门路下的类

c)应用程序类加载器

ClassLoader 负责,加载用户门路上所指定的类库

2.3.2 双亲委派模型

除顶层启动类加载器之外,其余都有本人的父加载器。

双亲委派模型工作过程:如果一个类加载器收到一个类加载申请,它首先不会本人加载,而是把这个申请委派给父类加载器,只有父类加载器无奈实现时子类才会尝试加载。

2.3.3 破坏性双亲委派模型
为什么应用双亲模型?一个是平安,另一个是性能,防止了反复加载和外围类被篡改。然而在以下三种状况中双亲委派模型被毁坏:

第一次毁坏:因为双亲委派模型是在 jdk2.0 呈现的,而类加载器和抽象类 java.lang.ClassLoader 在 jdk1.0 就呈现了,在双亲委派模型呈现前就曾经存在了的用户自定义类加载器是不合乎双亲委派模型的,这部分自定义类加载器继承了 ClassLoader 抽象类,重写了 loadClass()办法

第二次毁坏:是由双亲委派模型本身的缺点引起的,为此引入了线程上下文加载器(Thread Context ClassLoader),Java 中所有波及 SPI 的加载动作基本上都采纳了线程上下文加载器,例如 JNDI、JDBC、JCE、JAXB 和 JBI 等,毁坏了双亲委派模型的层次模型。

第三次毁坏:是由用户对程序动态性的谋求所导致的,譬如当初所谋求的代码热替换、模块热部署等,简略的说就是不须要机器进行重启,部署上就能用。OSGI 实现模块热部署的要害就是它自定义的类加载器的实现,在 OSGI 中,类加载器不再是双亲委派模型的树状构造,而是倒退成为更加简单的网状结构。

无关第二次第三次毁坏相干的,可能前面有须要会去进行理解。

3、垃圾回收
3.1 概述
java 虚拟机的程序计数器、虚拟机栈、本地办法栈是线程公有的,会随着线程生灭。而堆和办法区则不同,会随着程序运行加载类、调用办法、创立字段、创立实例对象,分配内存空间……这些操作所须要的内存空间大小和什么时候能够开释空间回收内存都是在程序运行期间才晓得的,C++ 中是人为地治理这部分空间,而 java 中实现了垃圾回收器来进行这部分内存空间的治理。

3.2 对象已死吗?
要正当地进行内存回收,就须要判断哪些对象是“死”的,哪些对象是“活”的。

3.2.1 援用计数算法 (reference-counting)
在对象头部维持一个援用计数器,每次减少对该对象的援用则 +1,援用失联则 -1,当援用计数器为 0 则阐明对象已死。

长处:简略高效。

毛病:无奈辨别强、软、弱、虚援用类别,而且当呈现循环援用的问题,就会成锁,造成内存溢出。

public class A{
    /* 非循环援用状况下 */
    B b1 = new B();//b1 指向对象 B1 的援用计数器 counter=1
    b1 = null; //b1.counter=0, 回收 b1 指向对象的内存
    /* 循环援用 */
    B b2 = new B();//b2 指向对象 B2 的 counter=1;
    B b3 = new B();//b3 指向对象 B3 的 counter=1;
    
    b2.instance = b3;//B2 的 counter=2; 维持着两个援用,一个来自于 b2, 一个来自于 B3.instance
    b3.instance = b2;//B3 的 counter=2;
    
    b2 = null; //B2 的 counter = 1; 维持着来自于 B3.instance 的援用
    b3 = null;//B3 的 counter = 1;
    
    // 尽管在程序中咱们曾经无法访问到 B2 和 B3 的空间了,然而单方相互援用,彼此维持着 counter= 0 的状况,若是应用援用计数算法,这两个内存永远无奈回收也无奈应用了。}
class B{public Object instance = null;}

3.2.2 可达性剖析算法 (Gc Roots Tracing)
通过一系列 GC Roots 对象为终点,向下搜寻,走过的门路成为援用链。而当一个对象到任一 GC Roots 对象都没有援用链相连的时候,则证实对象是不可用的。

在可达性剖析中被标记为不可达的对象并不意味着“死亡”,它会被临时标记并且进行一次筛选, 筛选条件是是否有必要执行 finalize()办法, 如果被断定有必要执行 finaliza()办法,就会进入 F -Queue 队列中,并有一个虚拟机主动建设的、低优先级的线程去执行它。稍后 GC 将对 F -Queue 中的对象进行第二次小规模标记。如果这时还是没有新的关联呈现,那基本上就真的被回收了。

在可达性剖析算法能够作为 GC Roots 对象的蕴含上面几种状况:

a)虚构栈中的局部变量表援用的对象

b)本机办法栈中的 JNI 所援用的对象

c)办法区的动态变量和常量所援用的对象

如图所示,reference1,reference2,reference3 作为 GC Roots 有以下三条援用链

reference1–> 对象实例 1

reference2–> 对象实例 2

reference3–> 对象实例 4 –> 对象实例 6

而对象实例 3 和对象实例 5 则是这次可达性剖析中被标记为不可达对象的实例

3.2.3 援用类型
在援用计数法中提到了四种援用类型,强援用、软援用、弱援用和虚援用,这里列一下概念。

强援用(Strong Reference)

强援用则是咱们程序中常常见到的,譬如“Object obj = new Object()”这类援用,只有强援用还存在,垃圾回收器永远不会将被援用的对象回收。

软援用(Soft Reference)

软援用是形容一些还有用但不是必须的对象,被软援用的对象在零碎将要产生内存溢出前会被列入回收范畴。

弱援用(Weak Reference)

弱援用也是形容非必须对象,而且比软援用更弱。无论是否产生内存溢出,弱援用对象都会在下一次垃圾回收产生时被回收。

虚援用(Phantom Reference)

虚援用也称为幽灵援用或者幻影援用,它是最弱的一种援用关系。一个对象是否有虚援用的存在,齐全不会对其生存工夫形成影响,也无奈通过虚援用来获得一个对象实例。为一个对象设置虚援用关联的惟一目标就是心愿能在回收这个对象时收到一个零碎告诉。

3.2.4 办法区回收
能够看到后面更多地着眼于堆的垃圾回收,这部分垃圾回收效率较高,而办法区的垃圾收集效率则远低于此。

办法区垃圾回收次要是两局部内容:废除的常量和无用的类。

判断废除常量:个别是判断没有该常量的援用。

判断无用的类:

a)该类的所有实例都曾经被回收,java 堆中不存在该类的实例

b)加载该类的 ClassLoader 曾经被回收

c)该类对应的 java.lang.Class 对象没有被援用,并且无奈通过反射机制被拜访。

3.3 内存回收办法
3.3.1 标记 - 革除
就是最简略的将须要回收的对象标记进去,而后把这些对象在内存中的信息革除。

毛病:效率不高,空间会产生大量碎片。

3.3.2 标记 - 整顿算法:标记 - 革除 - 压缩

这个算法在标记 - 革除的算法之上再进行一下压缩空间,从新挪动动向的过程。因为压缩空间须要肯定的工夫,会影响垃圾收集的效率。

3.3.3 复制算法:标记 - 革除 - 复制

把内存分为两个空间,一个空间用来负责装载失常的对象信息,另一个内存空间用于垃圾回收。每次把其中一个空间存活的对象都复制到另一个空间,而后再把这个空间一次性删除。这个算法比标记 - 革除 - 压缩效率高,然而须要两块空间,对内存要求比拟大,内存的利用率较低,实用于短生存期的对象。

3.3.4 分代回收
基于对象的生存期长短,将对象分为新生代、老年代,针对性地应用内存回收算法。

3.3.4.1 新生代
新生代通常存活工夫较短,因而基于标记 - 革除 - 复制算法来进行回收。

3.3.4.2 老年代
在垃圾回收屡次,如果对象依然存活,并且新生代的空间不够,则对象会寄存在老年代。

老年代采纳的是标记 - 革除 - 压缩算法。应用标记 - 革除来回收空间时会产生很多系统空间(即浮动垃圾),当老年代的系统空间不足以调配的时候,就会采纳压缩算法,在压缩的时候,利用须要暂停。

3.3.4.2 长久代
这部分空间次要寄存 java 办法区的数据以及启动类加载器加载的对象,这一部分对象通常不会被回收。

4、总结
明天先写到这里,明天这只是一部分,还有跟多没有深刻,比方 Hotspot,性能优化,毁坏双亲委托模型,线程与内存模型等等,前期须要再看看,大部分是从他人口中挑挑拣拣过去,然而每个人形容并重不一,本人写的时候才是建设本人的思路概念。

以上就是明天 Java 和 JVM 的常识分享,感激大家观看,也欢送大家交换探讨,该文章若有不正确的中央,心愿大家多多包涵。

若内容对小伙伴有帮忙,帮忙点个赞反对一下哦~~~ 你们的反对就是我创作的能源~

正文完
 0