Java底层知识:JVM笔记

谈谈你对java的理解

  • 平台无关性
  • GC
  • 语言特性(泛型、反射、兰姆达表达式)
  • 面向对象(封装、继承、多态)
  • 类库
  • 异常处理

平台无关性如何实现

Compile Once, Run Anywhere
在这里插入图片描述
java源码首先被编码成字节码(.class文件),再由不同平台的JVM进行解析。java语言在不同平台上运行时不需要进行重新编译,java虚拟机在执行字节码的时候,把字节码转换为具体平台上的机器指令。

为什么JVM不直接将源码解析成机器码去执行

  • 准备工作:每次执行都需要进行各种检查
  • 兼容性:也可以将别的语言解析成字节码

JVM如何加装.class文件

在这里插入图片描述

什么是反射

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

反射就是把java类中的各种成分映射成一个个java对象

写一个反射的例子

package javabasic.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 javabasic.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("javabasic.reflect.Robot");
        Robot r = (Robot) rc.newInstance();
        System.out.println("Class name is "+ rc.getName());
        //r.sayHi("bob");
        Method getHello = rc.getDeclaredMethod("throwHello",String.class);
        getHello.setAccessible(true);//throwHello是私有方法,直接访问会报错
        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");
        name.setAccessible(true);
        name.set(r,"Alice");
        sayHi.invoke(r, "Welcome");
    }
}

//input
Class name is javabasic.reflect.Robot
getHello result is HelloBob
Welcome null
Welcome Alice

Process finished with exit code 0

谈谈ClassLoader

类从编译到执行的过程

  • 编译器将Robot.java源文件编译成Robot.class字节码文件
  • ClassLoader将字节码转换为JVM中的Class对象
  • JVM利用Class对象实例化Robot对象

谈谈ClassLoader

ClassLoader主要工作在类加载阶段,其主要作用是从系统外获得Class二进制数据流。

它是java的核心组件,所有的Class都由ClassLoader进行加载,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给java虚拟机进行连接、初始化等操作。

ClassLoader的种类

在这里插入图片描述

自定义ClassLoader的实现

关键函数:

protected Class<T> findClass(String name) throws ClassNotFoundException{
        throw new ClassNotFoundException(name);
    }
    protected  final Class<?> defineClass(byte[] b, int  off, int len) throws ClassFormatError{
        return defineClass(null, b, off, len, null);
    }
package javabasic.reflect;

import java.io.*;

public class MyClassLoader extends ClassLoader{
    private String path;
    private String classLoaderName;

    public MyClassLoader(String path, String classLoaderName){
        this.path = path;
        this.classLoaderName = classLoaderName;
    }

    //用于寻找类文件
    public Class findClass(String name){
        byte[] b = new byte[0];
        try {
            b = loadClassData(name);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return defineClass(name, b, 0,b.length);
    }

    //用于加载类文件
    private byte[] loadClassData(String name) throws IOException {
        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);
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            out.close();
            in.close();
        }
        return out.toByteArray();
    }
}

双亲委派机制

在这里插入图片描述

过程

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。

使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。

为什么要设计这种机制

这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。

类加载方式

  • 隐式加载 new
  • 显式加载 loadClass,forName等

loadClass和forName的区别

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

类的装载过程
  • 类加载: 通过classloader加载字节码文件,生成class对象
  • 链接
    • 校验:检查加载的class的正确性和安全性
    • 准备:为类变量分配存储空间并设置类变量的初始值
    • 解析:JVM将常量池内的符号引用转换为直接引用
  • 初始化 :执行类变量赋值和静态代码块

java 的内存模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 程序计数器

    • 当前线程所执行的字节码行号指示器(逻辑
    • 改变计数器的值来选取下一条所需要执行的字节码指令
    • 和线程是一对一的关系(线程私有
    • 对java方法计数,如果是native方法则计数器的值为undefined
    • 不必担心内存泄漏
  • java虚拟机栈

    • java方法执行的内存模型
    • 包含多个栈帧,栈帧包含了局部变量表,操作栈,动态链接,返回地址等

    局部变量表和操作数栈
    局部变量表包含方法执行过程中的所有变量
    操作数栈:入栈,出栈,赋值,交换,产生消费变量

  • 本地方法栈 :与虚拟机栈类似,主要作用于标注了native的方法
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 元空间和永久代的区别
    元空间使用本地内存,永久代使用的是jvm的内存

  • meta Space比permGen的优势

    • 字符串常量池存在永久代中,容易出现性能问题和内存溢出
    • 类方法的信息大小难确定,给永久代的大小指定带来困难
    • 永久代会给GC带来不必要的复杂性
    • 方便HotSpot与其他JVM如Jrockit的集成
  • Java堆

    • 对象实例的分配区域
    • GC管理的主要区域

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值