JVM分析(一)_myeclipse全欧东tomcat object newlock = new object(); -程序员宅基地

技术标签: JVM  

还没有深入的理解,所以先记录下自己学到的东西
一说jvm,首先想到的莫过于类加载,而类加载的过程大致分为加载,验证,准备,解析,初始化。
这里写图片描述
加载
所谓的加载,即是将对应的class文件加载到jvm中,但是这涉及到一个问题,就是加载是需要类加载器来执行的,那么先说下类加载器。

虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类,JVM提供了3种类加载器:

  • 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
  • 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  • 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
    JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。

双亲加载机制的一大特点在于安全性,可以使用特定的加载器来加载特定的代码,防止出现的恶意的相同全路径的代码包。
接下来我们看下类加载器的代码

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    //getClassLoadingLock是为了获取
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                //双亲加载机制,如果parent可以加载则优先由parent进行
                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;
        }
    }
protected Object getClassLoadingLock(String className) {
        Object lock = this;
        //parallelLockMap 是一个ConcurrentHashMap
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

加载是类加载的第一个阶段。有两种时机会触发类加载:

1、预加载。虚拟机启动时加载,加载的是JAVA_HOME/lib/下的rt.jar下的.class文件,这个jar包里面的内容是程序运行时非常常常用到的,像java.lang.、java.util.、java.io.*等等,因此随着虚拟机一起加载。要证明这一点很简单,写一个空的main函数,设置虚拟机参数为”-XX:+TraceClassLoading”来获取类加载信息,运行一下:

[Opened E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.Object from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.io.Serializable from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.Comparable from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.CharSequence from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.String from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.reflect.GenericDeclaration from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.reflect.Type from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.reflect.AnnotatedElement from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.Class from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

[Loaded java.lang.Cloneable from E:\MyEclipse10\Common\binary\com.sun.java.jdk.win32.x86_64_1.6.0.013\jre\lib\rt.jar]

2、运行时加载。虚拟机在用到一个.class文件的时候,会先去内存中查看一下这个.class文件有没有被加载,如果没有就会按照类的全限定名来加载这个类。

那么,加载阶段做了什么,其实加载阶段做了有三件事情:

  1. 获取.class文件的二进制流

  2. 将类信息、静态变量、字节码、常量这些.class文件中的内容放入方法区中

  3. 在内存中生成一个代表这个.class文件的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。一般这个Class是在堆里的,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的

虚拟机规范对这三点的要求并不具体,因此虚拟机实现与具体应用的灵活度都是相当大的。例如第一条,根本没有指明二进制字节流要从哪里来、怎么来,因此单单就这一条,就能变出许多花样来:

从zip包中获取,这就是以后jar、ear、war格式的基础

从网络中获取,典型应用就是Applet

运行时计算生成,典型应用就是动态代理技术

由其他文件生成,典型应用就是JSP,即由JSP生成对应的.class文件

从数据库中读取,这种场景比较少见

总而言之,在类加载整个过程中,这部分是对于开发者来说可控性最强的一个阶段。
验证

连接阶段的第一步,这一阶段的目的是为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

Java语言本身是相对安全的语言(相对C/C++来说),但是前面说过,.class文件未必要从Java源码编译而来,可以使用任何途径产生,甚至包括用十六进制编辑器直接编写来产生.class文件。在字节码语言层面上,Java代码至少从语义上是可以表达出来的。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。

验证阶段将做一下几个工作,具体就不细讲了,这是虚拟机实现层面的问题:

1、文件格式验证

这个地方要说一点和开发者相关的。.class文件的第5~第8个字节表示的是该.class文件的主次版本号,验证的时候会对这4个字节做一个验证,高版本的JDK能向下兼容以前版本的.class文件,但不能运行以后的class文件,即使文件格式未发生任何变化,虚拟机也必须拒绝执行超过其版本号的.class文件。举个具体的例子,如果一段.java代码是在JDK1.6下编译的,那么JDK1.6、JDK1.7的环境能运行这个.java代码生成的.class文件,但是JDK1.5、JDK1.4乃更低的JDK版本是无法运行这个.java代码生成的.class文件的。如果运行,会抛出java.lang.UnsupportedClassVersionError,这个小细节,务必注意。

2、元数据验证

3、字节码验证

4、符号引用验证

准备

准备阶段是正式为类变量分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配。关于这点,有两个地方注意一下:

1、这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中

2、这个阶段赋初始值的变量指的是那些不被final修饰的static变量,比如”public static int value = 123;”,value在准备阶段过后是0而不是123,给value赋值为123的动作将在初始化阶段才进行;比如”public static final int value = 123;”就不一样了,在准备阶段,虚拟机就会给value赋值为123。

各个数据类型的零值如下图:

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。来了解一下符号引用和直接引用有什么区别:

1、符号引用。

这个其实是属于编译原理方面的概念,符号引用包括了下面三类常量:

类和接口的全限定名

字段的名称和描述符

方法的名称和描述符

这么说可能不太好理解,结合实际看一下,写一段很简单的代码:

package com.xrq.test6;

public class TestMain

{

private static int i;

private double d;



public static void print()

{



}



private boolean trueOrFalse()

{

    return false;

}

}

用javap把这段代码的.class反编译一下:

Constant pool:

   #1 = Class              #2             //  com/xrq/test6/TestMain

   #2 = Utf8               com/xrq/test6/TestMain

   #3 = Class              #4             //  java/lang/Object

   #4 = Utf8               java/lang/Object

   #5 = Utf8               i

   #6 = Utf8               I

   #7 = Utf8               d

   #8 = Utf8               D

   #9 = Utf8               <init>

  #10 = Utf8               ()V

  #11 = Utf8               Code

  #12 = Methodref          #3.#13         //  java/lang/Object."<init>":()V

  #13 = NameAndType        #9:#10         //  "<init>":()V

  #14 = Utf8               LineNumberTable

  #15 = Utf8               LocalVariableTable

  #16 = Utf8               this

  #17 = Utf8               Lcom/xrq/test6/TestMain;

  #18 = Utf8               print

  #19 = Utf8               trueOrFalse

  #20 = Utf8               ()Z

  #21 = Utf8               SourceFile

  #22 = Utf8               TestMain.java

看到Constant Pool也就是常量池中有22项内容,其中带”Utf8″的就是符号引用。比如#2,它的值是”com/xrq/test6/TestMain”,表示的是这个类的全限定名;又比如#5为i,#6为I,它们是一对的,表示变量时Integer(int)类型的,名字叫做i;#6为D、#7为d也是一样,表示一个Double(double)类型的变量,名字为d;#18、#19表示的都是方法的名字。

那其实总而言之,符号引用和我们上面讲的是一样的,是对于类、变量、方法的描述。符号引用和虚拟机的内存布局是没有关系的,引用的目标未必已经加载到内存中了。

2、直接引用

直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机示例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经存在在内存中了。

初始化

初始化阶段是类加载过程的最后一步,初始化阶段是真正执行类中定义的Java程序代码(或者说是字节码)的过程。初始化过程是一个执行类构造器()方法的过程,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。把这句话说白一点,其实初始化阶段做的事就是给static变量赋予用户指定的值以及执行静态代码块。

注意一下,虚拟机会保证类的初始化在多线程环境中被正确地加锁、同步,即如果多个线程同时去初始化一个类,那么只会有一个类去执行这个类的()方法,其他线程都要阻塞等待,直至活动线程执行()方法完毕。因此如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞。不过其他线程虽然会阻塞,但是执行()方法的那条线程退出()方法后,其他线程不会再次进入()方法了,因为同一个类加载器下,一个类只会初始化一次。实际应用中这种阻塞往往是比较隐蔽的,要小心。

Java虚拟机规范严格规定了有且只有5种场景必须立即对类进行初始化,这4种场景也称为对一个类进行主动引用(其实还有一种场景,不过暂时我还没弄明白这种场景的意思,就先不写了):

1、使用new关键字实例化对象、读取或者设置一个类的静态字段(被final修饰的静态字段除外)、调用一个类的静态方法的时候

2、使用java.lang.reflect包中的方法对类进行反射调用的时候

3、初始化一个类,发现其父类还没有初始化过的时候

4、虚拟机启动的时候,虚拟机会先初始化用户指定的包含main()方法的那个类

除了上面4种场景外,所有引用类的方式都不会触发类的初始化,称为被动引用,接下来看下被动引用的几个例子:

1、子类引用父类静态字段,不会导致子类初始化。至于子类是否被加载、验证了,前者可以通过”-XX:+TraceClassLoading”来查看

public class SuperClass

{
    

    public static int value = 123;



    static

    {

        System.out.println("SuperClass init");

    }

}



public class SubClass extends SuperClass

{
    

    static

    {

        System.out.println("SubClass init");

    }

}



public class TestMain

{
    

    public static void main(String[] args)

    {

        System.out.println(SubClass.value);

    }

}

运行结果为:

SuperClass init

123

2、通过数组定义引用类,不会触发此类的初始化

public class SuperClass

{

    public static int value = 123;



    static

    {

        System.out.println("SuperClass init");

    }

}



public class TestMain

{

    public static void main(String[] args)

    {

        SuperClass[] scs = new SuperClass[10];

    }

}

运行结果为:

1

3、引用静态常量时,常量在编译阶段会存入类的常量池中,本质上并没有直接引用到定义常量的类

public class ConstClass

{

    public static final String HELLOWORLD =  "Hello World";



    static

    {

        System.out.println("ConstCLass init");

    }

}



public class TestMain

{

    public static void main(String[] args)

    {

        System.out.println(ConstClass.HELLOWORLD);

    }

}

运行结果为:

Hello World

在编译阶段通过常量传播优化,常量HELLOWORLD的值”Hello World”实际上已经存储到了NotInitialization类的常量池中,以后NotInitialization对常量ConstClass.HELLOWORLD的引用实际上都被转化为NotInitialization类对自身常量池的引用了。也就是说,实际上的NotInitialization的Class文件中并没有ConstClass类的符号引用入口,这两个类在编译成Class之后就不存在任何联系了。

转自
https://mp.weixin.qq.com/s?__biz=MjM5NzMyMjAwMA==&mid=2651480924&idx=1&sn=4246b09ad87c839ed2860b7471663ed5&chksm=bd250f238a5286350bf640a6b2869d2d062c5c82cffbb925825a4d5da3004fbdc2afa3459271&mpshare=1&scene=1&srcid=0401KUsqK1lMYR4T2OJCHs1v#rd
转自
https://blog.csdn.net/zhangliangzi/article/details/51319033
转自
http://www.importnew.com/25295.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/sgnever/article/details/79794989

智能推荐

asp.net 几种跨页面传值的方法_asp.net数据传递到另一个页面-程序员宅基地

文章浏览阅读941次。第一种方法:通过URL链接地址传递,也即QueryString优点:实现起来非常简单缺点:传递的值是会显示在浏览器的地址栏上的(不安全),同时又不能传递对象,但是在传递的值少而安全性要求不高的情况下,这个方法还是一个不错的方案send.aspxResponse.Redirect("receive.aspx?name=xxx");receive.aspxstring us_asp.net数据传递到另一个页面

女网红靠GPT-4交1000+男朋友,聊天按分钟收费,一周收入50万!-程序员宅基地

文章浏览阅读629次。点击“开发者技术前线”,选择“星标”让一部分开发者看到未来来自:量子位 | 公众号 QbitAI注意看,这个女人叫卡琳,靠着GPT-4,她现在同时谈着1000+男朋友。对,我知道事情听上去有些离谱。就连GPT-4自己,都直呼“我一个AI都觉得非常不常见”。但是先别急,因为更让人挠头的事情还在后面……在与“卡琳”相处的过程中,男友们每聊一分钟天,就要支付给她1美元。并且就在我敲字这档口,候选男友列表..._这位在snapchat上坐拥180万粉丝的网红,已经利用包括gpt-4在内的一波最新ai技术,直

AmberTools23的安装-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏8次。e. 配置环境 即 在 用户根目录下 修改 .bashrc文件,添加 export PATH=/home/user/miniconda/bin:$PATH 其中 User换成用户名,并最好核实一下安装路径。5、安装跑完就可以了,平时要用的话,首先要 激活环境 conda activate AmberTools23 然后就可以开始命令计算了。2、利用终端对*.sh文件进行可执行授权 (在文件目录下执行 chmod +x ./*.sh,星号换成文件名)c. 确认安装路径,或者按需修改。_ambertools

高中信息技术python练习题_Python基础练习题5-程序员宅基地

文章浏览阅读5.2k次,点赞2次,收藏5次。import random"""1、实现剪刀石头布游戏,提示用户输入要出的拳 :石头(1)/剪刀(2)/布(3)/退出(4) 电脑随机出拳比较胜负,显示 用户胜、负还是平局。(提示:while循环加条件判断,做判断时建议先分析胜负的情况)user random1 2 1-2 = -12 3 2-3 = -13 1 3- 1 ..._高中信息技术python编程例题

编译型语言,解释型语言,动态语言,静态语言介绍_动态语言解释型语言-程序员宅基地

文章浏览阅读848次。编译型语言,解释型语言,动态语言,静态语言介绍_动态语言解释型语言

FTP服务器的搭建与文件目录详解_vsftpd添加用户并创建目录-程序员宅基地

文章浏览阅读8k次,点赞8次,收藏27次。文件传输协议FTP(File Transfer FTP)作为网络共享文件的传输协议,在网络应用软件中具有广泛的应用。FTP协议FTP是TCP/IP的协议簇协议之一,其主要功能是借助网络实现远距离主机间的文件传输。_vsftpd添加用户并创建目录

随便推点

C语言函数调用栈_函数指针跳转的堆栈问题-程序员宅基地

文章浏览阅读855次。程序的执行过程可看作连续的函数调用。当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行。函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。 不同处理器和编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一样的。1 寄存器分配 寄存器是处理器加工数据或运行程序的重要载体,用于存..._函数指针跳转的堆栈问题

别再瞎考证了!关于网络安全行业的证书,看完这篇,明明白白考证_网安参加比赛重要还是考证重要-程序员宅基地

文章浏览阅读742次。3、如果你是银行证券等金融行业,可报考CISA(国际注册信息系统审计师)、CISSP(国际注册信息安全专家)、CISM(国际注册信息安全经理)。4、如果你是安全技术支持、技术售前、工程师,可报考 CISSP(注册信息安全专家)、CISP(注册信息安全人员)。6、如果你专注于应急响应,可报考CISP-IRE(注册应急响应工程师)、CISP-IRE(注册应急响应专家)。5、如果你专注于渗透测试,可报考 CISP-PTE(注册渗透测试工程师)、CISP-PTS(注册渗透专家)。_网安参加比赛重要还是考证重要

怎样查找linux内核所在的分区,linux运维笔记:CentOS 系统的分区、启动及目录查询...-程序员宅基地

文章浏览阅读655次。8种机械键盘轴体对比本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?写在前面主要整理初次CentOS的基本操作,可能会显得比较乱。系统分区CentOS分区基本规则分区名称空间大小/boot200M/swap4G/20G/data剩余磁盘空间注:如果是虚拟机仅仅用来学习linux,磁盘空间有限的情况下,处 /boot 、/swap 之外的空间都可以分给 / 根目录,没有必要再分出/data..._内核在哪个分区

JavaScript随机生成一个满足数独结果的二维数组-程序员宅基地

文章浏览阅读219次。【代码】JavaScript随机生成一个满足数独结果的二维数组。

初识Cpp之 一、基础知识-程序员宅基地

文章浏览阅读1.8k次。​ C++融合了三种不同的编程方式:1、C语言代表的过程性语言,2、C++在C语言的基础上添加的类代表的面向对象语言,3、C++模板支持的泛型编程。使用C++的原因之一是为了利用其面向对象的特性。要利用这种特性,必须对标准C语言知识有较为深入的了解,因为它提供了基本类型、运算符、控制结构和语法规则。​ 面向对象编程(Object Oriented Programming, OOP)的诞生,试图让语言来满足问题的要求,其理念是设计与问题本质特性相对应的数据格式。_cpp

计算机教研评课记录,信息技术2.0 | 评课磨课共成长 信息技术促进步 ——东光县第二实验小学信息技术2.0数学组 课例研讨...-程序员宅基地

文章浏览阅读6.8k次。在我校推广信息技术2.0活动中,数学教研组成员推出高世甜老师在二年级进行《认识时间》的磨课评课活动。活动中二年级组数学教师和青蓝工程教师及教务处何春香主任参加听评课活动。高老师利用现代化教学手段,优化教学过程,突破教学重点、难点。利用微课视频,引导学生自学钟面的认识和时间的认读方法,通过开展小组合作,锻炼了学生自主学习、合作交流和语言表达能力。通过信息技术手段对各班学生进行学情分析,根据每个班的学..._信息技术2.0教研组听评课记录

推荐文章

热门文章

相关标签