JVM基础

自身对Java的理解(Java的一些特点):

  1. 平台无关性:一次编译,到处运行。
  2. 垃圾回收机制(GC)
  3. 语言特性:泛型、反射
  4. 面向对象:封装、继承、多态
  5. 自带的一些类库
  6. 有异常处理机制

1、Java如何实现平台无关???

Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令 。

2、JVM如何加载.class文件

JVM主要由类加载器、运行时数据区、执行引擎和本地接口组成。 太主要通过类加载器将class文件加载到内存中,并通过执行引擎解析文件中的字节码并提交给操作系统运行。

                        

  • 类加载器:依据特定格式,加载class文件到内存
  • 执行引擎:对命令进行解析
  • 本地接口:融合不同开发语言的原生库为Java所用
  • 运行时数据区:JVM内存空间结构模型

3、什么是Java反射机制

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

实例:通过java反射机制来调用Robot类中的属性和方法

//创建Robot类
package com.javabasic.bytecode.reflect;

public class Robot {
    private String name;
    public void sayHi(String helloSentence){
        System.out.println(helloSentence + " " + name);
    }
    private String throwHello(String tag){
        return "Hello " + tag;
    }
}
package com.javabasic.bytecode.reflect;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectSample {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class rc = Class.forName("com.javabasic.bytecode.reflect.Robot");//获取类
        Robot r = (Robot) rc.newInstance();//实例化对象
        System.out.println("class name is" + rc.getName());
        Method getHello = rc.getDeclaredMethod("throwHello", String.class);//Method类获取方法
        getHello.setAccessible(true);//获取私有方法和属性时必须调用此方法,修改方法参数为true
        Object str = getHello.invoke(r,"Bob");
        System.out.println("getHello result is " + str);
        Method sayHi = rc.getMethod("sayHi", String.class);
        sayHi.invoke(r,"welcome");
        Field name = rc.getDeclaredField("name");//Field类获取属性
        name.setAccessible(true);
        name.set(r,"Bob");
        sayHi.invoke(r,"welcome");
    }
}

 

4、类从编译到执行的过程

  • 编译器先将.Java原文及编译为.class字节码文件。
  • 类加载器将字节码转换成JVM中的类的对象。
  • JVM利用类的对象经行对象的实例化

5、谈谈类加载器

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

6、类加载器的种类

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

7、类加载器的双亲委派机制

                       

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。例如:如上图所示:MyClassLoader->AppClassLoader->Ext-ClassLoader->BootStrap.自定定义的MyClassLoader1首先会先委托给AppClassLoader,AppClassLoader会委托给ExtClassLoader,ExtClassLoader会委托给BootStrap,这时候BootStrap就去加载,如果加载成功,就结束了。如果加载失败,就交给ExtClassLoader去加载,如果ExtClassLoader加载成功了,就结束了,如果加载失败就交给AppClassLoader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的MyClassLoader1类加载器加载,如果加载失败,就报ClassNotFoundException异常,结束。

使用双亲委派机制的原因:避免类被重复加载,加载一次,多次使用。以下为classloader源码,classloader的执行顺序就是:loadClass→findClass→defineClass。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先先检查该类是否被加载,如果被加载过就不重复加载
            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;
        }
    }

8、类的加载方式

  • 隐式加载:new
  • 显式加载:loadclass,forName等
public class LoadDifference {
    public static void main(String[] args) throws ClassNotFoundException {
        Robot robot = new Robot();//隐式加载
        ClassLoader cl = Robot.class.getClassLoader();//显式加载
        Class r = Class.forName("com.javabasic.bytecode.reflect.Robot");//显式加载
    }
}

9、LoadClass和forName的区别

  • Class.forName得到的class是已经初始化完成的
  • Classloader.loadClass得到的class是还没有链接的

10、JVM内存模型

                    

  • 线程私有:程序计数器、虚拟机栈、本地方法栈
  • 线程共享:Java堆、方法区

1)、程序计数器(Program Counter Register)

  • 当前线程所执行的字节码的行号指示器。
  • 通过改变计数器的值来选取下一条需要执行的字节码指令,如分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
  • 和线程是一对一关系即“线程私有”。
  • 对Java方法计数,如果是Native方法则计数器值为空(Undefined)。
  • 此内存区域不会发生内存泄露问题。

2)、Java虚拟机栈(Java Virtual Machine Stacks)

  • 它的生命周期与线程相同。
  • 它描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

                                                   

其中局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。操作数栈其实就是实现对局部变量的的操作,如变量的运算、赋值等。在Java 虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。

3)、本地方法栈

与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

4)、方法区

方法区用来存储class的基本信息,如类的方法、常量、静态变量、即时编译器编译后的代码等数据。方法区有一种叫法是元空间(Meta Space),也有人叫“永久代”(Permanent Generation)。两者并不等价,但是两者都是方法区的实现。在Java7之后,原先位于方法区的字符串常量池被移动到Java堆中,在jdk1.8之后,用元空间替代了永久代。两者最大的区别是,永久代使用的是jvm的内存,而元空间使用的是本地内存。元空间相较于永久代有下面几个优势:字符串常量池在永久代中,容易出现性能问题和内存溢出;类和方法的信息大小比较难确定,给永久代大小的指定带来困难;永久代会给GC带来不必要的复杂性,效率较低。

5)、Java 堆

是对象实例的分配区域,是GC管理的主要区域。

11、常见面试题

1)、JVM三大性能调优参数-Xms、-Xmx、-Xss的含义

-Xss:规定了每个线程虚拟机栈的大小,一般256k足够,此配置将会影响此进程中并发线程数的大小。

-Xmx:堆的初始大小,即该进程刚刚创建时他的专属Java堆的大小,一旦对象的容量超过Java堆的最大容量,将会扩容至-Xms设置的大小

-Xms:堆能达到的最大值,但是在设置的时候往往将-Xmx和-Xms的大小设置为一样,因为当堆不够用要扩容时,会发生内存抖动,影响程序运行时的稳定性。

2)、Java内存模型中内存分配策略

  • 静态存储:编译时确定每个数据目标在运行时的存储空间需求,这种方式不允许有可变的数据类型存在。
  • 栈式存储:程序模块的数据区大小在编译的时候时未知的,只有在运行到程序模块入口前才会确定大小,从而分配内存空间
  • 堆式存储:编译时或运行到程序模块入口前都无法确定所需内存大小,动态分配。

3)、Java内存模型中堆和栈的联系

引用对象、数组时,栈里定义变量来保存堆中的目标地址的首地址,如下图:

                   

4)、Java内存模型中堆和栈的区别

  • 管理方式:栈自动释放,堆需要GC
  • 空间大小:栈比堆小
  • 碎片相关:栈产生的碎片小于堆
  • 分配方式:栈支持静态分配和动态分配,而堆只支持动态分配
  • 效率:栈的效率高于堆
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值