JVM(狂神说)

本文详细探讨了JVM的体系结构,包括类加载器(Bootstrap, ExtClassLoader, AppClassLoader)及其双亲委派机制,确保类的安全加载。此外,介绍了沙箱安全机制、Native、PC寄存器的作用,以及方法区、栈、HotSpot JVM、堆内存(新生区、养老区、永久区/元空间)的细节。文章还涉及了JVM调优、如何排查OOM异常和不同GC垃圾回收算法(复制、标记清除、标记整理)的优缺点。" 133615483,19990713,LVS调度算法与服务器群集管理详解,"['负载均衡', '服务器群集', 'LVS', '调度算法']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JVM体系结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

类加载器及双亲委派机制

在这里插入图片描述

类加载器

JVM提供了三层类加载器:

  • Bootstrap classLoader(启动类/根加载器):主要负责加载核心类库(如java.lang.*等),构造ExtClassLoader和AppClassLoader。
  • ExtClassLoader(扩展类加载器):主要负责加载jre/lib/ext目录下的一些扩展类
  • AppClassLoader(应用程序加载器):主要负责加载应用程序的主函数类

双亲委派机制

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

类加载器级别:App->Ext>Boot

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;
        }
    }

双亲委派机制的作用

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

测试

在这里插入图片描述
在这里插入图片描述

沙箱安全机制

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

Native

/**
 * native:凡是带用native关键字的,代表Java作用范围达不到了,需要回去调用底层C语言的库
 * 会进入本地方法栈 调用本地方法接口(JNI)
 * JNI作用:扩展JAVA程序,融合不同的编程语言为Java所用 最终是为了融合C、C++
 * Java诞生的时候 C、C++语言很流行,Java语言想要有立足之地的话就必须能够调用C、C++的程序
 * 它在内存区域中专门开辟了一块标记区域:Native Method Stack 登记native方法
 * 在最终执行的时候,通过JNI加载本地方法库中的方法
 *
 * 现在在开发中很少用到native了(适用场景:Java驱动打印机等)
 */
public native void start0();

PC寄存器

程序计数器:Program Counter Register

​ 每个线程都有程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址)

方法区(Method Area)

方法区被所有线程共享。静态变量(static)、常量(final)、类模板信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是实例变量存在堆内存中。(方法区中包含的都是在程序中永远的唯一的元素)

程序=数据结构+算法

为什么main先执行,最后结束?

在这里插入图片描述

栈:栈内存,主管程序的运行,生命周期和线程同步

线程结束,栈内存也就释放。

栈运行原理:栈帧

栈满了:StackOverError

栈中存放的是保存基本数据类型的对象和自定义对象的引用(不是对象)。

HosPot

三种JVM:

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

我们都是学习HotSpot

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

堆内存要分为三个区域:新生区、养老区、永久区

在这里插入图片描述

GC回收主要是在伊甸去和永久存储区

在JDK8以后,永久存储区 改名为元空间

元空间逻辑上存在 物理上不存在

新生区

新生区是类诞生和成长的地方,甚至死亡。

包含两个区域:

  • 伊甸园区:所有的类都是在伊甸园new出来的
  • 幸存者区(包含两个幸存者区)。

new出来的类先存在伊甸园去,当伊甸园满了之后,触发一次轻GC,存活的实例

则放在幸存者区。(经过研究,百分之99的对象都是临时对象,大多在新生区都被消灭了)

永久区

这个区域常驻内存。用来存放JDK自身携带的Class对象、interface元对象。这个区域不存在垃圾回收!关闭JVM虚拟机就会释放这个区域的内存。

该区一般不会出现OOM。

若出现OOM,可能原因有:

  • 一个启动类加载了大量第三方jar包
  • Tomcat部署很多的应用

jdk1.8之后,就被成为元空间了。

在这里插入图片描述

元空间逻辑上存在,物理上不存在。

内存测试

// 虚拟机试图使用的最大内存
long max = Runtime.getRuntime().maxMemory();
// 虚拟机初始化总内存
long total = Runtime.getRuntime().totalMemory();

System.out.println(max + "字节   " + max/(double)1024/1024 + "M");
System.out.println(total + "字节   " + total/(double)1024/1024 + "M");

输出:(默认情况下 分配的总内存是电脑内存的是1/4,初始化的内存是1/64)
857735168字节   818.0M
58720256字节   56.0M

自定义JVM参数:

在这里插入图片描述
在这里插入图片描述

所以说永久区逻辑上存在,物理上不存在。

JVM调优

OOM异常排除

使用JProfile分析OOM原因

  1. 安装JProfile

在这里插入图片描述
在这里插入图片描述

  1. 下载JProfile客户端并安装
  2. 设置JVM参数
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

GC垃圾回收算法

JVM进行垃圾回收的区域:新生代(Eden、Survivor from、Survivor to)、老年代。大部分GC都在新生代中发生。

新生代发生的GC叫Major GC,老年代发生的GC叫Full GC,Full GC至少伴随着一次Major GC。

GC算法:引用计数法

在这里插入图片描述

不是很常用

GC算法 :复制算法

年轻代的GC主要使用复制算法(负责Survivor from、 to两区的复制)

在这里插入图片描述

好处:没有内存碎片

坏处:浪费了一块内存空间。(多了一块空间to区永远是空的)(如果在极端情况下,对象100%存活,这样就不好 因而新生代采用该算法)

标记清除算法

在这里插入图片描述

优点:不需要额外的空间

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

标记整理算法

在这里插入图片描述

先标记清除几次,在压缩 这样比较节省性能

GC 算法总结

内存效率(时间复杂度):复制算法>标记清除算法>标记整理算法
内存整齐度:复制算法=标记整理算法>标记清除算法
内存利用率:标记整理算法=标记清除算法>复制算法
   
GC使用分代收集算法

 年轻代:存活率低,所以采用复制算法
 老年代:区域大,存活率高 使用标记清除算法+标记整理算法混合实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值