精华内容
下载资源
问答
  • 从JDK1.3开始,虚拟机设计团队就把对性能的优化集中到了后端的即时编译中,这样可以让那些不是由Javac产生的Class文件(如JRuby、Groovy等语言的Class文件)也能享受到编译期优化所带来的好处Java中即时编译在运行期...

    java编译期优化

    java语言的编译期其实是一段不确定的操作过程,因为它可以分为三类编译过程:
    1.前端编译:把.java文件转变为.class文件
    2.后端编译:把字节码转变为机器码
    3.静态提前编译:直接把*.java文件编译成本地机器代码
    从JDK1.3开始,虚拟机设计团队就把对性能的优化集中到了后端的即时编译中,这样可以让那些不是由Javac产生的Class文件(如JRuby、Groovy等语言的Class文件)也能享受到编译期优化所带来的好处
    Java中即时编译在运行期的优化过程对于程序运行来说更重要,而前端编译期在编译期的优化过程对于程序编码来说关系更加密切    

    早期(编译期)优化

    早期编译过程主要分为3个部分:1.解析与填充符号表过程:词法、语法分析;填充符号表  2.插入式注解处理器的注解处理过程  3.语义分析与字节码生成过程:标注检查、数据与控制流分析、解语法糖、字节码生成

    泛型与类型擦除

    java编译期优化

    java语言的编译期其实是一段不确定的操作过程,因为它可以分为三类编译过程:
    1.前端编译:把.java文件转变为.class文件
    2.后端编译:把字节码转变为机器码
    3.静态提前编译:直接把*.java文件编译成本地机器代码
    从JDK1.3开始,虚拟机设计团队就把对性能的优化集中到了后端的即时编译中,这样可以让那些不是由Javac产生的Class文件(如JRuby、Groovy等语言的Class文件)也能享受到编译期优化所带来的好处
    Java中即时编译在运行期的优化过程对于程序运行来说更重要,而前端编译期在编译期的优化过程对于程序编码来说关系更加密切    

    早期(编译期)优化

    早期编译过程主要分为3个部分:1.解析与填充符号表过程:词法、语法分析;填充符号表  2.插入式注解处理器的注解处理过程  3.语义分析与字节码生成过程:标注检查、数据与控制流分析、解语法糖、字节码生成

    泛型与类型擦除

    Java语言中的泛型只在程序源码中存在,在编译后的字节码文件中,就已经替换成原来的原生类型了,并且在相应的地方插入了强制转型代码

    泛型擦除前的例子    
    public static void main( String[] args )
    {
        Map<String,String> map = new HashMap<String, String>();
        map.put("hello","你好");
        System.out.println(map.get("hello"));
    }
    
    泛型擦除后的例子    
    public static void main( String[] args )
    {
        Map map = new HashMap();
        map.put("hello","你好");
        System.out.println((String)map.get("hello"));
    }

    自动装箱、拆箱与遍历循环

    自动装箱、拆箱在编译之后会被转化成对应的包装和还原方法,如Integer.valueOf()与Integer.intValue(),而遍历循环则把代码还原成了迭代器的实现,变长参数会变成数组类型的参数。
    然而包装类的“==”运算在不遇到算术运算的情况下不会自动拆箱,以及它们的equals()方法不处理数据转型的关系。

    条件编译

    Java语言也可以进行条件编译,方法就是使用条件为常量的if语句,它在编译阶段就会被“运行”:

    public static void main(String[] args) {
        if(true){
            System.out.println("block 1");
        }
        else{
            System.out.println("block 2");
        }
    }
    
    编译后Class文件的反编译结果:
    public static void main(String[] args) {
        System.out.println("block 1");
    }

    只能是条件为常量的if语句,这也是Java语言的语法糖,根据布尔常量值的真假,编译器会把分支中不成立的代码块消除掉

    晚期(运行期)优化

    解释器与编译器

    Java程序最初是通过解释器进行解释执行的,当程序需要迅速启动和执行时,解释器可以首先发挥作用,省去编译时间,立即执行;当程序运行后,随着时间的推移,编译期逐渐发挥作用,把越来越多的代码编译成本地代码,获得更高的执行效率。解释执行节约内存,编译执行提升效率。 同时,解释器可以作为编译器激进优化时的一个“逃生门”,让编译器根据概率选择一些大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立,则通过逆优化退回到解释状态继续执行。

    HotSpot虚拟机中内置了两个即时编译器,分别称为Client Compiler(C1编译器)和Server Compiler(C2编译器),默认采用解释器与其中一个编译器直接配合的方式工作,使用哪个编译器取决于虚拟机运行的模式,也可以自己去指定。若强制虚拟机运行与“解释模式”,编译器完全不介入工作,若强制虚拟机运行于“编译模式”,则优先采用编译方式执行程序,解释器仍然要在编译无法进行的情况下介入执行过程。

    分层编译策略

    分层编译策略作为默认编译策略在JDK1.7的Server模式虚拟机中被开启,其中包括:
    第0层:程序解释执行,解释器不开启性能监控功能,可触发第1层编译;
    第1层:C1编译,将字节码编译成本地代码,进行简单可靠的优化,如有必要将加入性能监控的逻辑;
    第2层:C2编译,也是将字节码编译成本地代码,但是会启动一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
    实施分层编译后,C1和C2将会同时工作,C1获取更高的编译速度,C2获取更好的编译质量,在解释执行的时候也无须再承担性能监控信息的任务。  

    热点代码探测

    在运行过程中会被即时编译器编译的“热点代码”有两类:
    1.被多次调用的方法:由方法调用触发的编译,属于JIT编译方式
    2.被多次执行的循环体:也以整个方法作为编译对象,因为编译发生在方法执行过程中,因此成为栈上替换(OSR编译)
    
    热点探测判定方式有两种:
    1.基于采样的热点探测:虚拟机周期性的检查各个线程的栈顶,如果某个方法经常出现在栈顶,则判定为“热点方法”。(简单高效,可以获取方法的调用关系,但容易受线程阻塞或别的外界因素影响扰乱热点探测)
    2.基于计数的热点探测:虚拟机为每个方法建立一个计数器,统计方法的执行次数,超过一定阈值就是“热点方法”。(需要为每个方法维护计数器,不能直接获取方法的调用关系,但是统计结果精确严谨)  

    HotSpot虚拟机使用的是第二种,它为每个方法准备了两类计数器:方法调用计数器和回边计数器,下图表示方法调用计数器触发即时编译:

    如果不做任何设置,执行引擎会继续进入解释器按照解释方式执行字节码,直到提交的请求被编译器编译完成,下次调用才会使用已编译的版本。另外,方法调用计数器的值也不是一个绝对次数,而是一段时间之内被调用的次数,超过这个时间,次数就减半,这称为计数器热度的衰减。

    下图表示回边计数器触发即时编译:

    回边计数器没有计数器热度衰减的过程,因此统计的就是绝对次数,并且当计数器溢出时,它还会把方法计数器的值也调整到溢出状态,这样下次进入该方法的时候就会执行标准编译过程。

    编译优化技术

    虚拟机设计团队几乎把对代码的所有优化措施都集中在了即时编译器之中,那么在编译器编译的过程中,到底做了些什么事情呢?下面将介绍几种最有代表性的优化技术:
    公共子表达式消除
    如果一个表达式E已经计算过了,并且先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就成为了公共表达式,可以直接用之前的结果替换。
    例:int d = (c b) 12 + a + (a + b c) => int d = E 12 + a + (a + E)

    数组边界检查消除
    Java语言中访问数组元素都要进行上下界的范围检查,每次读写都有一次条件判定操作,这无疑是一种负担。编译器只要通过数据流分析就可以判定循环变量的取值范围永远在数组长度以内,那么整个循环中就可以把上下界检查消除,这样可以省很多次的条件判断操作。

    另一种方法叫做隐式异常处理,Java中空指针的判断和算术运算中除数为0的检查都采用了这个思路:

    if(foo != null){
        return foo.value;
    }else{
        throw new NullPointException();
    }
    
    使用隐式异常优化以后:
    try{
        return foo.value;
    }catch(segment_fault){
        uncommon_trap();
    }
    当foo极少为空时,隐式异常优化是值得的,但是foo经常为空,这样的优化反而会让程序变慢,而HotSpot虚拟机会根据运行期收集到的Profile信息自动选择最优方案。

    方法内联
    方法内联能去除方法调用的成本,同时也为其他优化建立了良好的基础,因此各种编译器一般会把内联优化放在优化序列的最靠前位置,然而由于Java对象的方法默认都是虚方法,因此方法调用都需要在运行时进行多态选择,为了解决虚方法的内联问题,首先引入了“类型继承关系分析(CHA)”的技术。

    1.在内联时,若是非虚方法,则可以直接内联  
    2.遇到虚方法,首先根据CHA判断此方法是否有多个目标版本,若只有一个,可以直接内联,但是需要预留一个“逃生门”,称为守护内联,若在程序的后续执行过程中,加载了导致继承关系发生变化的新类,就需要抛弃已经编译的代码,退回到解释状态执行,或者重新编译。
    3.若CHA判断此方法有多个目标版本,则编译器会使用“内联缓存”,第一次调用缓存记录下方法接收者的版本信息,并且每次调用都比较版本,若一致则可以一直使用,若不一致则取消内联,查找虚方法表进行方法分派。

    逃逸分析
    逃逸分析的基本行为就是分析对象动态作用域,当一个对象被外部方法所引用,称为方法逃逸;当被外部线程访问,称为线程逃逸。若能证明一个对象不会被外部方法或进程引用,则可以为这个变量进行一些优化:

    1.栈上分配:如果确定一个对象不会逃逸,则可以让它分配在栈上,对象所占用的内存空间就可以随栈帧出栈而销毁。这样可以减小垃圾收集系统的压力。  
    2.同步消除:线程同步相对耗时,如果确定一个变量不会逃逸出线程,那这个变量的读写不会有竞争,则对这个变量实施的同步措施也就可以消除掉。  
    3.标量替换:如果逃逸分析证明一个对象不会被外部访问,并且这个对象可以被拆散的话,那么程序真正执行的时候可以不创建这个对象,改为直接创建它的成员变量,这样就可以在栈上分配。

    可是目前还不能保证逃逸分析的性能收益必定高于它的消耗,所以这项技术还不是很成熟。

    java与C/C++编译器对比

    Java虚拟机的即时编译器与C/C++的静态编译器相比,可能会由于下面的原因导致输出的本地代码有一些劣势:
    1.即时编译器运行占用的是用户程序的运行时间,具有很大的时间压力,因此不敢随便引入大规模的优化技术;
    2.Java语言是动态的类型安全语言,虚拟器需要频繁的进行动态检查,如空指针,上下界范围,继承关系等;
    3.Java中使用虚方法频率远高于C++,则需要进行多态选择的频率远高于C++;
    4.Java是可以动态扩展的语言,运行时加载新的类可能改变原有的继承关系,许多全局的优化措施只能以激进优化的方式来完成;
    5.Java语言的对象内存都在堆上分配,垃圾回收的压力比C++大
    
    然而,Java语言这些性能上的劣势换取了开发效率上的优势,并且由于C++编译器所有优化都是在编译期完成的,以运行期性能监控为基础的优化措施都无法进行,这也是Java编译器独有的优势。

    参考文章

    <https://segmentfault.com/a/1190000009707894>;

    <https://www.cnblogs.com/hysum/p/7100874.html>;

    <http://c.biancheng.net/view/939.html>;

    <https://www.runoob.com/>;

    https://blog.csdn.net/android_hl/article/details/53228348

    展开全文
  • 什么是JVM虚拟机JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集...

    什么是JVM虚拟机

    JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。Java虚拟机包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域。 JVM屏蔽了与具体操作系统平台相关的信息,使Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台(跨平台)上不加修改地运行。JVM在执行字节码时,实际上最终还是把字节码解释成具体平台上的机器指令执行。如图所示:

    RE/JDK/JVM是什么关系

    JRE(JavaRuntimeEnvironment,Java运行环境),也就是Java平台。所有的Java 程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。

    JDK(Java Development Kit)是程序开发者用来来编译、调试java程序用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是 安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录,用于存放JRE文件。

    JVM(JavaVirtualMachine,Java虚拟机)是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。

    JVM底层原理

    我们现在知道jvm在整个开发中的位置,那jvm它是如何做到跨平台的?底层是如何运行的?我们通过一个程序来说明:

    /***

    * 运算

    * @return

    */

    public int add() {

    int a = 1;

    int b = 2;

    int c = (a + b) * 100;

    return c;

    }

    /**

    * 程序入口

    * @param args

    * @throws InterruptedException

    */

    public static void main(String[] args) throws InterruptedException {

    App app = new App();

    int result = app.add();

    System.out.println(result);

    }}

    当运行main方法的时候会生产一个class文件,这个class文件是运行在我们jvm之上的。

    JVM运行时数据区分配,黄色背景为线程私有区域,当我们程序运行的时候会有一个main线程在我们

    虚拟机栈区域开辟两块空间叫做“栈帧”。为main栈帧和add栈帧(一个方法为一块栈帧)。

    每块栈帧的数据结构又为:

    那他们底层是如何运行的?这个时候我们需要通过class文件的jvm指令来分析了。

    我们通过java自带的javap -c命令对改程序进行反汇编:

    public int add();

    Code:

    0: iconst_1

    1: istore_1

    2: iconst_2

    3: istore_2

    4: iload_1

    5: iload_2

    6: iadd

    7: bipush 100

    9: imul

    10: istore_3

    11: iload_3

    12: ireturn

    public static void main(java.lang.String[]) throws java.lang.InterruptedException;

    Code:

    0: new #4 // class bat/ke/qq/com/App

    3: dup

    4: invokespecial #5 // Method "":()V

    7: astore_1

    8: aload_1

    9: invokevirtual #6 // Method add:()I

    12: istore_2

    13: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;

    16: iload_2

    17: invokevirtual #8 // Method java/io/PrintStream.println:(I)V

    20: return

    }

    然后我们通过java汇编指令手册找到这些指令在jvm中代表什么意思:

    需要这手册的童鞋可以关注文章末尾公众号,输入关键字JVM获取。

    整个的过程,大家可以关注公众号获取相应的视频讲解。

    欢迎关注公众号:Java大型网站架构

    展开全文
  • 深入理解JVM虚拟机

    2021-06-27 15:54:35
    首先使用javac命令将.java文件编译成字节码文件也就是.class文件,之后交给JVM虚拟机去执行,从而转换成特定平台的执行指令,这也是为什么一份相同的.class文件可以在不同的操作系统上运行的原因,下图是一张java...

    一、Compile Once,Run Anywhere如何实现

    首先使用javac命令将.java文件编译成字节码文件也就是.class文件,之后交给JVM虚拟机去执行,从而转换成特定平台的执行指令,这也是为什么一份相同的.class文件可以在不同的操作系统上运行的原因,下图是一张java项目从编译到执行的流程图:
    在这里插入图片描述

    二、JVM如何加载.class文件

    下图为java虚拟机的运行结构,分别由Class Loader、Execution Engine、Native Interface和Runtime Data Area组成,首先由Class Loader会读取特定格式的class文件,然后交给Execution Engine来对其解析,解析完成之后在通过Native Interface融合一些第三方类库,最后放到Runtime Data Area去运行即可;

    Class Loader:依据特定格式,加载class文件到内存中;
    Execution Engine:对命令进行解析;
    Native Interface:融合不同语言的开发库为Java所用;
    Runtime Data Area:JVM内存空间结构;
    在这里插入图片描述

    三、问类从编译到执行一共需要几步?

    假设现在有一个Demo.java文件,那它从编译到执行总共只需要三步即可:

    1.编译器将Demo.java文件编译成Demo.class字节码文件;
    2.ClassLoader将字节码转换为JVM中的Class(Demo)对象;
    3.JVM利用Class(Demo)对象实例化为Demo对象;

    四、ClassLoader到底是啥?

    ClassLoader在Java中有这非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。他是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。

    五、ClassLoader种类

    BootStrapClassLoader:C++编写,加载核心库java.*
    ExtClassLoader:Java编写,加载扩展库javax.*
    AppClassLoader:Java编写,加载程序所在目录
    自定义ClassLoader:Java编写,定制化加载

    六、自定义ClassLoader实战

    理论讲的在多,还是不懂啥是ClassLoader,该怎么用,那么接下来博主带领大家编写自己人生中的第一个ClassLoader(是不是第一无所谓0.0),让你感受Java真正的魅力:

    首先创建一个.java文件,放置在项目以外,然后将该文件编译成.class字节码,下图为.java文件内容:

    public class ClassLoaderDemo {
    	//.java文件内容只需要一个static代码块即可,当该类在进行实例化时会自动执行代码块中的内容;
        static{
            System.out.println("Hello, I'm loaded by a custom classloader");
        }
    }
    

    字节码文件地址如下图,项目在RocketMQ中(不要问为什么在这里面,我也不知道):
    在这里插入图片描述
    接着就是编写我们自己的ClassLoader啦(有点小激动):

    package com.intretech.classloader;
    
    import java.io.*;
    
    /**
     * 自定义ClassLoader
     * 主要实现两个方法即可:
     * 第一个是findClass方法,用于加载.class字节码文件
     * 第二个是defineClass方法,用于定义Class,当前自定义ClassLoader使用默认实现的方式即可
     */
    public class MyClassLoader extends ClassLoader{
    
        private String name;
        private String path;
    
        public MyClassLoader(String name,String path){
            this.name=name;
            this.path=path;
        }
    
        /**
         * 寻找需要加载的.class字节码文件
         * @param name .class字节码文件的名称
         * @return
         */
        @Override
        public Class findClass(String name){
            //加载.class字节码文件
            byte[] b=loaderClassData(name);
    
            //使用ClassLoader默认实现好的定义Class方法即可
            return defineClass(name,b,0,b.length);
        }
    
        /**
         * 加载.class字节码文件
         * @param name .class字节码文件的名称
         * @return
         */
        private byte[] loaderClassData(String name) {
            name = path+name+".class";
            InputStream in=null;
            ByteArrayOutputStream out=null;
            try{
                in = new FileInputStream(new File(name));
                out = new ByteArrayOutputStream();
                int i=0;
                while ((i =in.read())!= -1){
                    out.write(i);
                }
                return out.toByteArray();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                try {
                    in.close();
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }
    

    自定义ClassLoader写好了,接下来咱们来使用他吧:

    package com.intretech.classloader;
    
    /**
     * 自定义ClassLoader测试使用类
     */
    public class MyClassLoaderTest {
        
        public static void main(String[] args) throws IllegalAccessException, InstantiationException {
            //创建MyClassLoader对象,name随便填,只是个名称,path填需要加载的.class文件所在地址
            MyClassLoader myClassLoader = new MyClassLoader("myClassLoader", "F:\\YQ\\HolyGrail\\");
    
            //通过findClass定义出ClassLoaderDemo类
            Class classLoaderDemo = myClassLoader.findClass("ClassLoaderDemo");
    
            //实例化对象,并且会执行static代码块中的内容;即会在控制台打印"Hello, I'm loaded by a custom classloader"
            classLoaderDemo.newInstance();
        }
    
    }
    

    很简单吧,其实就是先通过findClass方法将.class文件加载成byte[],然后交给defineClass方法去执行得到对应的类,得到类之后就能为所欲为了0.0;

    七、ClassLoader的双亲委派机制

    加载一个类是有规则的,不是一通乱加载,首先是自底向上的,先从Customer ClassLoader查找是否加载过该类,有的话直接返回就好了,如果没有,那就交给他的上级,也就是App ClassLoader去查找,以此类推,一直到最顶层的Bootstrap ClassLoader,如果都没有加载过(好家伙,新来的),这时候就开始另一种方式了,也就是图中自项向下去尝试加载,首先是Bootstrap ClassLoader(因为现在球在它这嘛,所以当然是他先了),Bootstrap ClassLoader会去rt.jar中或者是指定的jar(这个位置可以自定义哦,方式就是启动jar包的时候加上-Xbootclasspath “路径”)去查找这个类,如果找到了这个类,那就加载完返回即可,如果没有,那就交给下级,去它们自己负责的路径里面查找有没有这个类,以此类推,如果说最终都没有找到这个类,好了,别启动了,直接给老子报异常,ClassNotFoundException,老老实实去解决吧。
    在这里插入图片描述
    说了一大堆,可能有些小伙伴没有听懂,没有关系,接下来咱们通过一段代码来看看ClassLoader到底是怎么加载的,是不是和上图的方式一致的,看下博主到底有没有在瞎说(也不是没有可能,所以下面一段代码认真看哦):

     protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
     		//加锁
            synchronized(this.getClassLoadingLock(name)) {
            	//先让当前ClassLoader去查找,是否有加载过该类
                Class<?> c = this.findLoadedClass(name);
                //没有的话,那就进来吧你
                if (c == null) {
                    long t0 = System.nanoTime();
    
                    try {
                    	//先判断是否有上级,也就是判断当前的ClassLoader是不是Bootstrap ClassLoader
                        if (this.parent != null) {
                        	//上级不为空,好办,让上级去查找
                            c = this.parent.loadClass(name, false);
                        } else {
                        	//上级为空了,说明是Bootstrap ClassLoader,那就专门调用orNull方法了
                        	//因为如果在没有找到说明就没有加载过嘛,当然就得返回null了
                            c = this.findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException var10) {
                    }
    				//因为上面第一种方式没有找到该类,接下来开始第二种方式了
                    if (c == null) {
                        long t1 = System.nanoTime();
                        //通过自项向下的方式去尝试加载该类了
                        c = this.findClass(name);
                        PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        PerfCounter.getFindClasses().increment();
                    }
                }
    
                if (resolve) {
                    this.resolveClass(c);
                }
                
                return c;
            }
        }
    

    这个注释加的可以吧,这下应该知道博主没有在瞎说了吧!0.0

    八、loadClass和forName的区别

    这两个家伙都是显式加载类的方式,你们如果不认识,那你们一定认识他们的亲兄弟——new,new是隐式加载类的方式,new出来的实例可以直接使用,而loadClass和forName加载的类需要在通过newInstance方法得到对应的实例才可使用,言归正传,那loadClass和forName到底有什么区别呢,咱们先了解一下类的装载过程(也有人叫加载过程,一样的):
    如下图,类的装载过程分为三步,loadClass装载类时只会到第二步,而forName会到第三步,这就是他们的区别,也就是loadClass不会初始化,而forName会。
    在这里插入图片描述
    可能你们又认为博主在乱说,接下来上证据:

    首先上的是loadClass源码,可以发现,resolve默认传的false,所以不会去执行resolveClass方法,这个方法从注释上来看就知道是要链接该类的意思,所以,loadClass在加载一个类时,只会进行到链接,根本到不了初始化;

    public Class<?> loadClass(String name) throws ClassNotFoundException {
            return loadClass(name, false);
    }
    
    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // First, check if the class has already been loaded
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
    
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
    
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }
    
    /**
         * Links the specified class.  This (misleadingly named) method may be
         * used by a class loader to link a class.  If the class <tt>c</tt> has
         * already been linked, then this method simply returns. Otherwise, the
         * class is linked as described in the "Execution" chapter of
         * <cite>The Java&trade; Language Specification</cite>.
         *
         * @param  c
         *         The class to link
         *
         * @throws  NullPointerException
         *          If <tt>c</tt> is <tt>null</tt>.
         *
         * @see  #defineClass(String, byte[], int, int)
         */
        protected final void resolveClass(Class<?> c) {
            resolveClass0(c);
        }
    

    我们在来看下forName的源码:
    重点关注initialize这个参数,它代表着是否要初始化,默认传的是true,所以我们可以清楚的判断出来forName加载类时是会进行初始化的;
    在这里插入图片描述
    有些小伙伴这时候会提问了,为什么需要有这两种方式,直接弄一种初始化的不就好了吗?(好问题),这是因为这两种方式都各有大用,比方说你在加载jdbc驱动时,你就需要去加载源码中的静态代码块,这时你就需要使用forName,loadClass在Spring IOC中则被大量使用,因为Spring IOC为了加快加载速度,使用了大量的懒加载,所以loadClass在加载速度方面的优势就提现出来了,所以我们可以知道一个道理:存在即合理;

    九、Java内存模型

    咱们先来看看一张老图,请各位小伙伴先思考一下,Java内存模型指的是下图中的哪个部分(等待10秒),聪明的小伙伴应该已经找到了,就是Runtime Data Area(你找对了吗);
    在这里插入图片描述
    上图仅仅是个开胃菜,想要真正搞懂Java内存模型还需要继续深入,本章使用JDK8的Java内存模型来讲解(其实后面的JDK9,JDK10…都是类似的),我们可以从两个角度去理解,一个是线程的角度,另一个就是存储的角度,咱们先从线程的角度来see see;

    如下图所示,线程可以分为线程私有和线程共享,像程序计数器、虚拟机栈、本地方法栈属于线程私有的,每个线程对应一个,而MetaSpace(元空间)、常量池和堆就属于线程共享的了,所有线程一起使用,看到这,详细大家都有同一个感觉就是:“懂了,但没完全懂”,没关系,博主接下来为各位小伙伴解释一下它们到底是个什么鬼;

    程序计数器(Program Counter Register):当前线程所执行的字节码行号指示器(逻辑计数器),通俗点说,就是记录当前线程运行到第几行代码了,如果需要执行下一行代码,那就改变计数器的值就可以了,因为cpu在特定时间只能处理一个线程(所谓的多线程其实是cpu在不停的切换线程执行,速度快到让你认为是在一起执行,但其实实质上还是单线程,除非你是多核),所以为了记录每个线程的执行位置,程序计数器和线程都是一对一关系即“线程私有”,并且线程技术器只对Java方法计数,如果是Native修饰的方法计数器的值为Undefined,因为计数器只几率行号,所以根本不用担心内存泄露问题哦!

    虚拟机栈(Stack):Java方法执行的内存模型,包含多个栈帧,栈帧用于存储局部变量表,操作栈,动态链接和方法出口等信息,当调用方法结束了,栈帧就会被删除,局部变量表包含方法执行过程中的所有变量,操作数栈包括入栈、出栈、复制、交换、产生消费变量的操作,大部分jvm字节码都把时间消耗在操作数栈的操作上,栈是一个后进先出的数据结构,除了入栈和出栈,栈帧不能直接被操作;

    本地方法栈:和虚拟机栈是类似是,不同的是本地方法栈是作用于有native修饰的方法,比如上文说的forName方法使用的就是本地方法栈;

    MetaSpace(元空间):所有线程共享,存储类的加载信息;

    堆(Heap):所有线程共享,主要是用来存放对象实例,同时也是GC管理的主要区域;
    在这里插入图片描述

    展开全文
  • java语言被称为跨平台的语言,而JVM可以被称之为跨语言的平台。只要是可以编译成class文件的语言都可以把classwen'ji

           JVM虚拟机class文件结构和加载过程

            java语言被称为跨平台的语言,而JVM可以被称之为跨语言的平台。很多语言如java,scala,kotlin,groovy,clojure,jython,jruby等等这些都可以编译成class文件。所以可以说JVM与java无关,任何语言只要可以编译成class文件就可以在jvm上运行。

           想要了解JVM那么首先需要了解java从编码到执行的过程。首先如我们前面所说xxx.java要经过javac编译成.class。然后通过ClassLoader load到内存,然后虚拟机还会把java类库也load到内存。因为java是编译和解释混合执行,接下来会分析热点代码编译执行,普通代码解释执行最后通过执行引擎执行写入硬件。可以用一张图来描述

     class文件是JVM运行的基础,那么先让我们来熟悉一下class文件的结构通常我们看到的是class文件16进制的格式我们来分析一下他的结构

    从版本号到常量池个数到常量池,代表方法接口class类名等。

           接下来我们要了解一下JVM加载类的过程。整个过程分为三个Loading、Linking、Initializing

    Linking又分为Verification,Preparation,Resolusion

          1.loading

    loading的过程是采用双亲委派机制。当需要加载一个class的时候当前类的Classloader会查询是否加载过,如果没有会委托父加载器检查(注意父加载器不是父类的加载器他们没有继承关系)。一直到最上面的Bootstrap如果没有在往下委派加载一直到能够把类加载进来。如果到最后也没能完成加载则抛出classNotfound异常

    加载器的层次关系如下

    双亲委派机制是Classloader的loadclass方法写好的逻辑。如果我们想要用自定义的classloader则需要重写findClass方法。而有些时候比如tomcat的热部署则需要打破双亲委派机制这时候我们就需要重写loadclass方法了。

          2.Linking

    进入到linking阶段,首先要对class文件进行校验Verification。如果不符合JVM规范则会在这个阶段返回异常。然后通过Preparation阶段将静态变量赋默认值。Resolusion阶段将符号引用解析为具体地址引用。

        3.Initializing最后Initializing则将静态变量赋初始值也就是初始化的过程。

     

    展开全文
  • 什么是多态及多态的应用了解 多态可以分为向上转型和向下转型,程序分为编译器和运行期。两则呈现不同的状态,称为多态。例如人、中国人、朝鲜人三个类。 public class Person { public void say(){ System.out....
  • 深入理解JVM虚拟机读书笔记——垃圾回收器

    千次阅读 多人点赞 2021-08-25 15:25:59
    注:本文参考自周志明老师的著作《深入理解Java虚拟机(第3版)》 如果说收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。《Java虚拟机规范》中对垃圾收集器应该如何实现并没有做出任何规定,因此...
  • 深入理解JVM虚拟机读书笔记——类的加载机制

    千次阅读 多人点赞 2021-08-25 17:59:28
    注:本文参考自周志明老师的著作《深入理解Java虚拟机(第3版)》 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作...
  • 资源内容:深入理解Java虚拟机全套完整视频教程|____6.杂谈.mp4|____5.jvm再体验-jvm可视化监控工具.mp4|____4.jvm初体验-内存溢出问题的分析与解决.mp4|____3.环境搭建以及jdk,jre,jvm的关系.mp4|____2.整个部分要...
  • 保姆式辅助,带你逐行分析java代码,更通透地了解jvm栈工作原理
  • 文章将同步到我的个人博客: www.how2playlife.com 本文是微信公众号【Java技术江湖】的《深入理解JVM虚拟机》其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客...
  • 限于篇幅,后面会继续分享自己对于JVM虚拟机深入的学习和了解,有需要 《深入JVM虚拟机第三版》 电子书可以添加我微信:abc730500468,那本书重点的部分我都打上了标记,便于你们刷,以及刷的过程有技术可以...
  • 深入理解JVM虚拟机读书笔记——运行时数据区

    千次阅读 多人点赞 2021-08-22 19:55:58
    下面就来通过深入学习 JVM 来进一步增加我们对 Java 这门编程语言的了解吧!(个人建议,最好能买来这本书去读一读,是非常有帮助的,当然,在看这本书之前,为了方便理解相关概念名词,可以先跟着某马程序员的视频...
  • 1. 简述JVM关闭了解JVM的关闭能帮我们在JVM关闭时做一些合理的事情。首先JVM的关闭方式可以分为三种:正常关闭、强制关闭、异常关闭在某些情况下,我们需要在JVM关闭时做些扫尾的工作,比如删除临时文件、停止日志...
  • 本文转自互联网,侵删本系列文章将整理到我在...的《深入理解JVM虚拟机》其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,...
  • JVM的永久代中会发生垃圾回收么 什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java并发专题 简述一下你对线程池的理解 Java中实现多线程有几种方法 如何停止一个正在运行的线程 volatile关键字的...
  • JVM 虚拟机手册

    2021-07-25 23:37:23
    前言 前段时间翻看自己多年以来攒下的满满家当 , 突然有一种满满的满足感 . 但是想想多年来找资料的艰辛 , 决定将这些文档整理出来, 分享给大家 . 笔记华而不实 , 其中可能也有不正确的地方 , ... 虚拟机栈和本地方法栈
  • 其实如果你经常解决服务器性能问题,那么这些问题就会变的非常常见,了解JVM内存也是为了服务器出现性能问题的时候可以快速的了解那块的内存区域出现问题,以便于快速的解决生产故障。 先看一张图,这张图能很清晰...
  • 深入理解JVM虚拟机 第二章 内存区域与内存溢出异常1. 概述2. 运行时数据区域2.1 程序计数器2.2 Java虚拟机栈2.3 本地方法栈2.4 Java堆2.5 方法区2.6 运行时常量池2.7 直接内存3. HotSpot 虚拟机对象探秘3.1 对象的...
  •  Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。 4.4.Serial Old...
  • 身为一个java程序员,怎么能不了解JVM呢,倘若想学习JVM,那就又必须要了解Class文件,Class之于虚拟机,就如鱼之于水,虚拟机因为Class而有了生命。《深入理解java虚拟机》中花了一整个章节来讲解Class文件,可是看...
  • 了解服务注册与发现的基本原理后,如果你要在项目中使用服务注册与发现组件,当面对众多的开源组件该如何进行技术选型? 在互联网公司里,有研发实力的大公司一般会选择自研或者基于开源组件进行二次开发,但是对于...
  • 我们接下来看一下java运行时数据区包含程序计数器,虚拟机栈,本地方法栈,方法区,堆,其中程序计数器,虚拟机栈,本地方法区属于指令,方法区和堆属于数据。一、程序计数器用来指示程序执行哪一条指令,这跟汇编语言的程序...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,710
精华内容 16,684
关键字:

深入了解jvm虚拟机