JVM学习
Kang Lv3

1、JVM位置

JVM位置

2、JVM的体系结构

JVM体系结构

3、类加载器

类加载过程:

  1. 加载
    • 将class文件字节码内容加载到内存中,并对这些静态数据转换成方法区的的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
  2. 链接
    • 验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  3. 初始化
    • 执行类构造器< clinit >()方法的过程,将所有类变量的赋值动作和静态代码块的语句运行
    • 初始化一个类时,如果其父类还没有初始化,则先触发其父类的初始化
    • 虚拟机会保证一个类的< clinit >()方法在多线程环境中被正确加锁和同步

三种类加载器

  1. 启动(Bootstrap)类加载器或根加载器
    • 启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 /lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
  2. 扩展(Extension)类加载器
    • 扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。
  3. 系统(System)类加载器/应用程序加载器
    • 也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

4、双亲委派机制

某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

5、沙箱安全机制

Java安全模型的核心就是Java沙箱。沙箱机制就是讲Java代码限定在虚拟机JVM特定的运行范围中,并且严格限制代码对本地资源的访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。

沙箱主要限制系统资源访问,例如:CPU、内存、文件系统、网络。不同级别的啥想对这些资源访问的限制也可以不一样

当前最新的安全机制实现,引入了域(Domain)的概念。虚拟机吧所有代码加载到不同的系统域和应用域,系统域部分专门负责与关键资源进行交互。而各个域应用部分则通过系统域的部分代理来对各种需要的资源进行访问。虚拟机中不同的受保护域(Protected Domain),对应不一样的权限(Permission)。

通俗来说就是虚拟机把代码加载到拥有不同权限的域里,然后代码就拥有了该域的所有权限。这样就能控制不同代码拥有不同调用操作系统和本地资源的权限

沙箱包括:

  • 字节码校验器
  • 类装载器

6、Native

凡是带了native关键字的,说明java的作用范围达不到了,会去调用底层C语言的库。

加上native的语句:

  • 会进入本地方法栈
  • 调用本地方法接口(JNI)

JVM在内存中专门开辟了一块标记区域:Native Method Stack 登记native方法 在最终执行的时候,加载本地方法库中的方法通过JNI

7、方法区

方法区是被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如:构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享空间

  • 静态变量(static)、常量(final)、类信息Class(构造方法、接口定义)、运行时的常量池存在于方法区中,但是实例变量存在于堆内存中,和方法区无关 (jdk8后 常量池放在了堆里)

8、栈

栈:先进后出 后进先出

队列:先进先出 后进后出 (FIFO :First Input First Output)

程序运行就是一个栈,main方法先执行 所以main方法先进栈,然后后面调用的方法以此进栈,后面的方法执行完了弹出栈最后弹出的是main(),所以程序的main方法都是最后结束

栈:栈内存,主管程序的运行,生命周期和线程同步。线程结束,栈内存也就释放了,对于栈来说 不存在垃圾回收问题 一旦线程结束 栈就over

栈里压的东西:8大基本类型、对象引用、实例的方法

栈运行的原理:栈帧

栈溢出:StackOverflowErroe,如果线程请求的栈深度大于虚拟机所允许的深度,将抛出此异常,如无线递归、死循环
无线递归为什么会栈溢出:因为每次调用方法所产生的的资源都存放在虚拟机栈中,如果一直递归,就会填满栈。

虚拟机栈存放什么:局部变量表、操作数栈、动态链接、方法出口
局部变量表存放了什么:存放了编译期的各种基本数据类型(八大基本类型),对象引用类型(不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄)

栈、堆和方法区

9、三种JVM

  • Sun公司 HotSpot
  • BEA公司 JRockit
  • IBM J9VM

10、堆(Heap)

一个JVM只有一个堆内存,堆内存的大小是可以调节的。

类加载器读取了类文件后,一般会把类的具体实例、方法、常量、变量放在堆中,保存我们所有引用类型的真实对象;

堆内存中还要细分为三个区域:

  • 新生区(伊甸园区) Young/New
  • 老年区 Old
  • 永久区 Perm

GC垃圾回收主要在伊甸园区和养老区
堆内存满了会报出OOM(OutOfMemoryError:java heap space)错误,堆内存不够。

堆

在JDK8以后,永久存储区改了名字 叫元空间,元空间逻辑上存在,但物理上可能并不存在。

新生区(Eden)、老年区(Old)

新生区包括伊甸园区和幸存者区
伊甸园区,所有的对象都是在伊甸园区new出来的。当伊甸园区装满就会进行一次轻GC。假设该对象在GC垃圾回收中能存活下来一次 就进入幸存者区
幸存者区(from,to):当幸存者区装满 就会触发重GC 存活下来的进入养老区。幸存者区是动态的,from,to区域会互换

经过研究,99%的对象都是临时对象,不会走到养老区。

永久区(Perm)

该区域常驻内存。用来存放一些JDK自身携带的Class对象、Interface元数据。存储的是Java运行时的一些环境或类信息。这个区域不存在垃圾回收。关闭虚拟机的时候就会释放永久区的内存。

一个启动类加载了大量的第三方jar包,或者tomcat部署了太多的应用,或者大量的动态生成的反射类。就会导致永久区内存被装满,就会产生OOM。

方法区存在于永久区内(也叫非堆(只是个名词,其实也是堆))

JDK1.6之前:永久代,常量池是在方法区
JDK1.7:永久代,但是慢慢退化了(去永久代),常量池在堆中
JDK1.8之后:无永久代,常量池在元空间

堆2

11、GC(垃圾回收机制)

作用区:堆(堆和方法区)

JVM在进行GC时,有三个区域:

  • 伊甸园区
  • 幸存区(from,to)
  • 老年区

JVM在进行GC垃圾回收时,并不是对这三个区域统一回收,大部分时候,回收都是伊甸园区

GC有两类:轻GC(普通GC),重GC(全局GC\Full GC)

GC的算法有:标记清除法、标记压缩法、复制算法、引用计数法

  • 引用计数法:各个对象每用一次,该对象计数器就+1(计数器本身也要有消耗)。当计数器为0时,说明该对象没有,就立刻进行垃圾回收。

堆内存调优

JMM

  • 本文标题:JVM学习
  • 本文作者:Kang
  • 创建时间:2021-02-26 21:21:14
  • 本文链接:ykhou.github.io2021/02/26/JVM学习/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!