JVM入门学习笔记

学习视频链接:B站 遇见狂神说

认识JVM

JVM,全称Java Virtual Machine,英文为Java虚拟机,“虚拟机(Virtual Machine)是指通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统”,但是 虚拟机本质还是该计算机系统的一个进程,为了方便描述,我们把整个计算机当成一幢大楼,而虚拟机则是某一个楼层。大楼划分了一个区域给一个楼层,让这个楼层自己管理自己,也就对应着,计算机划分了一个内存给JVM,让JVM自己管理自己。JVM的内存区域划分为两大块,线程隔离的区域和线程共享的区域。

在这里插入图片描述

JVM体系结构

在这里插入图片描述

三种JVM

  • Sun公司 HotSpot Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
  • BEA JRockit
  • IBM J9VM

类加载器

启动类加载器

(父)启动类加载器(BootstrapClassLoader):rt.jar

扩展类加载器

(母)扩展类加载器(ExtClassLoader): ext/*.jar

应用类加载器

App/SystemClassLoader: 应用/系统类加载器,加载当前classpath的所有类。

自定义类加载器

XxxClassLoader:用户自定义的类加载器,默认使用双亲委派,委托上级来加载。

工作流程

如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

在这里插入图片描述

在这里插入图片描述

生命周期

在这里插入图片描述

源码分析

protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先检查这个classsh是否已经加载过了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // c==null表示没有加载,如果有父类的加载器则让父类加载器加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        //如果父类的加载器为空 则说明递归到bootStrapClassloader了
                        //bootStrapClassloader比较特殊无法通过get获取
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {}
                if (c == null) {
                    //如果bootstrapClassLoader 仍然没有加载过,则递归回来,尝试自己去加载class
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

双亲委派机制

Java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为“父”。“父”无法加载到,再从扩展类加载器中加载,这个称为“母”。

双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

  1. 类加载器收到类加载的请求
  2. 将这个请求向上委托为父类加载器去完成,一直向上委托,直到启动类加载器
  3. 启动类加载器检查是否能够加载当前的这个类,能加载就结束,使用当前的加载器,否则抛出异常,通知子加载器进行加载
  4. 重复步骤 3

面试问题

  • 为什么需要双亲委派机制?(也就是双亲委派的优点)

    ①双亲委派机制使得类加载出现层级,父类加载器加载过的类,子类加载器不会重复加载,可以防止类重复加载

    ②使得类的加载出现优先级,防止了核心API被篡改,提升了安全,所以越基础的类就会越上层进行加载,反而一般自己的写的类,就会在应用程序加载器(Application)直接加载。

  • 如何打破双亲委派?

    ①自定义类加载器,重写loadClass方法

    ②使用线程上下文类加载器

沙箱安全机制

什么是沙箱

沙箱是一个限制程序运行的环境。沙箱机制就是将 Java 代码限定在虚拟机(JVM)特定的运行范围中,并且严格限制代码对本地系统资源访问,通过这样的措施来保证对代码的有效隔离,防止对本地系统造成破坏。沙箱主要限制系统资源访问,那系统资源包括什么?——CPU、内存、文件系统、网络。不同级别的沙箱对这些资源访问的限制也可以不一样。

所有的Java程序运行都可以指定沙箱,可以定制安全策略。

沙箱的基本组件

  • 字节码校验器(bytecode verifier):确保Java类文件遵循Java语言规范。这样可以帮助Java程序实现内存保护。但并不是所有的类文件都会经过字节码校验,比如核心类。

  • 类装载器(class loader):其中类装载器在3个方面对Java沙箱起作用

    • 它防止恶意代码去干涉善意的代码;
    • 它守护了被信任的类库边界;
    • 它将代码归入保护域,确定了代码可以进行哪些操作。

虚拟机为不同的类加载器载入的类提供不同的命名空间,命名空间由一系列唯一的名称组成,每一个被装载的类将有一个名字,这个命名空间是由Java虚拟机为每一个类装载器维护的,它们互相之间甚至不可见。

类装载器采用的机制是双亲委派模式

  1. 从最内层JVM自带类加载器开始加载,外层恶意同名类得不到加载从而无法使用;
  2. 由于严格通过包来区分了访问域,外层恶意的类通过内置代码也无法获得权限访问到内层类,破坏代码就自然无法生效。
  • 存取控制器(access controller):存取控制器可以控制核心API对操作系统的存取权限,而这个控制的策略设定,可以由用户指定。

  • 安全管理器(security manager):是核心API和操作系统之间的主要接口。实现权限控制,比存取控制器优先级高。

  • 安全软件包(security package):java.security下的类和扩展包下的类,允许用户为自己的应用增加新的安全特性,包括:

    • 安全提供者
    • 消息摘要
    • 数字签名
    • 加密
    • 鉴别

Native

Java在内存区域中专门开辟了一块标记区域——本地方法栈,用来登记native方法,凡是带了native关键字的,会进入到本地方法栈中,调用本地方法接口(JNI),在最终执行时,通过JNI(Java Native Interface)加载本地方法库中的方法。

**JNI作用:扩展Java的使用,融合不同的编程语言为Java所用。**最初是想融合C,C++的,因为Java诞生的时候,C,C++横行,想要立足的话就要有能调用C的程序

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

PC寄存器

程序计数器:Program Counter Register

每个线程都有一个程序计数器, 是线程私有的,就是一个指针, 指向方法区中的方法字节码(用来存储指向像一条指令的地址, 也即将要执行的指令代码),在执行引擎读取下一条指令, 是一个非常小的内存空间,几乎可以忽略不计。

方法区

方法区:Method Area

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

静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中,和方法区无关。

栈:先进后出,后进先出

队列:先进先出(FIFO:first input first output

线程结束,栈内存也就释放了,对于栈而言,不存在垃圾回收问题。

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

类加载器读取类文件后,一般会把类、方法、常量、变量放到堆中,保存所有引用类型的真实对象。

堆内存中细分为以下三个区域

  • 新生区
  • 养老区
  • 永久区

在JDK8后,永久存储区更换为元空间。

在这里插入图片描述

方法区是一种定义、概念。而所谓永久代或元空间是其一种实现机制。(元空间逻辑上存在,物理上不存在)

GC垃圾回收,主要是在伊甸园区和养老区。

若内存满了,OOM,堆内存不够。java.lang.OutOfMenoryError:Java heap space

新生区

  • 一个类诞生和成长(甚至死亡)的地方。
  • 所有的对象都是在伊甸园区new出来的。
  • 幸存者区(存活区)分为0,1区(from space ,to space谁空谁是to)。

在这里插入图片描述

养老区

主要存放JVM认为生命周期比较长的对象(经过几次的新生区(Young Gen)的垃圾回收后仍然存在,换个说法就是新生区没干掉,没杀死的来到了养老区),内存大小相对会比较大,垃圾回收也相对没有那么频繁(譬如可能几个小时一次)。

大多对象都是临时对象。

永久区

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

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

一个启动类,加载了大量的第三方jar包;Tomcat部署过多的应用,大量动态地生成的反射类,不断地被加载,直到内存满,就会出现OOM。

GC垃圾回收

垃圾回收区域在堆和方法区,99%(大多)在堆中。

GC分为两类:轻GC(普通的GC)、重GC(全局GC)

GC常用算法

标记清除法,标记压缩,复制算法,引用计数法。

引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象就是不可能再被使用的。但是此算法有个致命缺陷无法解决循环引用的问题。

复制算法

新生区使用这种算法进行gc 。将可用的内存按容量划分为大小相等的两块(from,to),每次只是用其中一块(总有一块是空的【to区域】)。当这一苦熬的内存用完了,就将还存活着的对象复制到另外一块上面,然后把已使用过的内存空间一次清理完。

  • 每次GC都会讲Eden区中的对象清到幸存区中,一旦Eden区被GC,那么Eden区就会是空的
  • 两个幸存区保持谁空谁是to的原则,即每次GC回将上一轮的from区中的对象复制,然后加上Eden区中新的被GC进来的对象,合在一起放入空的to区中,然后to区变成下一轮的from区,原来的from区被清空,变成新一轮的to区
  • 当一个对象经历了15次(默认值)GC还没有死,那么就会进入养老区中
    注:可通过-XX:MaxTenuringThreshold=value 来调节进入养老区的时间,这也是JVM性能调优的一种方式

优点:没有内存碎片
缺点:浪费内存空间,多了一半空间为空,加入存活率100%就会发生OOM,所以复制算法常用与存活率较低的新生区中

标记清除法

养老区通常使用这种算法。先标记出所有需要回收的对象,在标记完成后统一回收(清除)所有被标记的对象。

在这里插入图片描述

优点:不需要额外空间

缺点:两次扫描,浪费时间,同时会产生内存碎片

标记压缩算法

标记清除压缩算法,对于标记清除的进行压缩。

在这里插入图片描述

  • 30
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值