浅谈JVM,分析jvm的体系结构,jdk1.8、1.7、1.6有什么区别。

3 篇文章 1 订阅
订阅专栏

JVM

一、思考

  • 什么是JVM
  • JVM的位置,为什么有强大的跨平台性?
  • JVM的体系结构
  • JVM类加载器的类加载过程?什么是双亲委派机制?什么是沙箱安全机制?
  • 为什么要用程序计数器(PC寄存器)?程序计数器存在内存溢出吗?
  • 什么是OOM?什么是堆溢出?什么是栈溢出?
  • 栈的生命周期?什么是栈帧?
  • 内存快照怎么抓取?
  • JVM调优主要针对的哪部分调优
  • jdk1.8虚拟机和之前有什么变化

二、jvm探究

2.1、什么是jvm

  • JVM 是 java虚拟机,是用来执行java字节码(二进制的形式)的虚拟计算机。
  • 具体详细介绍可以看 百度百科
  • 下面是百度百科对 JVM 的定义

image-20220409175441079

2.2、jvm的位置

image-20220409170123795

  • jvm是运行在操作系统之上的

  • Java语言的可移植性正是建立在Java虚拟机的基础上。任何平台只要装有针对于该平台的Java虚拟机,字节码文件(.class)就可以在该平台上运行。这就是“一次编译,多次运行”。

    原理:使用Java编译器编译Java程序时,生成的是与平台无关的字节码,这些字节码只面向JVM。不同平台的JVM都是不同的,但它们都提供了相同的接口,只需要在不同的操作系统上安装一个对应版本的虚拟机(JVM) 。

2.3、虚拟机

image-20220409180028478

  • 通过 Java -version 命令可以查看自己是什么虚拟机(常用的是HotSpot)
  • HotSpot VM,是Sun JDK和OpenJDK中所带的虚拟机,也是目前使用范围最广的Java虚拟机。
  • 还有其他几种虚拟机有兴趣可以自行了解

2.4、作图(本人心得)

  • JVM相对抽象,不是口头就能描述清楚,通过图片,我们才能更直观,便于理解。
  • 要想清楚的记住JVM的体系,一定要自己作图,只有自己多次画图,才能在脑海里形成清楚的记忆。
  • 网上有很多详细的体系图,我们可以先画一些简单的简图,等到熟练以后再去看每一块详细的图,一步一步理解。
  • 能自己动手尝试的东西有空一定要尝试,只有自己动手才能记得牢固,有时候看了一遍理解了,过个一两个月就忘记了。只有频繁的使用,才会形成深度记忆,只有真正的理解,才能永不遗忘。
  • 作图:https://www.processon.com/ 或者本地画图软件

三、jvm体系结构

3.1、体系结构图

image-20220409174502344

  • java文件经过编译器(编译器前端)编译为字节码文件–>经过类加载子系统(将字节码加载到内存当中,生成一个class对象,中间经过三步:加载—>链接—>初始化 具体的可以查看类加载器内部图)

  • 在内存中,方法区和堆区是多个线程共享区。

  • Java虚拟机栈,本地方法栈,程序计数器每一个线程独有。

3.2、类加载器

3.2.1、概念及作用

概念:

  • 类的加载指的是将类的.class文件中的 二进制数据读入到方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
  • 类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError、ClassNotFoundException错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

作用:

  • 将class文件加载到虚拟机的内存
3.2.2、类的加载过程(生命周期)

image-20220409183813421

1、加载:查找并加载类的二进制数据加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:

  • 通过一个类的全限定名来获取其定义的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。

2、验证:确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证:

  • 文件格式的验证:验证字节流是否符合Class文件格式的规范,例如是否以0xCAFEBABE开头,版本号是否合理
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。例如是否有父类,继承了final类?非抽象类实现了所有的抽象方法
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
  • 符号引用验证:符号引用中通过字符串描述的全限定名是否能找到对应的类、符号引用类中的类,字段和方法的访问性(private、protected、public、default)是否可被当前类访问。

3、准备:正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配

  • 类变量(static )和 全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的值。
  • 对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过。
  • 对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
  • 局部变量不需初始化。

4、解析:虚拟机将常量池内的符号引用替换为直接引用的过程

5、**初始化:**类加载最后阶段,若该类具有超类,则对其进行初始化。只有当对类的主动使用的时候才会导致类的初始化,类的主动使用 包括以下六种:

  • 创建类的实例,也就是new的方式
  • 访问某个类或接口的静态变量,或者对该静态变量赋值
  • 调用类的静态方法
  • 反射
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

6、**使用:**使用类进行业务操作。

7、**卸载:**结束生命周期

  • 执行System.exit()方法
  • 程序正常执行结束
  • 由于操作系统出现错误而导致Java虚拟机进程终止
3.2.3、加载器

1、启动类加载器(Bootstrap ClassLoader)

  • 启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分
  • 它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中
  • 注意由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。

2、扩展类加载器(Extension ClassLoader)

  • 扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类
  • 它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。
  • jdk8后废弃,使用平台类加载器(Platform ClassLoader)替换了原来的扩展类加载器(Extension ClassLoader)

3、系统类加载器(应用程序加载器)

  • 应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader
  • 负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径

4、自定义类加载器

  • 用户根据自己业务需求自己定义的加载器

接下来我们通过代码看一下:

/**
 * 类加载器测试类
 */
public class Demo01 {
    public static void main(String[] args) {
        Demo01 demo = new Demo01();
        //输出应用程序加载器
        System.out.println(demo.getClass().getClassLoader());//AppClassLoader
        //输出扩展类加载器
        System.out.println(demo.getClass().getClassLoader().getParent());//PlatformClassLoader
        //输出启动类加载器
        System.out.println(demo.getClass().getClassLoader().getParent().getParent());//null
    }
}

jdk8输出:

image-20220409232130306

jdk18输出:

image-20220409210355235

  • 首先输出的是应用程序加载器
  • 然后是平台类加载器(jdk8以后扩展类加载器被丢弃)

​ 原因:

​ 1、在JDK8中的这个Extension ClassLoader,主要用于加载jre环境下的lib下的ext下的jar包。当想要扩展Java的功能的时候, 把jar包放到这个ext文件夹下。然而这样的做法并不安全,不提倡使用。

​ 2、这种扩展机制被JDK9开始加入的“模块化开发”的天然的扩展能力所取代

  • 最后启动类加载器输出了null,这是因为启动加载器使用C++语言实现,我们获取不到。
3.2.4、双亲委派机制

先看代码(在我们的src下建一个java.lang包)

package java.lang;

/**
 * 自定义String类
 * 放到自己建的 java.lang包下
 */
public class String {
    static {
        System.out.println("自定义String类");
    }

    public static void main(String[] args) {
        System.out.println("自定义Sting类");
    }
}

jdk8输出:

image-20220409232739767

  • 如果你的jdk版本过高,会显示: 程序包xxx不可见,程序包xxx已在模块 java.base,都是正常的。

​ 并且会导致其他类也无法运行,删除String类即可。

新增测试类

/**
 * 测试类,测试加载时是否会调用自定义String类的静态代码块
 */
public class Demo02 {
    public static void main(String[] args) {
        String s = new String();
        System.out.println("Hello String");
    }
}

运行:

image-20220409233900733

发现我们自定义的String类并不会执行,说明类加载器加载的不是我们自定义的String,这也就解释了为什么找不到main方法,因为

java.lang下的String类是没有main方法的,这就涉及到了我们的双亲委派机制。

双亲委派机制:

  • 如果一个类加载器收到类的加载请求,他并不会自己先去加载,而是把这个请求先委托给自己的父类去执行
  • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终会到达最顶层的启动类加载器
  • 如果父类加载器可以完成加载任务,就成功返回,倘若父类加载器无法完成此类的加载任务,子加载器才会尝试自己去加载,这就是双亲委派模型

通俗的说就是,自定义的类,但是系统类加载器不会直接加载,先向上传递给扩展类加载器,扩展类加载器在向上传递给启动类加载器,因为启动类加载器没有父类加载器,所以他就尝试自己加载,但是发现自己不能加载,然后他就向下传递给扩展类加载器,但是扩展类加载器发现自己也不能加载,就在向下传递,最终由系统类加载器进行加载(注意:扩展类加载器和启动类加载器有自己的类的加载目录,不在此目录中,这两个加载器就不会加载)

一句话总结:向上委托,向下加载。

工作原理图:

image-20220409235532445

优势:

  • 避免类的重复加载
  • 使得java中的类随着他的类加载器一起具备了一种带有优先级的层次关系,会保证系统的类不会受到恶意的攻击
  • 保护程序安全,防止核心API被随意篡改
3.2.5、沙箱安全机制

什么是沙箱?

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

java中的安全模型:

  • 在Java中将执行程序分成本地代码和远程代码两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的Java实现中,安全依赖于沙箱 (Sandbox) 机制。如下图所示:

​ JDK1.0安全模型

image-20220410000542970

  • 但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。如下图所示

​ JDK1.1安全模型

image-20220410000755625

  • 在 Java1.2 版本中,再次改进了安全机制,增加了代码签名。不论本地代码或是远程代码,都会按照用户的安全策略设定,由类加载器加载到虚拟机中权限不同的运行空间,来实现差异化的代码执行权限控制。如下图所示

​ JDK1.2安全模型

image-20220410000905545

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

image-20220410000947268

3.3、程序计数器

什么是程序计数器?

  • 程序计数器(Program Counter Register)是一个记录着当前线程所执行的字节码的行号指示器.
  • Register的命名源于CPU的寄存器,寄存器存储指令相关的现场信息。它占用很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域
  • 每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期保持一致。
  • 不会存在内存溢出(OutOfMemoryError)

看一段代码:

/**
 * 程序计数器
 */
public class Demo03 {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int c = a + b;
    }
}

然后将代码进行编译成字节码文件

Code:
      stack=2, locals=4, args_size=1
         0: bipush        10
         2: istore_1
         3: bipush        20
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: return
image-20220410013938381

左边的指令地址就是程序计数器记录的地址

优点

  • CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行
  • JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令

PC寄存器为什么被设定为私有的?

  • 所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况。
  • 由于cpu时间片轮限制,多个线程在执行的过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某一个线程中的一条指令。这样必然导致经常的中断和恢复,如何保证在这个过程中不出现差错,为了能够准确的记录各个线程正在执行的当前字节码指令的地址,最好办法是为每一个线程都分配一个pc寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现干扰的情况。

3.4、虚拟机栈

3.4.1、栈和队列
  • 栈:先进后出,后进先出
  • 队列:先进先出(FIFO)

image-20220410130149765

栈:就像一个弹夹,先压进去的子弹后出来

队列:就像一个管道,先进去的先出来

3.4.2、什么是栈

1、栈的概念

  • 栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

2、生命周期

  • 栈的生命周期和线程的生命周期一致,随着线程的启动而创建,随着线程的停止而销毁。当主线程结束后,整个的虚拟机的栈就全部销毁掉。

3、存放类型

  • 8大基本类型 + 对象的引用 + 实例的方法

4、特点

  • 访问速度快,仅次于程序计数器
  • 线程私有
  • 存在 OOM,不存在 GC
3.4.3、栈与栈帧

1、概念

  • 栈:线程运行时需要的内存空间。
  • 栈帧:每个方法运行时需要的内存,栈帧是栈的基本单位。每个线程只能有一个活动栈帧,对应当前执行的那个方法

例如下图:main(),test(),test1()都是一个栈帧,test1()是活动栈帧,也可以说是当前栈帧

image-20220410133215187

2、运行原理

  • 入栈时,栈帧1会通过子帧指向栈帧2
  • 出栈时栈帧2会通过父帧指向栈帧1

image-20220410132348261
3、栈帧演示

  • 代码
/**
 * 栈帧演示
 */
public class Demo04 {
    public static void main(String[] args) {
        test();
    }

    private static void test(){
        test1(1,2);
    }

    private static int test1(int a, int b) {
        int c = a + b;
        return c;
    }
}
  • debug启动,一步一步观察
  • 发现main()先入栈,然后是test(),最后是test1()
  • 再走下去,test1()先出栈,然后是test(),最后是main()

image-20220410135104528

4、总结

  • 每个线程在创建的时候都会创建一个虚拟机栈,其内部都会保存一个个的栈帧,对应着一次次的方法调用
  • 栈是线程私有的,会存在OOM,不存在GC
  • 随着线程的启动而创建,随着线程的停止而销毁
  • 先进后出
3.4.4、栈溢出(StackOverflowError)

1、栈溢出原因

  • 栈帧过多
  • 栈帧过大(不容易出现)

image-20220410140957849

代码演示(递归调用)

  • 采用递归,方法不停的调用方法,栈帧就会过多,导致栈溢出
/**
 * 测试内存溢出
 */
public class Demo05 {
    private static int count;

    public static void main(String[] args) {
        try {
            test();
        } catch (Throwable e) {//注意捕获异常,用Throwable,不要用Exception
            e.printStackTrace();
            System.out.println(count);//捕获异常,打印执行方法次数
        }
    }

    private static void test(){
        count++;
        //递归调用
        test();
    }
}

输出:

image-20220410142345506

  • 可以看出发生了栈溢出(StackOverflowError),记住这个错误,error错误Exception无法捕获
  • 我这里方法执行了32059次,我们可以自己设置栈大小

2、设置栈内存大小

-Xss256k//大小自己设定

image-20220410143352590

  • 我的idea版本没有,所以需要添加一个,正常的应该有VM options,直接输入就可以

image-20220410143506001

image-20220410143649454

  • 可以看到把栈内存设置小了以后,4368次栈就溢出了

image-20220410143928802

总结:

  • 递归调用没有终止条件会导致栈溢出
  • 有时候调用第三方接口也会产生栈溢出,互相调用产生了循环(套娃)。
  • 如果虚拟机栈容量不可以动态扩展,当线程请求的栈深度大于虚拟机允许的最大容量,就会抛出 StackOverFlowError 异常;
  • 如果虚拟机栈容量可以动态扩展,当栈无法申请到足够的内存会抛出 OutOfMemoryError 异常。

3.5、本地方法栈

1、概念

  • 一个Native Method就是一个Java调用非Java代码的接口
  • 本地方法接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序
  • 标识符native可以与其他的Java标识符连用,但是abstract除外

2、特点

  • 本地方法栈也是线程私有的
  • 实现Java应用与Java外面的环境交互
  • 标识符native

3、Native关键字

  • 我们打开Object类(Alt+7),可以看到很多方法都是Native调用的

image-20220410145809915

  • 线程star()

代码

/**
 * Native
 * 线程 start()
 */
public class Demo06 {
    public static void main(String[] args) {
        new Thread(()->{

        },"my threa").start();
    }
}

点进去看start()方法,可以看到调用了start0(),然后start0()是native修饰

image-20220410150746184

4、总结

  • 本地方法栈也是线程私有的
  • native关键字会进入本地方法栈,调用本地方法接口,调用其他语言的库
  • java诞生的时候C和C++很火,想要有一席之地,必须调用C C++.

3.6、堆

3.6.1、什么是堆

1、概念

  • 一个JVM实例只有一个堆内存,堆也是Java内存管理的核心区域,堆在JVM启动的时候创建,其空间大小也被创建,是JVM中最大的一块内存空间,所有线程共享Java堆,物理上不连续的逻辑上连续的内存空间,几乎所有的实例都在这里分配内存,在方法结束后,堆中的对象不会马上删除,仅仅在垃圾收集的时候被删除,堆是GC(垃圾收集器)执行垃圾回收的重点区域
  • 使用New关键字,创建的对象都会使用堆内存

2、特点

  • 线程共享,要考虑线程安全问题
  • 一个JVM实例只有一个堆内存
  • 有GC垃圾回收
  • 响性能主要因素之一,垃圾回收的重点区域
3.6.2、堆溢出

1、溢出原因

  • 对象实例过多

代码

/**
 * 测试堆溢出
 * -Xmx8m
 */
public class Demo07 {
    public static void main(String[] args) {
        int count = 0;//计数
        try {
            ArrayList<String> list = new ArrayList<>();
            String a = "Heap 堆内存";
            /*
            1、死循环做对象拼接
            2、String是固定不变的,每次拼接都是一个新的对象
            3、对象存在list集合中,由于不停的存放,所以list集合不能被垃圾回收
            4、字符串对象存在集合里,集合还在,字符串对象也不能被垃圾回收
             */
            while(true){
                list.add(a);
                a=a+a;
                count++;
            }
        } catch (Throwable e) {//捕获最大的错误
            e.printStackTrace();
            System.out.println(count);
        }
    }
}

输出:

image-20220410153848657

  • 报错:内存溢出,堆空间不足(OutOfMemoryError Java heap space)

2、设置堆内存大小

-Xmx8m

image-20220410154522950

  • 只执行了16次

image-20220410154548328

3.6.3、堆内存快照抓取
  • jps工具:查看当前系统中有那些进程
  • jmap工具:查看堆内存占用情况
  • jconsole工具:图形界面的,多功能的监测工具,可以连续监测

代码

/*
 * 堆内存监测
 * 命令:
 * 1、jps:查看当前系统中有那些进程
 * 2、jmap -head +当前线程  例如:jmap -head 39120
 */
public class Demo08 {
    public static void main(String[] args) throws InterruptedException{
        System.out.println("开始");
        Thread.sleep(30000);//睡眠30s,打命令
        byte[] bytes = new byte[1024 * 1024 * 10];//10M
        System.out.println("创建数组");
        Thread.sleep(30000);
        bytes = null;//清空对象
        System.gc();//垃圾回收
        System.out.println("结束");
        Thread.sleep(1000000L);
    }
}

输出:

1、jps jmap

  • 命令

image-20220410163041787

  • 堆参数

image-20220410162104709

  • 占用情况:

image-20220410162356843

  • 可以观察堆内存的使用情况

2、jconsole工具

  • 命令

  • 选择连接

image-20220410163919220

  • 可视化界面

image-20220410163539333

3.6.4、堆空间演变

image-20220410175944387

对空间划分

版本变化
jdk1.7及之前新生代(年轻代)老年代 永久代
jdk1.8及之后新生代(年轻代)老年代 元空间(本地内存)

3.7、方法区

3.7.1、什么是方法区
  • Java方法区和堆一样,方法区是一块所有线程共享的内存区域
  • 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中与Java堆区一样都是可以是不连续的。
  • .方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展
3.7.2、方法区溢出
  • 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMemoryError:PermGen space(永久代)或者java.lang.OutOfMemoryError:Metaspace(元空间),比如加载了大量的第三方的jar包或者tomcat部署的工程过多,都可能导致方法区溢出,出现java.lang.OutOfMemoryError错误。
3.7.3、jdk8和之前的区别

1、方法区的演变

版本变化
jdk1.6及之前有永久代(Permanent generation),静态变量存放在永久代
jdk1.7字符串常量池、静态变量移出永久代,存放在堆中
jdk1.8及之后去除了永久代,本地内存的元空间(Metaspace)取代

对比图:

image-20220410175413952

2、jdk1.7中字符串常量池StringTable为什么从永久代移到堆中

  • 永久代的回收效率很低,只有full Gc才会触发,(老年代或永久代空间不足会触发full Gc)导致StringTable回收效率不高,开发中会有大量字符串被创建,放到堆里能够及时回收内存。

3、为什么去掉永久代

  • 永久代在jvm中,合适的大小难以确定(元空间分配在本地内存,无需考虑大小)
  • 对永久代调优很困难
3.7.4、常量池

代码

/**
 * 二进制字节码(类的基本信息,常量池,类方法定义,虚拟机指令)
 */
public class Demo09 {
    public static void main(String[] args) {
        //梦开始的地方,又回到最初的起点
        System.out.println("Hello World");
    }
}

查看字节码文件

  • 常量池

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OrnjfI43-1651405760875)(https://gitee.com/hyl199605251513/typora-img/raw/master/img/image-20220412212520222.png)]

  • 编译后代码

image-20220412212713591

  • 在常量池中找 #2 发现对应的 #21 #22
  • #21 对应 #28 System #22 对应#29 out
  • 同理可以找到我们 Hello World在常量池中对应的编码
Code:
      stack=2, locals=1, args_size=1
         // 从常量池中符号地址为 #2 的地方,先获取静态变量System.out
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         // 从常量池中符号地址为 #3 的地方加载常量 hello world
         3: ldc           #3                  // String hello world
         // 从常量池中符号地址为 #3 的地方获取要执行的方法描述,并执行方法输出hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         // main方法返回
         8: return
3.7.5、运行时常量池

1、什么是运行时常量池

  • 当类的字节码被加载到内存中后,他的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池,并且把里面的符号地址变为真实地址
  • 运行时常量池具有动态性,即运行期间也可以向常量池中添加新的常量

代码

/**
 * 运行时常量池
 */
public class Demo10 {
    public static void main(String[] args) {
        Student student = new Student();
        student.name="凌晨";//存放运行时常量池
    }
    
}

class Student{
    String name;
}
3.7.6、字符串常量池

1、StringTable

/**
 * 串池
 * StringTable ["a","b","ab"] hashtable结构,不能扩容
 */
public class Demo11 {
    public static void main(String[] args) {
        /*
        常量池中的信息,都会被加载到运行时常量池,这是 a b ab 都是常量池中的符号,还没有成为字符串对象
        懒惰:只有走到当前代码,才会变为字符串对象
         */
        String s1 = "a";//把a变为字符串对象“a”
        String s2 = "b";//把b变为字符串对象"b"
        String s3 = "ab";//把ab变为字符串对象"ab"
        
		//new StringBuilder().append("a").append("b").toString();     new String("ab")
        String s4 = s1 + s2 ;
        System.out.println(s3 == s4);
        
        String s5 = "a" + "b";
        //javac 在编译期间优化,在编译阶段已经确定结果,不可改变
        System.out.println(s3==s5);
    }
}

image-20220412220556398

  • s1 + s2 等同于 new StringBuilder().append(“a”).append(“b”).toString()
  • 等同于 new String(“ab”) 但是字符串常量池不创建ab对象,因为StringBuilder的toString()中的new String 是调用的不同的构造器
  • s5 = “a” + "b"在编译期间优化,在编译阶段已经确定结果,不可改变 ,相当于“ab”

2、intern

  • jdk1.8主动将串池中还没有的字符串对象放入串池 ,有则不放,然后返回串池中的对象
  • jdk1.6 有则不放入,没有则对象复制一份放入串池,然后返回串池中的对象
/**
 * intern
 */
public class Demo12 {
    public static void main(String[] args) {
        String s1 = new String("a") + new String("b");//创建了几个对象
        //将字符串对象放入串池,如果有就返回,没有就放入
        String s2 = s1.intern();//串池中没有“ab”,会把s1放入串池,然后返回串池中的对象
        System.out.println(s2=="ab");//true
        System.out.println(s1=="ab");//true
    }

}
/**
 * intern
 */
public class Demo12 {
    public static void main(String[] args) {
        String s3 = "ab";
        String s1 = new String("a") + new String("b");//创建了几个对象
        //将字符串对象放入串池,如果有就返回,没有就放入
        String s2 = s1.intern();//串池有“ab”,不会会把s1放入串池,会返回串池中的对象
        System.out.println(s2==s3);//true
        System.out.println(s1==s3);//false
    }

}

于“ab”

2、intern

  • jdk1.8主动将串池中还没有的字符串对象放入串池 ,有则不放,然后返回串池中的对象
  • jdk1.6 有则不放入,没有则对象复制一份放入串池,然后返回串池中的对象
/**
 * intern
 */
public class Demo12 {
    public static void main(String[] args) {
        String s1 = new String("a") + new String("b");//创建了几个对象
        //将字符串对象放入串池,如果有就返回,没有就放入
        String s2 = s1.intern();//串池中没有“ab”,会把s1放入串池,然后返回串池中的对象
        System.out.println(s2=="ab");//true
        System.out.println(s1=="ab");//true
    }

}
/**
 * intern
 */
public class Demo12 {
    public static void main(String[] args) {
        String s3 = "ab";
        String s1 = new String("a") + new String("b");//创建了几个对象
        //将字符串对象放入串池,如果有就返回,没有就放入
        String s2 = s1.intern();//串池有“ab”,不会会把s1放入串池,会返回串池中的对象
        System.out.println(s2==s3);//true
        System.out.println(s1==s3);//false
    }

}
JAVAJVM详解
m0_74823595的博客
10-10 690
JVMJava Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。通过JVMJava实现了平台无关性。
jvm内存模型,1.6 1.7 1.8哪里不同
RuiDer的博客
07-11 3195
JVM 因为热爱,所以拼搏。 –RuiDer XI. 前导必备 Java基础 JVM内存模型 XII. JVM内存模型 说明一下,也是我本人的亲身感受。如果你是入门JVM新手,之前接触过JVM方面的东西或者你个人存有自己对于JVM理解,我希望你暂时先屏蔽他们,不然会带来很大的干扰。 YI. Java内存区域划分 1.程序计数器(P...
JVMJava虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)
热门推荐
墨鸦的博客
02-07 2万+
JVMJava虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)
jvm】各个java版本默认的垃圾回收器
最新发布
一个写了10年bug的程序员日常笔记。
03-26 947
请注意,随着Java版本的更新,垃圾回收器的特性和性能也在不断改进。开发者应根据应用程序的具体需求和JVM的性能特性来选择合适的垃圾回收器。此外,JVM的默认垃圾回收器可能会随着新版本的发布而发生变化,因此在使用特定版本的JDK时,最好查阅官方文档以获取最新信息。从Java 1(JDK 1.0)开始到Java 21之间的各个Java版本默认的垃圾回收器经历了一系列的演变。
Java --- JVM概述
qq_46093575的博客
09-18 451
1、java字节码,指的是用java语言编译成的字节码。准确的说任何能在jvm平台上执行的字节码格式都是一样的。所以应该统称为jvm字节码。2、不同的编译器,可以编译出相同的字节码文件,字节码文件也可以在不同的JVM上运行。3、Java虚拟机与 Java语言并没有必然的联系,它只与特定的二进制文件格式—Class文件格式所关联,class文件中包含了Java虚拟机指。
jdk大全(1.61.7和1.8)
03-04
各个版本的jdk包括,解压版的,非解压版的1.61.7和1.8,全部都有
JVM版本问题
专注于JAVA WEB开发
10-26 6861
文章转载自:http://www.cnblogs.com/newskysoft/archive/2010/04/23/1718720.html     今天遇到一个错误Error: no `server' JVM at `C:/Program Files/Java/j2re1.4.2/bin/server/jvm.dll'. 后面看了一下以下文章,问题解决。 转贴http://dev.csdn.net/article/32/32926.shtm jdk(java developmen
JVMJVM 1.7与1.8版本内存结构区别
我还可以
08-28 1110
细节介绍可以点我跳转,本篇只介绍两个版本之间的区别;可以看到1.8之前版本,线程共享为两个区域:堆与方法区;其中方法区只是JVM虚拟机规范的一部分,不是实际的实现;
jdk1.8jvm变化
weixin_34129145的博客
03-14 517
2019独角兽企业重金招聘Python工程师标准>>> ...
涵盖了90%以上的面试题
11-02
jdk1.7新特性 jdk1.8新特性 java语言有哪些优点? 同一个.java文件中是否可以有多个main方法 一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 如何在main方法执行前输出”hello world” java程序...
Java虚拟机
01-20
第2版在第1版的基础上做了很大的改进:根据最新的JDK1.7对全书内容进行了全面的升级和补充;增加了大量处理各种常见JVM问题的技巧和最佳实践;增加了若干与生产环境相结合的实战案例;对第1版中的错误和不足之处的...
jvm规范第七版本
12-25
jvm规范第七版本,英文版本,jdk 7 使用虚拟机的规范
疯狂JAVA讲义
10-17
学生提问:为什么选择设置用户变量,用户变量和系统变量有什么区别呢? 11 1.5 第一个Java程序 12 1.5.1 编辑Java源代码 12 1.5.2 编译Java程序 13 学生提问:当我们使用编译C程序时,不仅需要指定存放目标文件...
Spring.3.x企业应用开发实战(完整版).part2
05-31
1.4 Spring体系结构 1.5 Spring 3.0的新功能 1.5.1 核心API更新到Java 5. 1.5.2 Spring表达式语言 1.5.3 可通过Java类提供IoC配置信息 1.5.4 通用类型转换系统和属性格式化系统 1.5.5 数据访问层新增OXM功能 1.5.6 ...
Spring3.x企业应用开发实战(完整版) part1
05-31
1.4 Spring体系结构 1.5 Spring 3.0的新功能 1.5.1 核心API更新到Java 5. 1.5.2 Spring表达式语言 1.5.3 可通过Java类提供IoC配置信息 1.5.4 通用类型转换系统和属性格式化系统 1.5.5 数据访问层新增OXM功能 1.5.6 ...
JVM (完美图解)--1.6,1.7,1.8
o_nianchenzi_o的博客
11-25 3290
哪个版本JVM最快?
java_beautiful的博客
06-28 1015
​Chronicle Queue是一个持久性的低延迟Java消息传递框架。它适用于具有高性能的关键性应用程序。由于Chronicle Queue运行在映射到本地的内存上,因此它消除了垃圾收集的需求,并为开发人员提供了确定性和高性能。 本文将使用开源的Chronicle Queue的两个线程,彼此交换256字节的消息数据。同时,为了最小化对于磁盘子系统的影响,所有消息都将被存储在共享内存--/dev/shm中。...
Java虚拟机—Java8内存模型(整理版)
weixin_30435261的博客
04-18 89
1.概述 对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要手动释放内存,不容易出现内存泄露和内存溢出问题。一旦出现内存泄露和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,排查错误将会异常艰难。 2.运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时...
JVM数据区在JDK1.8带来的变化以及GC
coding to peak
04-01 6398
        前面有介绍过jvm的在jdk1.7的运行时数据区,现在讲讲JDK1.8带来的变化,JDK1.8的运行时数据区把方法区移除了,变成了元数据区,所以之前的那个图里面的数据块应该变成如下:        在jdk1.8之前的版本,我们通常把堆分为新生代,老年代和永久代(方法区)(我们通常认为方法区也是属于堆的),新生代又包含了eden,from和to(survivor),当对象存活了超过...
jdk1.8相比djk1.7JVM有那些升级
06-09
相对于 JDK 1.7,JDK 1.8 中的 JVM 有如下一些主要的升级: 1. Lambda 表达式:JDK 1.8 引入了 Lambda 表达式,这是一种新的语言特性,可以简化代码并提高性能。 2. PermGen 空间的移除:JDK 1.8 中的 JVM 将 PermGen 空间移除,取而代之的是 Metaspace 空间,这使得 JVM 的内存管理更加高效。 3. 默认的垃圾回收器:JDK 1.8 中默认使用 G1 垃圾回收器,相对于 JDK 1.7 中的 CMS 垃圾回收器,G1 垃圾回收器具有更好的性能和更低的延迟。 4. 类加载器的优化:JDK 1.8 中的 JVM 对类加载器进行了优化,使得类加载更加高效。 5. 启动速度的提升:JDK 1.8 中的 JVM 提供了一种新的机制,可以在启动时预先编译部分代码,从而提高启动速度。 6. 代码优化:JDK 1.8 中的 JVM 对代码优化进行了改进,从而提高了应用程序的性能。 总的来说,JDK 1.8 中的 JVM 相对于 JDK 1.7 中的 JVM 有很多升级,这些升级使得 JDK 1.8 更加高效、安全和易用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
14
原创
10
点赞
59
收藏
1
粉丝
关注
私信
写文章

热门文章

  • JDK安装,配置环境变量,浅谈jdk,jre,jvm区别与联系以及为何要配置环境变量。 8058
  • 数据库去重,group by、distinct、rowid的用法,oracle和mysql如何数据去重保留一条 2604
  • 浅谈JVM,分析jvm的体系结构,jdk1.8、1.7、1.6有什么区别。 1828
  • 如何查看二进制字节码文件 1622
  • java静态代码块,构造代码块,构造函数,mian()代码执行顺序详细分析 772

分类专栏

  • JAVA基础 8篇
  • Maven 1篇
  • 数据库 2篇
  • JVM 3篇

最新评论

  • JDK安装,配置环境变量,浅谈jdk,jre,jvm区别与联系以及为何要配置环境变量。

    凌晨三点不下班: 环境配置不用刻意的记忆,只要理解为什么需要配置就可以了

  • JDK安装,配置环境变量,浅谈jdk,jre,jvm区别与联系以及为何要配置环境变量。

    凌晨三点不下班: 谢谢你的肯定,祝你工作顺利

  • JDK安装,配置环境变量,浅谈jdk,jre,jvm区别与联系以及为何要配置环境变量。

    Boge_Xie: 不好意思哈,这里没错,是path不对,win10识别不了

  • JDK安装,配置环境变量,浅谈jdk,jre,jvm区别与联系以及为何要配置环境变量。

    Boge_Xie: 引用「变量值:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;」 这里最后面不用加分号吧

  • JDK安装,配置环境变量,浅谈jdk,jre,jvm区别与联系以及为何要配置环境变量。

    疯癫小呼: 很多教程只说了要修改环境变量,但是我不知道为什么要弄这个啊表情包感谢博主解答了我的疑惑

您愿意向朋友推荐“博客详情页”吗?

  • 强烈不推荐
  • 不推荐
  • 一般般
  • 推荐
  • 强烈推荐
提交

最新文章

  • java二分法查询有序数组
  • java选择排序
  • java冒泡排序
2023年3篇
2022年11篇

目录

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌晨三点不下班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值

深圳SEO优化公司保定网站推广多少钱衡阳SEO按天收费多少钱黔西南网站优化按天收费公司民治至尊标王价格迪庆SEO按天扣费报价伊犁关键词按天计费哪家好普洱网站制作推荐泰州百度网站优化排名公司景德镇网站优化哪家好张家界设计网站报价乌海模板网站建设张掖外贸网站制作价格黄山百度网站优化排名襄樊网页制作黄山网络营销哪家好毕节百度网站优化排名公司坪地百度网站优化排名报价布吉百度标王推荐绥化网站优化公司贺州网站seo优化价格株洲外贸网站建设公司济宁SEO按效果付费多少钱和田网络广告推广公司肇庆关键词排名推荐塘坑网站seo优化公司丹东SEO按效果付费推荐萍乡seo网站推广推荐自贡网站改版秦皇岛关键词排名包年推广绵阳关键词按天计费哪家好歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化