JAVA底层

谈谈你对java的理解

1:平台无关性

2: GC

3:语言特性

4:面向对象,封装继承多肽

5:类库 网络库 io nio 之类的

6:异常处理

1:平台无关性

 java 分为编译时,运行时

java 源码会编译为字节码,再有不同平台的jvm去解析,java语言在不同平台上运行时不需要进行重新编译,java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令

jvm如何加载class文件

jvm 是一个内存中的虚拟机,

Class Loader :依据特定格式加载class文件到内存

Execution Engine:对命令进行解析,解析完成之后到操作系统去执行 

Native Interface :融合不同开发语言、的原生库为java所用

Runtime Data Area:JVM内存空间结构模型

java反射

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

反射例子

创建test类

 

package com.test.hq;
public class Test {
    private String name;
    public void sayHi(String helloSentence) {
        System.out.println("helloSentence"+":"+name);
    }
    private String sayHello(String tag) {
        return "sayHello"+":"+tag;
    }
}
public class testStr {
    public static void main(String[] args) throws Exception {
        Class ts= Class.forName("com.test.hq.Test"); 
        Test test = (Test) ts.newInstance();
        Method getm = ts.getDeclaredMethod("sayHello", String.class);//可以获取所有方法包括私有  不能获取继承的方法 
        getm.setAccessible(true);
        Object invoke = getm.invoke(test, "test");
        System.out.println(invoke);
//        Method method = ts.getMethod("sayHi", String.class);//getMethod能获取到public 及继承的方法,但是不能获取私有的
//        System.out.println(invoke2);
        Field name = ts.getDeclaredField("name");
        name.setAccessible(true);
        name.set(test, "xiaoming");
        Method method = ts.getMethod("sayHi", String.class);
        Object invoke2 = method.invoke(test, "choose");
        
        }
}    

反射就是将java中的各种成分映射为一个个java对象。

类从编译到执行的过程

1:编译器将Test.java源文件编译成Test.class字节码文件。

2:Classloader(类加载器),将字节码文件转换为JVM中的Class<Test>对象。

3:jvm利用Class<Test>对象实例化为Test对象

1. 类加载的生命周期:加载(Loading)-->验证(Verification)-->准备(Preparation)-->解析(Resolution)-->初始化(Initialization)-->使用(Using)-->卸载(Unloading)

什么是Classloader?

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

Classloader是一个抽象类。

 

一.概述

 

定义:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型。类加载和连接的过程都是在运行期间完成的。

 

二. 类的加载方式

 

1):本地编译好的class中直接加载

2):网络加载:java.net.URLClassLoader可以加载url指定的类

3):从jar、zip等等压缩文件加载类,自动解析jar文件找到class文件去加载util类

4):从java源代码文件动态编译成为class文件

 

三.类加载的时机

 

1. 类加载的生命周期:加载(Loading)-->验证(Verification)-->准备(Preparation)-->解析(Resolution)-->初始化(Initialization)-->使用(Using)-->卸载(Unloading)

 

2.加载:这有虚拟机自行决定。

3.初始化阶段:

a)遇到new、getstatic、putstatic、invokestatic这4个字节码指令时,如果类没有进行过初始化,出发初始化操作。

b)使用java.lang.reflect包的方法对类进行反射调用时。

c)当初始化一个类的时候,如果发现其父类还没有执行初始化则进行初始化。

d)虚拟机启动时用户需要指定一个需要执行的主类,虚拟机首先初始化这个主类。

注意:接口与类的初始化规则在第三点不同,接口不要气所有的父接口都进行初始化。

 

四.类加载的过程

 

4.1.加载

 

a)加载阶段的工作

i.通过一个类的全限定名来获取定义此类的二进制字节流。

ii.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。

iii.在java堆中生成一个代表这个类的java.lang.Class对象,做为方法区这些数据的访问入口。

b)加载阶段完成之后二进制字节流就按照虚拟机所需的格式存储在方区去中。

 

4.2.验证

 

这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求。

a)文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。

b)元数据验证:对字节码描述的信息进行语义分析,以确保其描述的信息符合java语言规范的要求。

c)字节码验证:这个阶段的主要工作是进行数据流和控制流的分析。任务是确保被验证类的方法在运行时不会做出危害虚拟机安全的行为。

d)符号引用验证:这一阶段发生在虚拟机将符号引用转换为直接引用的时候(解析阶段),主要是对类自身以外的信息进行匹配性的校验。目的是确保解析动作能够正常执行。

 

4.3.准备

 

准备阶段是正式为变量分配内存并设置初始值,这些内存都将在方法区中进行分配,这里的变量仅包括类标量不包括实例变量。

 

4.4.解析

 

解析是虚拟机将常量池的符号引用替换为直接引用的过程。

a)符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任意形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。

b)直接引用:直接引用可以是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。直接饮用是与内存布局相关的。

c)类或接口的解析

d)字段的解析

e)类方法解析

f)接口方法解析

 

4.5.初始化

 

是根据程序员制定的主观计划区初始化变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。

 

五.JVM三种预定义类型类加载器

 

当一个 JVM 启动的时候,Java 缺省开始使用如下三种类型类装入器:

 

启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

 

标准扩展(Extension)类加载器:扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将

< Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

 

系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。

 

除了以上列举的三种类加载器,还有一种比较特殊的类型就是线程上下文类加载器,这个将在后面单独介绍。

 

a. Bootstrap ClassLoader/启动类加载器

主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作.

 

b. Extension ClassLoader/扩展类加载器

主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作

 

c. System ClassLoader/系统类加载器

主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作.

 

d. User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)

在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性.

 

1.1 什么是双亲委派模型

首先,先要知道什么是类的加载器。简单说,类加载器就是根据指定全限定名称将class文件加载到JVM内存,装维Class对象。如果站在JVM的角度来说,只存在两种加载器:

  • 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在<JAVA_HOME>\lib目录或-Xbootclasspath参数指定路径中的类库加载到内存中。
  • 其他类型加载器:由Java语言实现,继承自抽象类ClassLoader。如:
    • 扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录或java.ext.dirs系统便令指定的路径中的所有类库。
    • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

双亲委派模型工作过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载中这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在及其的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

 

1.2 为什么需要双亲委派模型?

为什么需要双亲委派模型呢?假设没有双亲委派模型,试想一个场景:

黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这个函数中,黑客加入一些“病毒代码”。并通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。

而有了双亲委派模型,黑客自定义的java.lang.String永远不会被加载到内存中。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

或许你会想,我在定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗?的确,这样是可行的。但是,在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与带比较类型的类加载器不同,那么会返回false。

举个栗子:

ClassLoader1、ClassLoader2都加载java.lang.String类,对应Class1,、Class2对象。那么Class1对象不属于ClassLoader2对象加载的java.lang.String类型。

2. 自定义类加载器

2.1 几个重要函数

loadClass默认实现如下:

 

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}

再看看loadClass(String name, boolean resolve) 函数:

 

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;
    }
}

从上面代码可以明显看出,loadClass(String,boolean)函数即实现了双亲委派模型,整个大致过程如下:

  1. 首先,检查下制定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.ladClass(name,false);)或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

换句话说,如果自定义类加载器,就必须重写findClass方法!

find Class

findClass的默认实现如下

 

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
}

可以看出,抽象类ClassLoader的findClass函数默认是抛出异常的。而前面我们知道,loadClass在父加载器无法加载类的时候,就会降低用我们自定义的类加载器中的findClass函数,因此我们必须在loadClass这个函数里面实现将一个指定类名称转换为class对象。

如果是读取一个指定的名称的类为字节数组的话,这个很好办。但是如何将字节数组转为class对象呢?很简单,Java提供了defineClass方法,通过这个方法,就可以把一个字节数组转为Class对象了。

defineClass

defineClass主要的功能是:

将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。如,假设class文件时加密过的,则需要解密后作为形参传入defineClass函数。

defineClass默认实现如下:

 

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError  {
        return defineClass(name, b, off, len, null);
}

2.2 函数调用过程

 

image

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值