文章目录

  • VM是什么?
  • JVM是什么?
  • JVM作用是?
  • JVM特点是?
  • JVM位置在哪?
  • JVM组成?
    • 1、类加载子系统
      • 类加载的角色?
      • 类加载的过程?
        • ① 加载 Loading
        • ② 链接 Linking
        • ③ 初始化 Initialization
          • 类什么时候初始化?
          • 类的初始化顺序?
      • 类加载器分类?
      • 双亲委派机制是什么?
      • 双亲委派的优点?
      • 类的主动使用/被动使用区别?
    • 2、运行时数据区
      • 栈和堆的区别?
      • ① 程序计数器
      • ② Java虚拟机栈
        • 栈中存储的是什么?
        • 栈的运行原理是什么?
        • 栈帧的内部结构是什么?
      • ③ 本地方法栈
      • ④ java堆内存
      • HotSpot虚拟机
    • 3、执行引擎
      • 什么是执行引擎?
      • 执行引擎能干什么?
      • 概念区分:
      • 执行引擎组成:
        • ①解释器
        • ②即时编译器
          • 为什么java是半编译半解释型语言?
          • JIT编译器执行效率高为什么还需要解释器?
        • ③垃圾回收GC
          • java和c++区别?
          • 什么是垃圾回收?
          • 为什么要垃圾回收?
          • 垃圾是什么?
          • 早期垃圾回收?
          • 内存泄漏和内存溢出?
          • 垃圾回收机制是什么?
          • 自动内存管理优缺点?
          • 垃圾回收的区域有哪些?
          • 垃圾回收的相关算法?
            • ①垃圾标记阶段算法
            • 对象的finalization机制
            • "对象"生存还是死亡?
            • ②垃圾回收阶段算法
            • 标记-压缩算法和标记-清除算法的比较
            • 分代收集
          • 垃圾回收相关概念:
            • System.gc()理解
            • 内存溢出和内存泄漏
            • Stop The World
            • 对象引用
    • 4、本地方法接口
      • 什么是本地方法接口?
      • 为什么使用本地方法?

VM是什么?

VM Java Virtual Machine 虚拟机

就是一个虚拟的计算机。

是一个款软件,用来执行一些虚拟计算机指令。

分为系统虚拟机和程序虚拟机。

VMware:系统虚拟机,提供可以运行完整操作系统的软件平台。

程序虚拟机:Java虚拟机,为了执行计算机程序。

JVM是什么?

是一种执行java字节码文件的虚拟计算机,

有独立的运行机制,

是Java技术的核心,因为所有java程序都运行在java虚拟机内部。

JVM作用是?

Java虚拟机是二进制字节码的运行环境,

它负责将字节码装载到虚拟机,解释/编译对应平台上的机器码指令执行。

每一个java指令,java虚拟机中都有详细定义,如果取操作数,如何处理操作数,处理结果放在哪里;

JVM特点是?

一次编译,到处运行;

自动内存管理;

自动垃圾回收功能;

JVM位置在哪?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P1PwWS3H-1653744022330)(https://s2.loli.net/2022/05/15/Nlk1ju9vRf764qm.png)]

运行在操作系统之上,与硬件没有直接交互。

JVM组成?

  1. 类加载子系统 ClassLoader
  2. 运行时数据区 Runtime Data Area
  3. 执行引擎 Execution Engine
  4. 本地数据库 Native Interface

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tw0P9qab-1653744022331)(https://s2.loli.net/2022/05/15/CVT9LDxGJXP4F5I.png)]

  • java程序执行过程描述

    先把.java文件转换为.class字节码文件,jvm首先把字节码通过一定方式的类加载子系统把文件加载到内存中的运行时数据区,而字节码文件是jvm的一套指令集规范,并不能交给底层操作系统执行,所以需要特定的命令解析器执行引擎将字节码翻译为底层系统指令,再交给CPU去执行,而至二个过程需要调用其他语言的接口本地数据库来实现整个程序的功能。

    JVM主要任务就是负责将字节码装载到其内部,解释/编译为对应平台上的机器指令执行。

    JVM使用类加载器装载class文件。

    类加载完成后,会进行字节码校验,字节码校验通过之后JVM解释器会把字节码翻译成机器码交给操作系统执行。

    但不是所有代码都解释执行,JVM对此作了优化,比如HotSpot虚拟机,它本省体用了JIT( Just In Time )即时编译期。

  • 我们平时所说的JVM指的是 运行时数据区,程序员需要调试的是运行时数据区中的堆模块

1、类加载子系统

Class Loader Subsystem

类加载子系统负责从文件系统或网络中加载class文件,

类加载只负责class文件的加载,不管是否能够运行,

是否能否运行,是由执行引擎决定,

加载到运行时数据区

加载的类放在一块称为方法区(元空间)的内存空间

类加载子系统阶段: 加载(引导类加载器、扩展类加载器、系统类加载器) 、 链接(验证、准备、解析) 、 初始化

类加载的角色?

class文件存在硬盘上,-----> 设计师画在纸上的模板

class文件执行的时候要加载到JVM中,根据这个模板实例化出n个一模一样的实例

class文件加载到JVM中,被称为DNA元数据模板,放在方法区中

在.class—>JVM—>最终成为元数据模板,此过程就要有一个运输工具(类加载器 ClassLoader),

类加载器—> 快递员, 把class文件加载到JVM中,例如User.class文件,通过类加载器,加载到JVM中,成为元数据模板 User Class ,然后可以根据这个模板实例化出user对象

类加载的过程?

① 加载 Loading

通过类名(地址)获取此类的二进制字节流,

将这个字节流所代表的静态存储结构转为方法区(元数据)的运行时结构

在内存中生成一个代表这个类的java.lang.Class对象,作为这个类的各种数据的访问入口

② 链接 Linking

验证:检验被加载的类内部结构是否正确,并和其他类协调一致;

①验证文件格式是否一致; class文件开头有特定的文件标识

②元数据验证;对字节码描述的信息进行语义分析,保证符合JAVA语言规范的要求,

准备:负责将类的静态属性分配内存,并设置默认初始值

不包含用final修饰的static常量,在编译时进行初始化

例如:public static int value = 12;

//value 在准备阶段后的初始值是0,不是12,初始化阶段才是12;

解析: 将类的二进制数据中的符号引用替换为直接引用,(符号引用是class文件的逻辑符号,直接引用指向的方法区中的某一个地址)

③ 初始化 Initialization

类什么时候初始化?

1)创建类的实例

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

3)调用类的静态方法

4) 反射 Class.forName(“”)

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

类的初始化顺序?

初始化,就是对Static修饰的变量或语句赋值,

多个静态变量、静态代码块,按照从上到下的顺序执行,

有父类,先初始化父类,

顺序: 父类 static —> 子类static —>父类构造方法 —>子类构造方法

类加载器分类?

  • JVM角度看:分为两种

    ①引导类加载器(启动类加载器)

    ②其他所有类加载器,由java语言实现,独立存在虚拟机外部,并全部继承自抽象类java.lang.ClassLoader

  • 开发人员角度看:分为三层

    [外链图片转存中…(img-t2nbIwdR-1653744022336)]

    • (引导类加载器)启动类加载器 BootStrap ClassLoader

      c/c++实现的,嵌套在jvm外部,用来加载java核心类库,

      不是继承于java.lang.ClassLoader 没有父类加载器,

      负责加载扩展类加载器和应用类加载器,并未他们指定父类加载器,

      处于安全考虑,引用类加载器只加载存放在<JAVA_HOME>\lib目录,或者被 -Xbootclasspath 参数锁指定的路径存储放的类

    • 扩展类加载器 Extension ClassLoader

      java实现的,由sun.misc.launcher$ExtClassLoader实现,

      派生与ClassLoader类,

      从java.ext.dirs系统属性锁指定的目录中加载类库,或从jdk安装目录下的jre/lib/ext子目录(扩展目录)下加载类库,如果用户创建的Jar在此目录,会自动由扩展类加载器加载。

    • 应用程序类加载器(系统类加载器 ) Appliction ClassLoader

      java实现的,由sun.misc.Launche$AppClassLoader实现,

      派生与ClassLoader类

      加载我们自己定义的类,用于加载用户路径(classpath)上的所有类。

    ClassLoader类, 是一个抽象类,其后所有的类加载器都继承自ClassLoader (不包含启动类加载器)

双亲委派机制是什么?

  • java虚拟机对class文件采用按需加载方式,就是只有需要这个类,才会将他的class文件加载到内存中生成class对象,而且加载某个类的class文件时,java虚拟机采用的是双亲委派模式。

  • 双亲委派模式工作原理:

    1. 类加载器收到类加载请求,不会自己去先加载,而是把这个请求委托给父类的加载器去执行

    2. 如果父类加载器还有父类加载器,则进一步向上委,依次递归,请求最终将到达顶层的启动类加载器

    3. 如果父类加载器可以完成类加载任务,就成功返回;

      如果父类加载器不能完成任务,子类加载器就自己加载;

    总结一句就是:获取类加载请求,先请求下儿子,儿子拿到请求,先问下他爸,他爸能干,那就完成了加载;他爸干不了,儿子再自己干,完成加载任务。

双亲委派的优点?

  • 安全,避免用户自己编写的类动态替换Java核心类,

    如:自己编写的java.lang包中的String方法,执行的时候执行的是java核心类包中的String方法,不是我们自己定义的。

  • 避免全限定命名的类重复加载(使用findLoadClass()方法判断类是都已经加载)

类的主动使用/被动使用区别?

JVM规定,每个类或者结构被首次主动使用时才能对其进行初始化;

有主动使用,就有被动使用;

  • 区别在于:类是否会被初始化;

主动使用:导致类的初始化

  • new关键字导致类的初始化
  • 访问类的静态变量,包括读取和更新
  • 访问类的静态方法
  • 对类的反射操作
  • 初始化子类会导致父类的初始化
  • 执行该类的main函数

被动使用:不会导致类的初始化

除了上面主动使用场景,其余就是被动使用

  • 引入类的静态常量,(除需要计算的常量)

    例外:public final static int RANDOM = new Random().nextInt();//会导致类的初始化,主动使用

  • 构造某个类的数组

    Student [] students = news Student[10];

2、运行时数据区

(Runtime Data Area)

栈和堆的区别?

  • 栈:运行时单位

    栈解决的是程序的运行问题,即程序如何执行,或者说如何处理数据;

  • 堆:存储单位

    堆解决的是数据的存储问题,即数据怎么放,放在哪。

① 程序计数器

(Program Counter Register)

线程A程序计数器

线程B程序计数器

  • 是一块小的内存空间,运行速度最快的存储区域;
  • 可以看做当前线程所执行的字节码的行号指示器;
  • 每个线程都有自己的程序计数器,是线程私有的,生命周期与线程生命周期一致;
  • 是对物理PC寄存器的一种抽象模拟,
  • 字节码解释器工作时就是通过改变计数器的值来选取下一套需要执行的字节码指令;
  • 是唯一一个在java虚拟机中没有规定任何OutOfMemoryErroer情况的区域;

作用:

程序计数器是用来存储下一条指令的地址,即将要执行的指令代码,由执行引擎读取下一条指令

[外链图片转存中…(img-KCq4jqdy-1653744022339)]

[外链图片转存中…(img-eWfslI8b-1653744022340)]

② Java虚拟机栈

(Java Virtual Machine Stacks)

线程A (栈帧 : 局部变量表、操作数栈、动态链接、方法返回地址、附加信息)

线程B (栈帧 : 局部变量表、操作数栈、动态链接、方法返回地址、附加信息)

  • 是java方法执行的内存模型,

  • 每个方法执行的同时都会创建一个线帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;

  • 每个方法从调用到执行完成,都对应一个线帧在虚拟机栈中入栈到出栈的过程。

  • 每个线程在创建时都会创建也该虚拟机栈,内部保存一个栈帧,对应一次方法的调用

  • 是线程私有的

  • 生命周期和线程一致

作用:

主管Java程序的运行,保存方法的局部变量,部分结果,并参与方法的调用和返回;

特点:

快速有效的分配存储空间;

访问速度仅次于程序计数器;

JVM直接对Java栈的操作:调用方法,进栈;执行结果后出栈;

[外链图片转存中…(img-LatomQca-1653744022342)]

栈中出现的异常:

StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度

栈中存储的是什么?

  • 每个线程都有自己的栈
  • 栈中的数据以栈帧为单元存储
  • 线程上执行的每个方法各自对应一个栈帧
  • 栈帧是一个内存区块,数据集,维系着方法执行过程中的各种数据信息

栈的运行原理是什么?

  • JVM对栈的操作有两个,就是栈帧的压栈和出栈

  • 遵循"先进后出/后进先出" 原则

  • 在一个活动的线程中,一个时间点上,只有一个活动栈,

    只有当前活动的栈帧是有效的,称为当前栈

    与当前栈对应的方法,为当前方法,

    定义这个方法的类,为当前类

  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作

  • 如果在一个方法中调用了其他方法,对应的新的栈帧就会被创建处理啊,放在栈端,成为新的当前栈帧

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S0RMjldb-1653744022343)(https://s2.loli.net/2022/05/15/Q68iIoUAZnd9jLr.png)]

  • 不能在一个栈中引用另一个线程的栈帧(方法)

  • 如果当前方法调用了其他方法,方法返回之际,当前栈会传回此方法的执行结果,给前一个栈帧,接着虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧

  • java方法有两种返回方式,一种是正常的函数返回,使用return指令,另一种是抛出异常,不管哪种方式,都会导致栈帧被弹出

栈帧的内部结构是什么?

  1. 局部变量表

    • 是一组变量值存储空间
    • 用于存放方法参数和方法内部定义的局部变量
    • 基本数据类型的变量,则直接存储它的值
    • 引用类型的变量, 则存的是指向对象的引用
  2. 操作数栈

    • (或表达式栈)

    • 对表达式求值

    • 程序中的所有计算过程都是在借助于操作数栈来完成的

  3. 动态链接

    • (或指向运行时常量池的方法引用)

    • 为在方法执行的过程中有可能需要用到类中的常量,所以要有一个 引用指向运行时常量

  4. 方法返回地址

    • (或方法正常退出或者异常退出的定义)
    • 当一个方法执行完毕之后,要返回之前调用它的地方,所以在栈帧中保存一个方法返回地址

③ 本地方法栈

(Native Method Stack)

  • Java 虚拟机栈管理 java 方法的调用,而本地方法栈管理本地方法的调用

  • 是线程私有的

  • 允许被实现成固定或者是可动态扩展的内存大小.

  • 内存溢出

    如果线程请求分配的栈容量超过本地方法栈允许的最大容量抛出 StackOverflowError

  • 是用 C 语言写的.

  • 它的具体做法是在 (Native Method Stack )本地方法栈 中登记 native 方法,在( Execution Engine) 执行引擎 执行时加载本地方法库

④ java堆内存

(java heap)

  • 是java虚拟机中内存最大的一块,被所有线程共享,
  • 在虚拟机启动时创建,
  • java堆是为了存放对象实例,
  • 几乎所有的对象实例都在这里分配内存;

⑤方法区

(Method Area)

所有线程共享的,

是系统资源,是硬盘和CPU的中间桥梁,承载着操作系统和应用程序的实时运行,

用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译后的代码等数据,

HotSpot虚拟机

JVM内存布局规定java在运行过程中的内存申请、分配、管理的策略,保证了JVM的高效稳定运行,

不同的JVM对于内存的划分方式和管理机制存在部分差异,

现在最流行的HotSpot虚拟机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KDeMt2vZ-1653744022346)(https://s2.loli.net/2022/05/15/uiE9UCFPD2KyHQd.png)]

方法区:锅铲瓢盆

堆:各种食物

程序计数器:人

Java虚拟机栈:灶台

本地方法栈:冰箱中的(本地)

红色 线程共享:方法区、堆

灰色 线程私有:程序计数器、本地方法栈、java虚拟机栈

3、执行引擎

什么是执行引擎?

执行引擎是java虚拟机核心组成部分之一;

执行引擎能干什么?

负责将装载字节码文件到其内部的;

如果要让一个java程序运行起来,执行引擎拿到字节码文件后,由于字节码不能直接运行到操作系统上,因为字节码指令不等同于本地机器码指令,它内部只是包含了仅仅能够被JVM所识别的字节码指令、符号表、以及其他辅助信息;

所以要让这个java程序运行起来,就需要通过执行引擎(Execution Engine)将字节码指令解释/编译为对应平台上的本地机器指令;

简单来说,JVM中的执行引擎充当了将高级语言翻译成机器语言的译者;

概念区分:

  1. 前端编译: 指的是java程序员-字节码文件这个过程叫前端编译
  2. 执行引擎这里有两个行为:一种是解释执行,一种是编译执行(这里指的是后端编译)

执行引擎组成:

①解释器

就是当java虚拟机启动时,将字节码采用逐行解释的方法执行,将字节码文件中的内容"翻译"为对应平台的本地机器码指令执行。

②即时编译器

英文是 Just In Time Compiler

就是虚拟机将源代码一次直接编译成和本地机器平台相关的机器语言,但并不马上执行;

为什么java是半编译半解释型语言?

起初是将Java语言定义为"解释执行"还是比较准确的。再后来,Java也发展处了可以直接生成本地代码的编辑器。现在JVM在执行Java代码的时候,通常会将解释执行与编译执行二者结合起来使用。

原因:

JVM设计初衷:仅仅是为了满足Java程序实现跨平台特性,避免采用静态编译的方式由高级语言直接生成本地机器指令,从而诞生了实现解释器在运行时采用逐行解释字节码执行程序的想法;

解释器真正意义上是一个运行时"翻译者",将字节码文件中的内容"翻译"为对应平台的本地机器指令执行,执行效率低;

JIT即时编译期将字节码翻译成本地代码后,就可以做一个缓存操作,存储在方法区中的JIT缓存中(执行效率更高了)。

是否需要启动JIT编译器将字节码直接便以为对应平台的本地机器指令,则需要根据代码被调用执行的频率而定。

JIT即时编译器在运行时会针对那些被频繁调用的"热点代码"做出深度优化,将其直接编译为对应平台的本地机器码指令,以此提升Java程序员的执行性能。

一个被多次调用的方法,或者是一个方法体内部循环次数较多的循环体都可以被称之为"热点代码";

目前HotSpot VM所采用的热点探测方式是基于计数器的热点探测;

JIT编译器执行效率高为什么还需要解释器?
  1. 当程序启动后,解释器可以马上发挥作用,响应速度快,省去编译的时间,立即执行;
  2. 编译器要想发挥作用,把代码编译成本地代码,需要一定的执行时间,但是编译为本地代码后,执行效率高,就需要采用解释器和及时编译器并存的架构来换取一个平衡点;

③垃圾回收GC

GC:garbage collection

java和c++区别?

Java和C++区别就在于:垃圾收集技术和内存动态分配;c++语言没有垃圾收集技术,需要程序员手动收集。

注意:

  • 垃圾收集,是Java语言的伴生产物。早在1960年,第一门开始使用内存动态分配和垃圾收集技术的Lisp语言诞生;
什么是垃圾回收?

垃圾回收机制是Java语言拥有的但不是独有的能力,能够提高开发效率。

C++语言和java语言都是面向对象语言,但c++语言就没有垃圾回收技术,它需要程序员手动收集。

为什么要垃圾回收?
  1. 内存空间是有限的,不进行垃圾回收,迟早内存就会被消耗完,如果不断分配内存但是不回收,就没有内存可以用了,就相当于你给垃圾筐一直扔垃圾但不倒,那垃圾就溢出来了。
  2. 除了释放没用的对象,垃圾回收还会清除内存中的记忆碎片。
  3. 随着应用程序业务庞大、复杂、用户越来越多,没有垃圾回收就不能保证应用程序的正常进行。
垃圾是什么?

垃圾指的是我们在运行程序中没有任何引用指向的对象

解释一下:User u1 = new User(); u1 = null;

在这里new了一个User对象,并让u1指向新创建的这个对象,那么这个新创建的对象就是有引用指向的,这时我让u1为null,那么刚新建的对象就没有引用指向它,它就是一个垃圾了。

通俗的说:就是没有人引用你新创建的对象,那这个对象就是垃圾。

早期垃圾回收?

早期c/c++时期,垃圾回收基本是手工进行。

开发人员通过使用new关键字镜像内存申请,并使用delete关键字进行内存释放。

  • 优点:可以灵活控制内存释放空间;

  • 缺点:给开发人员带来频繁申请和释放内存的管理负担。如果有一个内存区间由于程序员编码的问题忘记被回收,那么就会产生内存泄漏,垃圾对象永远无法被清除,随着系统运行时间的不断增长,垃圾对象所消耗的内存可能持续上升,直到出现内存溢出并造成应用程序崩溃。

目前使用自动垃圾回收的思想的语言:java c# python Ruby 等

内存泄漏和内存溢出?
  • 内存泄漏 memory leak

    指程序申请了内存后,无法释放已经申请的内存空间,比如向系统申请分配内存使用 new User对象,然后让u1指向它,这时候让u1为空时,刚才new出来的对象就没有引用指向它,那它就发生了内存泄漏。

  • 内存溢出 out of memory

    指的是程序在申请内存时,没有足够的内存空间供其使用,出现内存溢出,比如申请了int 类型的变量a,但是又给他赋值了一个long类型的值,那int类型的内存空间就存不下这个值,这就发生了内存溢出。

垃圾回收机制是什么?

自动内存管理

自动内存管理优缺点?
  • 优点

    1. 自动内存管理,无需开发人员手动参与内存的分配和回收,降低了内存泄漏和内存溢出的风险。
    2. 自动内存管理机制,将程序员从繁重的内存管理中释放出来,可以更专心专注于业务开发。
  • 缺点
    1. 对于开发人员,自动内存管理就像一个黑匣子,过度依赖"自动",会弱化java开发人员在程序出现内存溢出时定位问题和解决问题的能力。
    2. 我们需要了解JVM的自动内存和内存回收原理,只有真正了解JVM是如何管理内存的,才能够在遇见OutofMemoryError时,快速地根据错误异常日志定位问题和解决问题。
    3. 当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集称为系统达到更高并发量的瓶颈时,我们必须对这些"自动化"的技术实施必要的监控和调节。
垃圾回收的区域有哪些?

  • 可以对新生代回收 频繁收集Young区
  • 可以对老年代回收 较少收集Old区
  • 可以对全栈和方法区回收 基本不收集元空间(方法区)

垃圾回收的相关算法?
①垃圾标记阶段算法

包含:

  • 引用计数算法
  • 可达性分析算法

垃圾标记阶段:主要是为了判断对象是否存活着

在堆内存中几乎存放着所有的Java对象实例,在GC执行垃圾回收之前,首先需要区别出内存哪些是存活的对象,哪些是已经死亡的对象,只有被标记会死亡的对象,GC在执行垃圾回收时,才会释放已经死亡的对象所占用的内存空间,这个过程称为垃圾标记阶段。

简而言之,就是回收前区别出哪些对象是活的、哪些对象是死的,死了的就释放内存。

那在JVM中如何标记一个死亡对象,使得GC在回收时得以区别?

就是当一个对象不被任何存活对象所引用,就判定其已死亡。

判断一个对象存活有两种方式:引用计数法和可达性分析算法

  1. 引用计数算法

    Reference Counting

    对每个对象保存一个整型的应用计数器属性,用于记录对象被引用的情况。

    对于一个对象A,只要有任何一个对象引用二楼A,则A的引用计数器就加1;当引用失效时,引用计数器减1。只要对象A的引用计数器值为0,即表示对象A不会再被使用,可进行回收。

    • 优点

      ① 实现简单;

      ② 垃圾对象便于辨识;

      ③ 判定效率高;

      ④ 回收没有延迟;

    • 缺点

      ① 需要单独的字段存储器,增加存储空间的开销;

      ② 每次赋值都需要更新计数器,伴随加法和减法操作,增加了时间开销;

      ③ 引用计数器无法处理循环引用,这是一条致命缺陷,导致Java的垃圾回收器中没有使用引用计数算法;

      p指向一个循环引用,如果这时让p重新指向为null,那么这个循环引用就没有指向它的引用,它就无法被回收,就出现了内存泄漏。

  2. 可达性分析算法

    也称为根搜索算法、追踪性垃圾收集。

    • 优点

      ① 相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是,可达性算法能够有效解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。

      ② 相对于引用计数算法,可达性分析就是Java、C#选择的。这种类型的垃圾收集通常也叫追踪性垃圾收集(Tracing Garbage Colleciton TGC)

    实现思路:

    1. 可达性分析算法是以根对象(GCRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标是都可达。

      GCRoots:根节点就是一组必须活跃的引用。

    2. 使用可达性分析算法后,内存中的存活对象都会被根对象结合直接或间接连接着,搜索所走过的路径称为引用链(Reference Chain)

    3. 如果目标对象中没有任何引用链相连,则是不可达的,就意味着该对象已经死亡,可以标记为垃圾对象。

    4. 在可达性分析算法中,只有能够被根对象集合直接或间接连接的对象才是存活对象。

    GC Root解释:

    GC Roots:根节点就是一组必须活跃的引用。

    GC Roots 可以是哪些元素?

    1. 虚拟机栈中的引用对象

      比如:各线程被调用的方法中常用的参数、局部变量等。

    2. 本地方法栈内 JNI (Java National Interface)通常说的本地方法栈,引用的对象。

    3. 方法区中类静态属性引用的对象。比如:Java类的引用类型静态变量。

    4. 方法区中常量引用的对象,比如:字符串常量池(StringTable)里的引用

    5. 所有被同步锁synchronized持有的对象。

    6. Java虚拟机内部的引用。

    7. 基本数据类型对应的Class对象,一些常驻的异常对象(如:NullPointerException、OutofMemoryError),系统加载类。

    总结:就是说,除了对空间的周边,比如:虚拟机栈、本地方法栈、方法区、字符串常量池等地方对对空间进行引用的,都可以作为GC Roots进行可达性分析。

对象的finalization机制

finalization机制: 终止机制

finalize()方法机制

对象销毁前的回调函数:finlize() ;

Java语言提供了终止(finalization)机制来允许开发人员提供对象被销毁之前的自定义处理逻辑。

就是说如果开发人员还想要在垃圾销毁之前做一些事情,就在finalize()方法中写就行。

当垃圾回收器发现没有引用指向一个对象,即发现一个垃圾,在回收此对象之前,总会先调用这个对象的finalize()方法,一个finlaize()方法只能被调用一次。

finalize()方法允许在子类中被重写,用于在对象被回收时进行资源释放。

finalize()方法通常会进行一些资源释放和清理工作,比如:关闭文件、套接字、和数据库的链接等。

Object类中的 finalize() 源码:

protected void finalize() throws Throwable {}
  • 注意!!!

    永远不要主动调用某个对象的finalize()方法,应该交给垃圾回收机制调用。理由:

    1. 在finalize()时可能会导致对象复活。
    2. finalize()方法在执行时间是没有保障的,它完全由GC线程决定,极端情况下,若不发生GC,则finalize()方法将没有执行机会。
    3. 一个糟糕的finalize()会严重影响GC的性能,比如finalize()是个死循环。
"对象"生存还是死亡?

由于finalize()方法的存在,虚拟机中的对象一般处于三种可能的状态。

如果从所有根节点都无法访问到某个对象,说明对象已经不再使用了。一般来说,此对象需要被回收。但事实上,也并非是"非死不可"的,这时候他们暂时处于"缓刑"阶段。一个无法触及的对象有可能在某一条件下"复活"自己,如果这样,那么对它立即进行回收就是不合理的。为此,定义虚拟机中的对象可能的三种状态。如下:

  1. 可触及的:从根节点开始,可以到达这个对象;

  2. 可复活的:对象的所有引用都被释放,但是对象有可能在 finalize() 中复活;

  3. 不可触及的:对象的 finalize() 被调用,并且没有复活,那么就会进入不可触及状态。不可触及的对象不可能被复活,因为 finalize() 只会被调用一次。

    只有在对象不可触及时才能被回收。

这三种状态中,是由于 finalize() 方法 存在,进行区分。

具体过程:

判定一个对象objA是否可以被回收,至少经历两次标记过程:

  1. 判定一个对象objA到 GC Roots 有没有引用链,没有引用链,进行第一次标记;

  2. 第一次标记后的进行筛选,判断次对象是否有必要执行finalize() 方法

    • 如果对象objA没有重写 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,则虚拟机视为"没有必要执行",objA被判定为不可触及;

    • 如果对象objA重写了 finalize() 方法,且还未执行过,那么 objA 会被插入到 F-Queue 队列中,由一个虚拟机自动创建、低优先级的 Finalizer 线程触发其 fianllize() 方法执行;

    • finalize() 方法是对象逃脱死亡的最后机会,稍后GC会对F-Queue队列中的对象进行第二次标记。如果objA 在finalize() 方法中与引用链上的任何一个对象建立了联系,那么在第二次标记时,objA 会被移出"即将回收"集合。之后,对象会再次出现没有引用存在的情况。

      在这个情况下,finalize() 方法不会再次调用,对象会直接变成不可触及的状态,也就是说,一个对象 finalize() 方法只会被调用一次。

代码演示 finalize() 方法可复活对象

第一次自救成功,但是由于finalize() 方法只执行一次,所以第二次自救失败;

②垃圾回收阶段算法

包含:

  • 标记-清除算法
  • 复制算法
  • 标记-压缩算法
  1. 标记-清除算法

    什么是标记-清除算法?

    标记:Collertor 从引用根节点开始遍历,标记所有被引用的对象。

    清除:Collettor对堆内存从头到尾进行线性遍历,如果发现某个对象在其Header只中没有标记为可达对象,则将其回收。

    执行过程:当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(stop the world ),然后进行两个步骤的工作,标记和清除。

    • 优点:算法容易理解
    • 缺点:①效率不高;②在进行垃圾回收的时候,需要停止整个程序,用户体验差;③清理出来的内存空间不连续,产生内存碎片,需要一个空闲列表;

    注意:什么是清除?

    清除不是清空,是把需要清除的对象地址保存在空闲的地址列表中,下次有新对象需要加载时,判断垃圾的位置空间是否够,如果够,就存放(也就是覆盖原有的地址);

  2. 复制算法

    为了解决标记-清除算法在垃圾回收效率方面的缺陷,它将可以内存按容量划分为大小相等的两块,每次只使用其中的一块。在垃圾回收时将正在使用的内存中的存活对象赋值到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。

    • 优点:

      ①没有标记和清除过程,实现简单,运行高效。

      ②复制过去后保证空间的连续性,不会出现"碎片"问题。

    • 缺点:

      ①需要两倍的内存空间。

      ②对于G1这种拆分为大量的 region(区) 的垃圾回收,复制而不是移动,意味着GC需要维护 region 之间对象引用关系,不管是内存占用或者时间开销也不小。

    复制算法的应用场景?

    如果系统中的垃圾对象很多,复制算法需要复制的存活对象数量并不会太大,效率比较高;

    老年代大量的对象存活,那么复制的对象将会有很多,效率会很低;

    新生代,对常规应用的垃圾回收,一次通常可以回收70%-99%的内存空间。回收性价比很高,所以现在的商业虚拟机都是用这种手机算法回收新生代。

  3. 标记-压缩算法

    背景

    复制算法的高效是建立在存活对象少、垃圾对象多的前提下。

    这种情况在新生代进程发生,但是在老年代中,更常见的情况是大部分对象都是存活对象,如果依然使用复制算法,由于存活的对象较多,复制的成本就很高。

    所以,基于老年代回收的特性,需要使用其他算法。

    标记-清楚算法可以应用到老年代中,但是该算法不仅执行效率低下,而且在执行完内存回收后还会产生内存碎片,所以JVM设计者需要在其基础上进行该进。

    标记压缩算法执行过程

    第一阶段和标记-清除算法一样,从根节点开始标记所有被引用对象。

    第二阶段将所有存活对象压缩到内存的一端,按顺序排放,之后,清除边界外的所有的空间。

    标记压缩算法优缺点:

    优点:

    ①消除了标记-清除算法中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM只需要只有一个内存的起始地址即可。

    缺点:

    ①标记-压缩效率低于复制算法;

    ②移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址;

    ③移动过程中,需要全程暂停用户应用程序。即STW

标记-压缩算法和标记-清除算法的比较

标记-压缩算法的最终效果是等同于标记-清除算法执行完成后,再进行一次内存的碎片整理,因此,也可以把它称之为标记-清除-压缩(Mark-Sweep-Compact)算法。

区别:

标记-清除算法是一种非移动式的回收算法(空闲列表记录位置),标记-压缩是移动式的。

是否移动回收后的存活对象是一项优缺点并存的风险决策。

可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉。

这样一来,当我们需要给新对象分配内存时,JVM只需要只有一个内存的首地址就行,这比维护一个空闲列表少了很多开销。

垃圾回收算法小结

从效率上来说,赋值算法是老大,但是浪费了大量内存。

而为了兼顾三个指标,标记-整理算法相对来说更平滑一些,但是在效率上不尽人意,它比复制算法多了一个标记的阶段,比标记-清除算法多了一个整理内幕才能的阶段。

分代收集

为什么要使用分代收集?

前面所有的算法中,没有一个算法可以完全代替其他算法,它们都具有自己的优势和特点。

所以分代收集就应用而生。

分代收集,基于这样一个事实条件:不同的对象的生命周期不一样。

因此,不同生命周期的对象可以采用不同的收集方式,以便提高回收效率,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同的回收算法,以提高垃圾回收的效率。

在java程序运行过程中,会产生大量的对象,其中有些对象是与业务信息相关:

比如 Http 请求的 Session 对象、线程、Socket连接,这类对象和业务直接挂钩,因此生命周期比较长。

但是还是有一些对象, 主要是程序运行过程中生成的临时变量,这些对象生命周期比较短,比如:String 对象,由于其不变的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。

目前几乎所有的GC都采用分代收集算法执行垃圾回收。

在HotSpot中,基于分代的概念,GC所使用的内存回收算法必须结合年轻代和老年代各自的特点。

年轻代 Young Gen

年轻代特点:区域相对于老年代较小,对象生命周期短、存活率低,回收频繁。

这种情况复制算法的回收整理,速度是最快的。

复制算法的效率只和存活对象大小有关,因此很适用于年轻代的会回收。

而复制算法内存利用率不高的问题,通过HotSpot 中的两个 survivor 的设计得以缓解。

老年代 Tenured Gen

老年代特点:区域较大,对象生命周期长、存活率高,回收不及年轻代频繁。

这种情况存在大量存活率高的对象,复制算法就不太合适,一般使用标记-清除或者标记-整理的混合实现。

  1. Mark阶段的开销和存活对象的数量成正比。
  2. Sweep阶段的开销与所管理的区域大小成正比。
  3. Compact阶段的开销与存活对象的数据成正比。

分代的思想被现有的虚拟机广泛应用,几乎所有的垃圾回收器都区分老年代和新生代。

垃圾回收相关概念:
System.gc()理解

在默认情况下,通过System.gc()或者Runtime.getRuntime().gc()的调用,会显式地触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。

但是System.gc()调用附带一个免责声明,无法保证对垃圾回收器的调用(不能确保即时生效)。

JVM实现者可以通过System.gc()调用来决定JVM的GC行为。

一般情况下,垃圾回收应该是自动进行的,无需手动触发,否则就过于麻烦了。在一些特殊情况下,我们可以在运行之间调用System.gc()。

内存溢出和内存泄漏

内存溢出

内存泄漏和内存溢出都会引发程序崩溃。

现在GC的发展很快,一般情况下,除非应用程序占用的内存增长非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现OOM( Out OfMemory 内存溢出)的情况。

大多数情况下,GC会进行各种年龄段的垃圾回收,实在不行,来一次独占式的Full GC操作,这时候会手机大量的内存,供给应用程序继续使用。

javadoc中对OutofMemoryError的解释是,没有内存空闲,并且垃圾收集器也无法提供更多的内存。

内存泄漏

内存泄漏也称之为"存储泄漏"。

严格的说,只有对象不会再被程序用到了,但是GC又不能回收他们的情况,才叫内存泄漏。

或者称为,没有引用去指向的对象,没有办法进行回收,就会发生内存泄漏。

但是实际情况下右一些不太好实践(或者疏忽)会导致对象的生命周期变得很长,甚至导致OOM,也可以叫做宽泛意义山的"内存泄漏"。

尽管内存泄漏不会立即引起程序崩溃,但是一但发生内存泄漏,程序中的可用内存就会被逐渐蚕食,直至耗尽所有内存,最终出现OutofMemory异常,导致程序奔溃。

注意!上面所指的存储空间不是物理内存,而是指虚拟内存大小,这个虚拟内幕才能大小取决于磁盘交换区设定的大小。

常见实例

①单例模式

单例的生命周期和应用程序是一样长的,所以在单例程序中如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内幕才能泄漏的产生。

②一些提供close()的资源未挂壁导致内存泄漏

数据库连接dataSourcse.getConnection(),网络连接socket和io连接必须手动close,否则不能被回收的。

Stop The World

中止;

简称STW,指的是GC事件发生过程中,会产生应用程序的停顿。

停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为STW。

可达性分析算法中枚举根节点(GC Roots)会导致所有java执行线程停顿,为什么需要停顿所有java执行线程呢?

  1. 分析工作必须在一个能确保一致性的快照中进行。

    一致性指的是整个分析期间整个执行系统看起来像被冻结在某个时间节点上。

    如果出现分析过程中对象引用关系还在不断变化,那么分析结果的准确性无法保证。

  2. 被STW中断应用程序线程会在完成GC后恢复,频繁中断会让用户感觉像是网速不快造成的电影卡带一样,所以我们需要减少STW的发生。

  3. STW事件和采用哪款GC无关,所有GC都有这个事件。

  4. 越优秀,回收效率越高,尽可能缩短了暂停时间。

STW是JVM在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。

对象引用

我们希望能描述这样一类对象:当内存空间还足够时,则能保留在内存中;如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。

既偏门又非常高频的面试题:

强引用、软引用、弱引用、虚引用有什么区别?

具体使用场景是什么?

在 JDK1.2 版之后,Java 对引用的概念进行了扩充,将引用分为:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)

这4种引用强度依次逐渐减弱。

除强引用外,其他3种引用均可以在java.lang.ref 包中找到它们的身影。

如下图,显示了这 3 种引用类型对应的类,开发人员可以 在应用程序中直接使用它们。

Reference 子类中只有终结器引用是包内可见的,其他 3 种引用类型均为 public,可以在应用程序中直接使用。

强引用(StrongReference)

最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。 无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用 的对象。宁可报 OOM,也不会 GC 强引用。

软引用(SoftReference)

在系统将要发生内存溢出之前,将会把这些对象列入回收范围之中进行第二次回收。如果这次回收后还没有足够的内存,才会抛出内存溢出异常。

弱引用(WeakReference)

被弱引用关联的对象只能生存到下一次垃圾收集 之前。当垃圾收集器工作时,无论内存空间是否足够,都会回收掉被弱引用关联的对象。

虚引用(PhantomReference)

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获得一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

  • 强引用 :不回收

    在 Java 程序中,最常见的引用类型是强引用(普通系统 99%以上都是强引用), 也就是我们最常见的普通对象引用,也是默认的引用类型。

    当在 Java 语言中使用 new 操作符创建一个新的对象,并将其赋值给一个变量的时候,这个变量就成为指向该对象的一个强引用。

    只要强引用的对象是可触及的,垃圾收集器就永远不会回收掉被引用的对象。 只要强引用的对象是可达的,jvm 宁可报 OOM,也不会回收强引用。

    对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为 null,就是可以当做垃圾被收集了,当然具体回收时机还是要看垃圾收集策略。 相对的,软引用、弱引用和虚引用的对象是软可触及、弱可触及和虚可触及的, 在一定条件下,都是可以被回收的。

    所以,强引用是造成 Java 内存泄漏的主要原因之一。

    public class StrongReferenceTest {public static void main(String[] args) {StringBuffer str = new StringBuffer ("Hello,world");StringBuffer str1 = str;str = null;System.gc();try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(str1);}
    }
    
  • 软引用:内存不足时即回收

    软引用是用来描述一些还有用,但非必需的对象。

    只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回 收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。

    注意,这里的第 一次回收是不可达的对象 。

    软引用通常用来实现内存敏感的缓存。比如:高速缓存就有用到软引用。

    如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。

    垃圾回收器在某个时刻决定回收软可达的对象的时候,会清理软引用,并可选地把引用存放到一个引用队列(Reference Queue)。

    类似弱引用,只不过 Java 虚拟机会尽量让软引用的存活时间长一些,迫不得已才清理。

    Object obj = new Object();// 声明强引用
    SoftReference<Object> sf = new SoftReference<>(obj);
    obj = null; //销毁强引用
    
  • 弱引用:发现即回收

    弱引用也是用来描述那些非必需对象,只被弱引用关联的对象只能生存到下一次垃圾收集发生为止。

    在系统 GC 时,只要发现弱引用,不管系统堆空间使用是否充足,都会回收掉只被弱引用关联的对象。

    // 声明强引用
    Object obj = new Object();
    WeakReference<Object> sf = new WeakReference<>(obj);
    obj = null; //销毁强引用
    

    弱引用对象与软引用对象的最大不同就在于:

    当 GC 在进行回收时,需要通过算法检查是否回收软引用对象,而对于弱引用对象,GC 总是进行回收。

    弱引用对象更容易、更快被 GC 回收。

  • 虚引用:对象回收跟踪

    也称为“幽灵引用”或者“幻影引用”,是所有引用类型中最弱的一个。

    一个对象是否有虚引用的存在,完全不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它和没有引用几乎是一样的,随时都可能被垃圾回收器回收。

    虚引用必须和引用队列一起使用。

    虚引用在创建时必须提供一个引用队列作为参数。

    // 声明强引用
    Object obj = new Object();
    // 声明引用队列
    ReferenceQueue phantomQueue = new ReferenceQueue();
    // 声明虚引用(还需要传入引用队列)
    PhantomReference<Object> sf = new PhantomReference<>(obj, phantomQueue);
    obj = null
    

4、本地方法接口

Native Method Interface

[外链图片转存中…(img-4YoH0N4T-1653744022361)]

什么是本地方法接口?

简单来讲,一个 Native Method 就是一个 java 调用非 java 代码的接口。

一个 Native Method 是这样一个 java 方法:该方法的底层实现由非 Java 语言实现, 比如 C。

这个特征并非 java 特有,很多其他的编程语言都有这一机制在定义一个 native method 时,并不提供实现体(有些像定义一个 Java interface),因为其实现体是由非 java 语言在外面实现的。

关键字 native 可以与其他所有的 java 标识符连用,但是 abstract 除外。

为什么使用本地方法?

Java 使用起来非常方便,但是有些层次的任务用 java 实现起来不容易,当我们对程序的效率很在意时,问题就来了。

  1. 与 java 环境外交互:

    有时 java 应用需要与 java 外面的环境交互,这是本地方法存在的主要原因。

    你可以想想 java 需要与一些底层系统,如某些硬件交换信息时的情况。本地方法是这样的一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解 java 应用之外的繁琐细节。

  2. Sun’s Java

    Sun 的解释器是用 C 实现的,这使得它能像一些普通的 C 一样与外部交互。

    jre 大部分是用 java 实现的,它也通过一些本地方法与外界交互。

    例如:类 java.lang.Thread 的 setPriority()方法是用 Java 实现的,但是它实现调用的是该类里的本地方法 setPriority0()。

【JVM】VM是什么?JVM是什么?JVM作用是什么?JVM特点?JVM位置?JVM组成?相关推荐

  1. java jvm调优面试题_【Java面试题第一期】有没有jvm调优经验?调优方案有哪些?...

    ​1. 调优时机: a. heap 内存(老年代)持续上涨达到设置的最大内存值: b. Full GC 次数频繁: c. GC 停顿时间过长(超过1秒): d. 应用出现OutOfMemory 等内存 ...

  2. 聊聊jvm的内存结构, 以及各种结构的作用

    前言 在JVM的管控下,Java程序员不再需要管理内存的分配与释放,这和在C和C++的世界是完全不一样的.所以,在JVM的帮助下,Java程序员很少会关注内存泄露和内存溢出的问题.但是,一旦JVM发生 ...

  3. JVM调优实战:三、Class文件是如何被加载到JVM的?

    Class文件是如何被加载到JVM的? 前面我们讲了class的文件结构,相信大家对class文件的构成以及他们之间的排列顺序都有了比较深的了解.说一个小伙伴可能会问的问题,了解学习class文件结构 ...

  4. 深入理解java虚拟机 - jvm高级特性与最佳实践(第三版)_深入理解Java虚拟机,JVM高级特性与最佳实践!...

    第一部分 走进Java 第二部分 自动内存管理机制 第三部分 虚拟机执行子系统 参考资料: 书籍,网站资源 Java不仅仅是一门编程语言,还是一个由一系列计算机软件和规范形成的技术体系,这个技术体系提 ...

  5. 分享一个JVM的在线图,特别细致和全面,绝对是大神级别制作的jvm图,我们一起来参观一下吧

    为了不侵犯大神的知识劳动产权,我这里直接引用原地址:https://processon.com/view/5c749debe4b0f9fba6921d15 建议收藏这张图.做一个书签什么的

  6. 单个JVM下支撑100w线程数vm.max_map_count

    I.环境要求: 1.64bit Linux 2.64bit JDK 3.Memory够大,512GB 4.cpu:64 processors II.测试工具:[DieLikeADog.java] ja ...

  7. HotSpot VM垃圾收集器——Serial Parallel CMS G1垃圾收集器的JVM参数、使用说明、GC分析

    目录 HotspotVM的垃圾收集器简介 1. Serial Collector 2. Parallel Collector(throughput collector) 3. Concurrent M ...

  8. 面试高频!JVM必备教程~

    请你谈谈你对JVM的理解?Java8虚拟机和之间的变化更新? JVM类加载器是怎么样的?有几种? 什么是OOM,什么是StackOverFlowError? 怎么分析? JVM常用调优参数有哪写? G ...

  9. [译]深入理解JVM

    深入理解JVM 原文链接:http://www.cubrid.org/blog/dev-platform/understanding-jvm-internals 每个使用Java的开发者都知道Java ...

最新文章

  1. mysql 重装问题
  2. 22.循环控制.rs
  3. redis ubuntu php 5.2,ubuntu 14.04下简易安装php5.5 + apache2 + redis + mysql
  4. react 解决 setState 异步问题
  5. python dict批量选择_这一定是你见过最全面的python重点
  6. 内推熟人来自己公司一定要慎重
  7. python已停止工作请关闭该程序_解决PyCharm的Python.exe已经停止工作的问题
  8. World中利用宏命令批量删除页眉和页脚(亲测好用!)
  9. 小蓝本 第一本《因式分解技巧》第四章 拆项与添项 笔记(第四天)
  10. Spring —— Spring 手册官网下载地址
  11. Redis分布式锁剖析和几种客户端的实现
  12. 使用DHT11和51单片机进行温湿度的读取(保证好用版本)
  13. 计算机桌面图标怎么显示出来,显示桌面图标不见了怎么办?显示桌面图标不见了解决方法...
  14. C++之vector的高维数组
  15. vlc的应用之六:简单的视频点播系统(B/S)的实现
  16. react-navigation Navigation使用
  17. apk安装提示:Failure [INSTALL_FAILED_DUPLICATE_PERMISSION perm=XXX]
  18. 吕鑫MFC学习系列七
  19. github优秀项目分享:基于yolov3的轻量级人脸检测、增值税发票OCR识别 等8大项目...
  20. CMake入门二——子目录的嵌套

热门文章

  1. 阿里巴巴已offer:Java实习五面详细面经(附解答)
  2. Promise 大全
  3. 制作SpringBoot启动图案
  4. ArtFin艺术饭 | 文艺资产观察:「心直酒快」谈自酿酒安全性的几个问题
  5. 某程序员吐槽:买房自己家出400万首付,女朋友家里一分不出还要求加名字,怎么说服女朋友放弃写名?...
  6. 西南民族大学第十届校赛(同步赛)(F题——集训队脱单大法:这是一道只能由学姐我自己出数据的水题)
  7. Apple Store 的翻新机怎样?
  8. 实战案例:世界银行全球 GDP 数据分析
  9. 有一种银行叫生命银行
  10. python画三维几何图形拼成的图案_Scratch3.0少儿编程案例:循环画窗花