深入【Java】底层细节知识点

版权声明: https://blog.csdn.net/Song_JiangTao/article/details/82023526

  • 首先,面向对象是一种思想,它呢是基于面向过程而言的,

    其次,理解对象概念,什么是对象?对象就是类的一个实例,什么是类?类就是对一类事物的抽象,这类事物的共同特性就是程序中的属性,共同的行为就是方法

    例如:动物这个类。有属性name,有方法eat()。动物类的对象:狗,狗呢就具有name的属性和吃的行为。

    • 封装:隐藏了对象的具体细节。提高了复用性和安全性
    • 继承:两个类之间有一些的属性和方法的重复。就使用继承,这样既简化了代码,也提高了代码的复用性
    • 多态:继承是多态的前提。体现在:父类对象的引用可以指向子类对象,这样提高了程序的扩展性

j v m

  • 大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区),Heap(堆),Program Counter Register(程序计数器) , VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack ( 本地方法栈 ),其中Method Area 和 Heap 是线程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非线程共享的。为什么分为 线程共享和非线程共享的呢?请继续往下看。

    首先我们熟悉一下一个一般性的 Java 程序的工作过程。一个 Java 源程序文件,会被编译为字节码文件(以 class 为扩展名),每个java程序都需要运行在自己的JVM上,然后告知 JVM 程序的运行入口,再被 JVM 通过字节码解释器加载运行。那么程序开始运行后,都是如何涉及到各内存区域的呢?

    概括地说来,JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。这也是为什么我把内存区域分为线程共享和非线程共享的原因,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说知发生在Heap上)的原因。

    当我们创建一个对象(new Object)时,就会调用它的构造函数来开辟空间,将对象数据存储到堆内存中,与此同时在栈内存中生成对应的引用,当我们在后续代码中调用的时候用的都是栈内存中的引用,还需注意的一点,基本数据类型是存储在栈内存中。

    h e a p s t a c k ?
    java的内存分为两类,一类是栈内存,一类是堆内存。

  • 栈内存是指程序进入一个方法时,会为这个方法单独分配一块私属存储空间,用于存储这个方法内部的局部变量,当这个方法结束时,分配给这个方法的栈会释放,这个栈中的变量也将随之释放。
  • 堆是与栈作用不同的内存,一般用于存放不放在当前方法栈中的那些数据,例如,使用new创建的对象都放在堆里,所以,它不会随方法的结束而消失。方法中的局部变量使用final修饰后,放在堆中,而不是栈中。

    更多参考:
    【深入Java虚拟机】之一:Java内存区域与内存溢出
    【转】JVM介绍

j a v a

  • 加入泛型之前:如果我们有如下需求:一个StringArr,你只想放string,但你并不能阻止其他类型数据放入。为了通用性,这样的数组一般都是Object。当我们获取里面的值的时候,就得强制转换,这就是它的缺点。
  • 加入泛型之后:例如:ArrayList<String> stringValues=new ArrayList<String>();这样,就指定了具体的类型,添加一些数据的时候,如果不符合初定的类型,就会报错,安全性提高!再有就是指定了具体的类型,提高了代码的质量,可读性提高!
  • 实现原理:
    这里写图片描述
    如上,两者的类型分明不同,输出的结果却是true,这是因为,泛型它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的。这就是为什么,Java的泛型被称为“伪泛型”的原因

    注意点:

    • 泛型变量不允许是基本数据类型,只能是他们的包装类
      这里写图片描述
    • 静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
      这里写图片描述

S t r i n g

了解一个类最好的方式就是看源码

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence
{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    public String substring(int beginIndex, int endIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    if (endIndex > count) {
        throw new StringIndexOutOfBoundsException(endIndex);
    }
    if (beginIndex > endIndex) {
        throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
    }
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

public String concat(String str) {
    int otherLen = str.length();
    if (otherLen == 0) {
        return this;
    }
    char buf[] = new char[count + otherLen];
    getChars(0, count, buf, 0);
    str.getChars(0, otherLen, buf, count);
    return new String(0, count + otherLen, buf);
}

public String replace(char oldChar, char newChar) {
    if (oldChar != newChar) {
        int len = count;
        int i = -1;
        char[] val = value; /* avoid getfield opcode */
        int off = offset;   /* avoid getfield opcode */

        while (++i < len) {
        if (val[off + i] == oldChar) {
            break;
        }
        }
        if (i < len) {
        char buf[] = new char[len];
        for (int j = 0 ; j < i ; j++) {
            buf[j] = val[off+j];
        }
        while (i < len) {
            char c = val[off + i];
            buf[i] = (c == oldChar) ? newChar : c;
            i++;
        }
        return new String(0, len, buf);
        }
    }
    return this;
}
}

更多的不再展示。

从以上的源码中获得信息:

  • String是final类,这意味着,这个类不能被继承,也不可有子类,其中的方法默认都是final方法
  • String类是通过char数组来保存字符串的
  • String类对字符串的操作都是对新字符串操作。 S t r i n g

字符串常量池:

  • 每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串不可变,所以常量池中一定不存在两个相同的字符串
  • 静态常量池和运行时常量池

    • 静态常量池,即.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
    • 运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

    这里写图片描述
    虽说,字符串的比较,我们使用equals方法,但用==号就可以看出,a和b指向的同一个对象。而new以后就产生新的对象。

    如果使用equals比较三者,得出的结果,肯定都是true
    这里写图片描述
    由于c是new出来的,所以产生了两个对象,一个是栈区中的c,另一个就是堆中的123,他们的引用关系是c->123->123(常量池中的)

    也就是说,尽管c是创建在堆中,但其value还是常量池中的123

    当我们对字符串,进行拼接,替换等操作时,会创建一个新的对象来操作,之后旧的对象,就会被当作垃圾回收。

e q u a l s ( ) ==

他们最大的区别就是一个是方法,一个是关系操作符。

Object类中的equals方法源码:

public boolean equals(Object obj)
   {
       return this == obj;
   }

可见它直接比较的对象本身。

String类中的equals方法源码(重写Object中的):

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                        return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

上面的equals比较当中,都运用到了“==”,这就说明,equals(不管哪个类中的)是==的扩展。

引用《java编程思想》中的原话:

关系操作符生成的是一个boolean结果,它们计算的是操作数的值之间的关系

总结:

  • 当==比较基本数据类型的时候,就是比较他们本身的值。
  • 而==比较引用数据类型的时候, 就是比较他们在内存的地址
  • equals用来比较引用数据类型一般都要重写该方法。例如String类就重写了这个方法,
  • 如果没有重写equals就直接比较,就是比较他们的内存地址
  • equals不能用于基本数据类型
    这里写图片描述
    上面的str中存储的并不是“a”,而是它所指向的对象的地址。所以将他赋值给str2,比较他俩时返回的就是true
    这里写图片描述
    上面的Student类并没有重写equals方法,结果位false

    还有hashCode()方法
    这里写图片描述

i n t I n t e g e r

基本区别:

  • (1)Integer是int的包装类;int是基本数据类型;
  • (2)Integer变量必须实例化后才能使用;int变量不需要;
  • (3)Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
  • (4)Integer的默认值是null;int的默认值是0。

深入比较:

package com.nuc;

public class Demo {
    public static void main(String[] args) {
        Integer i1 = new Integer(1);
        Integer i2 = new Integer(1);
        Integer i3 = 1;
        Integer i4 = 127;
        Integer i5 = 127;
        Integer i6 = 128;
        Integer i7 = 128;
        int i8 = 1;
        //1、直接比较时,比较两者的地址,每次new生成的都是新的对象
        System.out.println(i1==i2);//false
        //2、integer和int比较时会自动拆箱转换为int类型,然后比较两者的值
        System.out.println(i1==i8);//ture
        //3、非new生成i3指向的是java常量池的对象,new是新生成的
        System.out.println(i1==i3);//false
        //4、127和128问题,JavaAPI中对Integer定义:在-128到127(含)之间的数会缓存,只存在一个对象中,即在此创建只是从缓存中取
            //超过这个每次创建就会new,即产生新的对象
        System.out.println(i4==i5);//true
        System.out.println(i6==i7);//false
    }
}

这里写图片描述
以上是测试结果

n u l l

本质区别:

  • null是没有地址的
  • “”是有地址的,里边的内容是空的

具体区别:

  • 1、做成员变量(字段/属性/类变量)时,如果只写String str;那么是默认赋值为null的。null的话,你屏幕输出(toString方法)的结果为字符串null,而且其它调用str的操作,编译可通过,运行时会空指针异常,此时是异常。
  • 2、做局部变量(方法里的变量)时,如果只写String str;是不会默认赋值null的,这里仅声明了一个str变量,在栈内存中有定义,但没有任何值,null其实也是一种值。此时任何调用str的操作,编译时就均会报错,是error,不是异常。
  • 3、“”是空字符串,但也是字符串,没有什么东西。 而null是空的意思,什么都没有,没有地址
    这里写图片描述
    如果你直接输出str1,本质上其实是调用了toString方法。所以就会报错
    西 ( = )

    String str = null;String str1; 这两者也是不同的,前者是分配了内存,也就是说,你对他可以进行相关操作,而不会报错,后者是不可以的。

f i n a l f i n a l l y f i n a l i z e

f i n a l 关键字,是个修饰符。final不可以和abstract共存

  • 修饰类:这样意味着这个类不能有子类,也不能作为父类被继承
  • 修饰方法:这样意味着这个方法只能使用,不可以被重写,可以被重载
  • 修饰变量:这样意味着这个变量在使用过程中不可以被改变,声明时必须赋予初始值,后来用的时候只能读取

f i n a l l y 是异常处理时提供finally块来执行一些操作,不管有没有异常被抛出、捕获,finally块都会被执行

  • try,catch,finally,try中的内容如果没有异常时会执行到底,然后去执行finally代码块(如果有的话);catch代码块中的内容是:try中的内容遇到异常时来执行,try中遇到异常那一行代码之后的代码都不会执行,然后继续执行finally代码块(如果有的话);finally中的代码是始终都会执行的

  • t r y c a t c h t r y

    也就是说:try不可以单独存在,必须和catch和finally的其中一个连用

f i n a l i z e 是个方法名

  • java允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前,对这个对象调用的

  • 异常
    • 异常即exception
    • 异常:可以预知、可以通过修改程序来弥补”错误”,可避免的
  • 错误

    • 错误:error
    • 错误:无法预知的,系统级别的,程序员修改程序是无法修复的;例如:系统宕机,JVM挂掉了
      这里写图片描述
      常见的异常总结:

    • 1、java.lang.NullPointerException(空指针异常)
        调用了未经初始化的对象或者是不存在的对象

    • 2、java.lang.ClassNotFoundException
        指定的类不存在
    • 3、java.lang.NumberFormatException
      字符串转换为数字异常  
    • 4、java.lang.IndexOutOfBoundsException
      数组下标越界异常
    • 5、 java.lang.ArithmeticException
      数学运算异常
    • 6、 java.lang.ClassCastException
      数据类型转换异常
    • 7、java.lang.FileNotFoundException
      文件未找到异常
    • 8、java.lang.IllegalArgumentException
      方法的参数错误
    • 9、 java.lang.NoSuchMethodException
      方法不存在异常
    • 10、java.lang.NoClassDefFoundException
      未找到类定义错误

    违背安全原则异常:SecturityException

    操作数据库异常:SQLException

    输入输出异常:IOException

    通信异常:SocketException

  • 异常的处理

    • 功能抛出几个异常,功能调用如果进行try处理,需要与之对应的catch处理代码块,这样的处理有针对性,抛几个就处理几个。
      特殊情况:try对应多个catch时,如果有父类的catch语句块,一定要放在下面。
  • throws 和throw

    • throws 方法声明时使用,表示该方法可能产生的异常,抛出,谁调用谁捕获处理,throws用在函数上。
    • throw方法体中使用,表示抛出一个具体的异常对象,谁调用谁捕获处理,throw用在函数内。
    • catch中存在return,此时finally中是否被执行?执行。但finally外边不执行。(其实就不能有语句)

J a v a 23

篇幅过长,具体请看大神总结的:
Java之美[从菜鸟到高手演变]之设计模式

Java之美[从菜鸟到高手演变]之设计模式二
Java之美[从菜鸟到高手演变]之设计模式三
Java之美[从菜鸟到高手演变]之设计模式四
转自:http://blog.csdn.net/zhangerqing

j a v a 线

  • 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
  • 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
  • 区别并发和并行:并发是指:多个事件在同一时间间隔内发生,并行是指:多个事件在同一时刻发生

    更多概念参考操作系统基础知识总结整理的第四大点:处理机管理

    更多多线程的知识请参考Java中的多线程你只要看这一篇就够了

G C

  • 什么是GC?
    • GC是垃圾收集的意思(Gabage Collection)
  • 为什么要有GC?
    • 内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法
  • 垃圾回收器的基本原理是什么?
    • 对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是”可达的”,哪些对象是”不可达的”。当GC确定一些对象为”不可达”时,GC就有责任回收这些内存空间。
  • 有什么办法主动通知虚拟机进行垃圾回收?

    • 程序员可以手动执行System.gc()或者Runtime.getRuntime().gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。 我们只是“提醒”一下

    关于Java堆内存结构,分代回收算法,垃圾收集器,GC日志,JVM参数使用,请参考JavaGC介绍

猜你喜欢

转载自blog.csdn.net/Song_JiangTao/article/details/82023526