美文网首页程序员
JVM问题(二)

JVM问题(二)

作者: WinkTink | 来源:发表于2019-11-21 21:22 被阅读0次

    21. JVM初始化步骤?

            1)假如这个类还没有被加载和连接,则程序先加载并连接该类。

            2) 假如该类的直接父类还没有被初始化,则先初始化其直接父类。

            3)假如类中有初始化语句,则系统依次执行这些初始化语句。

    类初始化时机:只有当对类主动使用的时候才会导致类的初始化,类的主动使用包括以下四种:

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

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

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

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

    以上四种情况称为主动使用,其他的情况均称为被动使用,被动使用不会导致初始化。

    22. 内存分配方式?

            有指针碰撞空闲列表两种分配方式:选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的GC是否带有压缩整理功能决定;因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用的分配算法时指针碰撞,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表。

    此外还有对象创建的并发问题:

    (1)堆分配内存空间的动作进行同步处理--采用CAS配上失败重试的方式保证更新操作的原子性;

    (2)把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓存(Thread Local Allocation Buffer,TLAB)

            注:指针碰撞:内存是规整的,空闲列表:内存不是规整的

    23.对象的创建?

           Java是一门面向对象的语言,Java程序运行过程中无时无刻都有对象被创建出来。在语言层面上,创建对象(克隆、反序列化)就是一个new关键字而已,但是虚拟机层面上却不是如此。看一下在虚拟机层面上创建对象的步骤:

            1、虚拟机遇到一条new指令,首先去检查这个指令的参数能否在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化。如果没有,那么必须先执行类的初始化过程。

            2、类加载检查通过后,虚拟机为新生对象分配内存。对象所需内存大小在类加载完成后便可以完全确定,为对象分配空间无非就是从Java堆中划分出一块确定大小的内存而已。这个地方会有两个问题:

            1)如果内存是规整的,那么虚拟机将采用的是指针碰撞法来为对象分配内存。意思是所有用过的内存在一边,空闲的内存在另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针向空闲那边挪动一段与对象大小相等的距离罢了。如果垃圾收集器选择的是Serial、ParNew这种基于压缩算法的,虚拟机采用这种分配方式。

            2)如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表法来为对象分配内存。意思是虚拟机维护了一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。如果垃圾收集器选择的是CMS这种基于标记-清除算法的,虚拟机采用这种分配方式。

            另外一个问题及时保证new对象时候的线程安全性。因为可能出现虚拟机正在给对象A分配内存,指针还没有来得及修改,对象B又同时使用了原来的指针来分配内存的情况。虚拟机采用了CAS配上失败重试的方式保证更新更新操作的原子性和TLAB两种方式来解决这个问题。

            3、内存分配结束,虚拟机将分配到的内存空间都初始化为零值(不包括对象头)。这一步保证了对象的实例字段在Java代码中可以不用赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值。

            4、对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息存放在对象的对象头中。

            5、执行<init>方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

    24. Class.forName()和ClassLoader.loadClass()区别?

            Class.forName():将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;

            ClassLoader.loadClass():只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。

    :Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。

    25. 什么是双亲委派模型?

            除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。顺序依次是:

            Bootstrap ClassLoader::启动类加载器,加载java_home/lib中的类。

            Extension ClassLoader:扩展类加载器,加载java_home/lib/ext目录下的类库。

            Application ClassLoader::应用程序类加载器,加载用户类路径上指定类库。

            双亲委派模型的工作原理是:如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求时,加载器才尝试自己加载。这种方式保证了Oject类在各个加载器加载环境中都是同一个类。

    26. 双亲委派模型的好处?

            它的好处可以用一句话总结,即防止内存中出现多份同样的字节码。从反向思考这个问题,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,多个类加载器都去加载这个类到内存中,系统中将会出现多个不同的Object类,那么类之间的比较结果及类的唯一性将无法保证,而且如果不使用这种双亲委派模型将会给虚拟机的安全带来隐患。所以,要让类对象进行比较有意义,前提是他们要被同一个类加载器加载。

            系统安全性:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。

            1)启动类加载器:\lib

            2)扩展类加载器:\lib\ext

            3)应用程序类加载器:加载用户类路径上所指定的类库

    相关文章

      网友评论

        本文标题:JVM问题(二)

        本文链接:https://www.haomeiwen.com/subject/ksdetctx.html