从hello开始学java

1.从Hello World开始

最近在复习java知识,但是效果不是很理想,单纯的去记忆一些东西感觉没有那种特别的感觉,所以想通过写博客记录的方式帮助自己加深印象。今天我们就从一个Demo开始开启我们的java之旅,请各位朋友多多指教。

1.1使用IDE写我们的第一个程序

代码如下所示:

/**
 * @version 1.0.0
 * @description:
 * @author: author
 * @time: 2020/4/19 9:16
 */
public class HelloWorld {
    private int AA = 5;
    private static Integer MIN_VALUE = 0;
    private static Integer MAX_VALUE = 9;
    private static final Integer BB = 9;
    public static void main(String[] args) {
        HelloWorld helloWorld = new HelloWorld();
        System.out.println("HelloWorld.main");
        helloWorld.add(MIN_VALUE, MAX_VALUE);
    }
    public void add(Integer min, Integer max){
        System.out.println(min + max);
    }
}

运行结果:

HelloWorld.main
9
Process finished with exit code 0

现在我们一起来看看程序是如何将页面上的代码变成结果的

1.1.1运行过程

既然是一种语言那我们当然需要了解对应的开发运行环境

Java和JDK的关系
JDK(Java Development Kit) Java开发工具包,它包括:编译器,Java运行环境,监控和诊断工具等,而Java则表示一种开发语言.
JRE(Java Runtime Environment) Java运行环境,
JVM(Java Virtual Machine) Java虚拟机 JVM是可运行Java代码的假想计算机,包括一套字节码指令集、一组寄存器、一个栈、 一个垃圾回收,堆 和 一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接的交互。

运行过程:

Java源文件
编译器
字节码文件
JVM
机器码
  1. 先把Java代码编译成字节码,也就是把 .java类型的文件编译成 .class类型的文件.这个过程的大致执行流程: Java源代码 -> 词法分析器 -> 语法分析器 -> 语义分析器 -> 字节码生成器 ->最终生成JVM字节码文件*.class,其中任何一个节点执行失败就会造成编译失败;
  2. 把class文件放置到Java虚拟机,这个虚拟机通常指的是Oracle官方自带的Hotspot JVM;
  3. Java虚拟机使用类加载器(Class Loader)装载class文件;
  4. 类加载完成之后,会进行字节码校验,字节码校验通过JVM解释器会把字节码翻译成机器码交由操作系统执行.但不是所有代码都是解释执行的,JVM对此做了优化, 比如, 以Hotspot虚拟机来说, 它本身提供了JIT (Just In Time)也就是我们通常所说的动态编译器,它能够在运行时将热点代码编译成机器码,这个时候字节码就变成了编译执行.
1.1.1.1类加载机制

什么是类的加载?
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误
在这里插入图片描述

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这 五个过程。

1.1.1.1.1加载

加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既 可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理), 也可以由其它文件生成(比如将JSP文件转换成对应的Class类)

代码:

HelloWorld helloWorld = new HelloWorld();

当new的时候,会用到HelloWorld.class,所以第一时间找到HelloWorld.class文件,并加载到内存中
执行该类的static代码块,给HelloWorld.class进行一个初始化
heap(堆)中开辟空间,分配地址
heap(堆)中建立对象的特有属性,并进行默认初始化
对属性进行显示初始化
对对象进行构造代码块初始化
对对象进行的与之对应的构造函数进行初始化
把内存地址赋给stack(栈)中的helloWorld变量

1.1.1.1.2连接
1.1.1.1.2.1验证

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

1.1.1.1.2.2准备

为类的静态变量分配内存,初始化为系统的初始值(不初始化静态代码块),对static final,直接定义为用户赋的值

    public static int AA = 8080;
    private static final int BB = 9090;

如上代码,在准备阶段过后AA的初始值是0而不是8080,将AA赋值为8080的putstatic指令是程序被编译后,存放于类构造器方法之中。而在编译阶段会为BB生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将BB赋值为9090。

1.1.1.1.2.3解析

该阶段将常量池中的符号引用转化为直接引用。

符号引用:就是class文件中的CONSTANT_Class_info、CONSTANT_Field_info、CONSTANT_Method_info等类型的常量。符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。

在编译时,Java类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如我们有一个com.test类引用了阿里巴巴的com.aliyun类,编译时Test类并不知道阿里巴巴类的实际内存地址,因此只能使用符号com.aliyun。

直接引用:通过对符号引用进行解析,找到引用的实际内存地址,直接引用可以是目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

1.1.1.1.3初始化

初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其他操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。该阶段可实现应用程序自身类加载顺序。
初始化阶段是执行类构造器方法的过程,即真正执行类中定义的Java程序代码。

比如:

String str = new String("need a Girlfriend");

使用new实例化一个String对象,此时就会调用String类的构造方法对str进行实例化。

说完类加载过程,就不得不说类加载器,什么是类加载器?

1.1.1.1.3.1类加载器

还记得使用JdbcTemplate连接数据库过程吗?我们把数据库连接封装为工具类DruidUtils方法,该方法就用到了类加载器:

import com.alibaba.druid.pool.DruidDataSourceFactory;
 
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
 
/**
 * 提供连接
 */
public class DruidUtils {
	private static DataSource dataSource = null;
	static { // 必须优先执行 只执行一次
		try {
			// 需要一个文件流
			InputStream is = DruidUtils.class.getClassLoader().getResourceAsStream("db.properties");
			// 创建配置文件对象
			Properties props = new Properties();
			props.load(is);
			// 核心类
			dataSource = DruidDataSourceFactory.createDataSource(props);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * 返回数据源方法
	 * 
	 * @return
	 */
	public static DataSource getDataSource() {
		return dataSource;
	}
	/**
	 * 提供连接的方法
	 * 
	 * @return
	 */
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}
}

其中,class.getClassLoader()就是类加载器的标志了。Jvm设计之初就把加载动作放到外部实现,以便让应用程序决定如何获取所需的类,所以提供了3种类加载器:

1)启动类加载器(Bootstrap ClassLoader):加载jre/lib包下面的jar文件,比如说常见的rt.jar。 (java.time.、java.util.、java.nio.、java.lang.、java.text.、java.sql.、java.math.*等等都在rt.jar包下)
2)扩展类加载器(Extension or ExtClassLoader):加载jre/lib/ext包下面的jar文件。由Java语言实现,父类加载器为null。
3)应用类加载器(Application or AppClassLoader):根据程序的类路径(classpath)来加载Java类。由Java语言实现,父类加载器为ExtClassLoader。JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader 实现自定义的类加载器

待补充
1.1.1.1.3.2双亲委派

当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父 类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中, 只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class),子类加载器才会尝试自己去加载。
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载 器加载这个类,终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载 器终得到的都是同样一个Object对象

在这里插入图片描述

protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
    //check the class has been loaded or not
    Class c = findLoadedClass(name);
    if(c == null){
        try{
            if(parent != null){
                c = parent.loadClass(name,false);
            }else{
                c = findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){
            //if throws the exception ,the father can not complete the load
        }
        if(c == null){
            c = findClass(name);
        }
    }
    if(resolve){
        resolveClass(c);
    }
    return c;
}
1.1.1.2虚拟机

JVM: 堆、方法区、虚拟机栈、本地方法栈、程序计数器

堆(类实例区)
用两张图来简单描述虚拟机

public class Parent {
    int P_MIN = 0;
    static int P_MAX = 9;
    static final int P_MID = 99;
    {
        System.out.println("父类的初始化代码块");
    }
    static {
        System.out.println("父类的静态方法");
    }
    Parent(){
        System.out.println("父类的构造方法");
    }
    void parentMethod(){
        System.out.println("父类的普通方法");
        System.out.println("P_MIN\t" + P_MIN);
        System.out.println("P_MID\t" + P_MID);
        System.out.println("P_MAX\t" + P_MAX);
    }
}
public class Child extends Parent {
    int C_MIN = 0;
    static int C_MAX = 9;
    static final int C_MID = 99;
    static int value = value();
    {
        System.out.println("子类的初始化代码快");
    }

    static {
        System.out.println("子类的静态方法");
    }

    Child(){
        System.out.println("子类的构造方法");
    }

    @Override
    void parentMethod() {
        super.parentMethod();
    }

    void childMethod(){
        System.out.println("子类的普通方法");
        System.out.println("C_MIN\t" + C_MIN);
        System.out.println("C_MID\t" + C_MID);
        System.out.println("C_MAX\t" + C_MAX);
    }
    public static void main(String[] args) {
        Child child = new Child();
        child.childMethod();
    }
    static int value(){
        System.out.println("静态代码块");
        return 3;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dave_Fong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值