java基础题

本文深入讲解Java内存分配机制,探讨JVM、JRE与JDK的关系,解析串行与并行垃圾回收器的工作原理,对比String、StringBuffer与StringBuilder的区别,并阐述final与static关键字的使用技巧。

一、java内存分配

java程序执行流程:java源代码文件(.java)->通过编译器(javac.exe)->java(JVM文件)字节码文件(.class)->通过java.exe执行后输出结果
JRE,JVM,JDK三者之间的关系
JDK(java development kit)是java语言的软件开发工具包,在JDK的安装目录下有一个jre目录,在下载jdk的同时会安装jre,jre是运行java程序所必须的环境的集合,包含JVM标准实现及java核心类库,JVM是java实现跨平台最核心的部分,能够运行以java语言写的软件程序。
在这里插入图片描述
JVM体系结构
JVM的中文名称叫java虚拟机,是jre的一部分,是由软件技术模拟出计算机运行的一个虚拟的计算机,java的程序需要经过编译之后才能被JVM识别并运行。
jvm体系包含以下三项:

  1. 类装载器ClassLoader:用来装载.class文件
  2. 执行引擎:执行字节码,或执行本地方法
  3. 运行时数据区:方法区(线程共享的)、堆(通过new出来的对象都存在堆中)、java栈(线程创建时创建,栈内存负责程序的运行,其生命周期和线程生命周期相同,同时消亡,线程结束时栈同时释放,栈不存在垃圾回收的问题,平时所写的类变量,引用类型变量,实例方法等都是在栈内存中分配)、程序计数器(线程私有的,相当于一个指针)、本地方法栈(登记native方法,然后在execution engine执行的时候加载本地方法库)

java堆、栈、方法区详解
堆: 1.主要存放java在运行过程中new出来的对象,凡是通过new生成出来的对象都放在堆中,对于堆中的对象生命周期的管理由java虚拟机的垃圾回收机制GC进行回收和统一管理,类的非静态成员变量也放在堆区,其中基本数据类型是直接保存值,而复杂类型是保存指向对象的引用,非静态成员变量在类的实例化时开辟空间并且初始化。类的时机:加载–连接–初始化–实例化。
2.java虚拟机不需要知道从堆内存里边存放多少空间大小的变量信息,也不需要知道每个对象生命周期,所以一般程序运行灵活度很高。
3.堆区中存放的大量的对象信息是GC重点回收区域模块。

栈:主要存放在运行期间用到的一些局部变量(基本数据类型的变量)或者是指向其他对象的一些引用,因为方法执行时,被分配的内存就在栈中,所以当然存储的局部变量就在栈中了,当一段代码或者一个方法调用完成后,栈中为这段代码提供的基本数据类型或者对象的引用立即被释放。

方法区:是各个线程共享的内存区域,它用于存储class二进制文件,包含了虚拟机加载信息、常量、静态变量、及时编译后的代码等数据。方法区是线程安全的

局部变量和成员变量

局部变量成员变量
定义在方法中定义在类中
只在当前方法中使用整个类中都可以使用
没有默认值,使用之前必须手动赋值无需手动赋值,有默认值,规则同数组
位于栈内存位于堆内存
随着方法进栈而诞生,随着方法出栈而消失随着对象的创建而诞生,随着对象消失而消失

垃圾回收机制

java语言最显著的一个特点就是引入了垃圾回收机制,使得程序员在开发时无需考虑内存的问题;
在开发的时候,我们有时候会创建大量的对象,而没有被引用的对象就是无用的,此对象被称为‘垃圾’,其占用的内存需要被销毁,不然会导致内存被占满,造成溢出,在此提出两个概念,内存溢出和内存泄漏
内存溢出out of memory:指的是程序申请内存时,没有足够的内存供申请者使用,常见的原因有以下几种:

  • 内存中加载的数据量过于庞大,如一次从数据库取出过多的数据;
  • 集合类中有对对象的引用,使用完后未清理,使得JVM不能回收;
  • 代码中存在死循环或者循环过多产生过多重复的对象实体;
  • 使用第三方软件中的BUG;
  • 启动参数内存值设定的过小。

内存泄漏memory leak:指的是程序在申请内存之后,无法释放已经申请的内存,一次的内存泄漏不会造成很大的影响,但是多次之后内存泄漏堆积的后果就是内存溢出。内存泄漏可分为以下四类:

  • 常发性内存泄漏:发生内存泄漏的代码会被多次执行到,每次执行都会导致内存泄漏;
  • 偶发性内存泄漏:发生内存泄漏的代码只有在某些特定的环境或者操作过程才会发生;
  • 一次性内存泄漏:发生内存泄漏的代码只会被执行一次,或者是由于算法上缺陷,导致总会有一部分且仅有一块内存发生泄漏;
  • 隐式内存泄漏:程序在运行的过程中不停的分配内存,但是知道结束才会释放内存。

垃圾的定义

  • 引用计数算法
    引用计数算法是通过在对象头中分配一个空间来保存该对象被引用的次数,如果该对象被其他对象引用,则它的引用计数加一,如果删除该对象的引用,其计数减一,当该引用对象的计数为0时,该对象就会被回收。

  • 可达性分析算法
    可达性分析算法的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象时不可用的。
    在这里插入图片描述

垃圾回收 GC(垃圾收集器)

  • Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效;
  • Serial Old收集器(标记整理算法):老年代单线程收集器,Serial收集器的老年代版本
  • ParNew收集器(停止-复制算法) :新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。
  • Parallel Scavenge收集器(停止-复制算法):并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。
  • Parallel Old收集器(停止-复制算法): Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先
  • CMS(Concurrent Mark Sweep)收集器(标记-清理算法):高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择
    GC的执行机制
    由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。
    Scavenge GC: 一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。
    Full GC: 对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:
      1.年老代(Tenured)被写满
      2.持久代(Perm)被写满
      3.System.gc()被显示调用
      4.上一次GC之后Heap的各域分配策略动态变化

常量池 :java常量池总体来说分为两种:静态常量池和运行时常量池。
静态常量池: 存在于class文件中,class文件中不仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间,这种常量池主要用于存放两大类常量:字面量和符号引用量,字面量相当于JAVA语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包含了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段名称和描述符
  • 方法名称和描述符

运行时常量池: 则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等描述信息外还有一项信息是常量池,它用于存放编译期生成的字面量和符号应用,这部分内容将在类加载后进入方法区的时候存到运行时常量池中。运行时常量池还有个更重要的的特征:动态性。Java要求,编译期的常量池的内容可以进入运行时常量池,运行时产生的常量也可以放入池中。常用的是String类的intern()方法【当调用 intern() 方法时,编译器会将字符串添加到常量池中(stringTable维护),并返回指向该常量的引用。 】。

二、String

String可以被继承吗?
不可以,因为String底层是被final所修饰的,而被final修饰的类,方法和变量是不能被继承的;String类代表字符串,字符串是常量,他们的值在创建之后不可被改变。可以理解为String类是一个final类,并且它的成员方法都默认为final方法,String类是通过char[ ]数组来保存字符的。
String源码
String方法

  • 返回指定索引处的值
 - charAt(int index)
  • 返回指定索引处的字符(Unicode代码点)
 - codePointAt(int index)
  • 返回指定索引之前的字符(Unicode代码点)
 - codePointBefore(int index)
  • 按照字典顺序比较两个字符串
 -compareTo(String anotherString)
  • 按照字典顺序比较两个字符串的大小,不考虑大小写
 - compareToIgnoreCase(String str)
  • 将指定字符串连接到此字符串的结尾
 - concat(String str)
  • 返回指定字符在此字符串中第一次出现处的索引
 - indexOf(int ch)
  • 返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索
 - indexOf(int ch, int fromIndex)
  • 返回指定子字符串在此字符串中第一次出现处的索引
 - indexOf(String str)
  • 当且仅当 length() 为 0 时返回 true
 - isEmpty()
  • 返回指定字符在此字符串中最后一次出现处的索引
 - lastIndexOf(int ch)
  • 返回此字符串的长度
 - length()
  • 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索
-lastIndexOf(String str, int fromIndex)
  • 使用默认语言环境的规则将此 String 中的所有字符都转换为小写
 - toLowerCase()
  • 使用给定 Locale 的规则将此 String 中的所有字符都转换为小写
 - toLowerCase(Locale locale)
  • 使用默认语言环境的规则将此 String 中的所有字符都转换为大写
 - toUpperCase()
  • 使用给定 Locale 的规则将此 String 中的所有字符都转换为大写
 -toUpperCase(Locale locale)

StringBuffer和StringBuilder

当需要对字符串进行修改操作时,需要使用到StringBuffer和StringBuilder
StringBuffer:是一个容器,是字符串缓冲区
扩容机制

  • 其初始容量可以容纳16个字符,当该对象的实体容量超过16个字符时,会自动进行扩容, 可通过length()方法获取实体中存放的字符序列长度,通过capacity()方法获取当前实体的实际容量
  • StringBuffer(int size)可以指定分配给该对象的实体的初始容量参数为参数size指定的字符个数。当该对象的实体存放的字符序列的长度大于size个字符时,实体的容量就自动的增加。以便存放所增加的字符。
  • StringBuffer(String s)可以指定给对象的实体的初始容量为参数字符串s的长度额外再加16个字符。当该对象的实体存放的字符序列长度大于size个字符时,实体的容量自动的增加,以便存放所增加的字符。

线程安全:StringBuffer是线程安全的,其底层被synchronized修饰
StringBuffer的源码注释

  public synchronized int length() {
        return count;
    }
    public synchronized int capacity() {
        return value.length;
    }
    public synchronized void ensureCapacity(int minimumCapacity) {
        if (minimumCapacity > value.length) {
            expandCapacity(minimumCapacity);
        }
    }

StringBuilder:与StringBuffer基本类似,其方法和构造器也基本相同
StringBuilder源码注释
由此可见StringBuilder是非线程安全的,其没有被synchronization所修饰;因为是非线程安全,所以其效率要高于StringBuffer。

装箱和拆箱

java为每种数据类型都提供了对应的包装器
装箱:自动将基本数据类型转化为相应的包装器类型

//自动装箱
Integer total = 99;

拆箱:自动将包装类型的数据自动转换为基本数据类型

//自动拆箱
int n = i;  

基本数据类型与相对应的包装器类型对照

基本数据类型包装器类型默认值
int(4字节)Integer0
byte(1字节)Byte0
short(2字节)Short0
long(8字节)Long0L
float(4字节)Float0.0f
double(8字节)Double0.0d
char(2字节)Character‘/uoooo’(null)
booleanBooleanfalse

为什么要装箱和拆箱: java早年设计的一个缺陷,基本数据类型不是对象,自然不是Object的子类,需要装箱才能把数据类型变成一个类,那就可以把装箱过后的基本数据类型当做一个对象,就可以调用object子类的接口。而且基本数据类型是不可以作为形参使用的,装箱后就可以。而且在jdk1.5之后就实现了自动装箱拆箱,包装数据类型具有许多基本数据类型不具有的功能,只是装箱拆箱过程会稍稍微的影响一下效率。

三、关键字final和static

final关键字

final关键字可以用来修饰类、方法、变量。
**修饰类:**使用final修饰的类叫做final类,final类通常功能是完整的,它们不能别继承,final类中的成员变量可以根据需要设置为final,但是需要注意的是final类中的所有成员方法都会被隐式的指定为final方法。java中有许多类是final的,例如String、Interger。
修饰变量: 用final修饰的变量称作fina变量,当用final修饰一个变量时,表示这个变量的值不可变,final关键字经常和static关键字一起使用,作为常量。final修饰基本数据类型时,必须赋予初始值,且不能被改变,修饰引用变量时,该引用变量不能再指向其他对象,但是它指向对象的内容是可变的。
修饰方法: final修饰方法时,表示这个方法不可以被子类重写。

static关键字

static关键字可以用来修饰内部类、属性、方法、代码块,在类中,用static声明的成员变量为静态成员变量,也称为类变量,类变量的生命周期和类相同,在真个应用执行期间都有效。
static关键字的作用:(1)为某特定数据类型或者对象分配单一的存储空间,而与创建对象的个数无关;(2)实现某个方法与类而不是对象关联在一起。

  • static修饰的成员变量和方法,从属于类
  • 普通变量和方法从属于对象
  • 静态方法不能调用非静态成员,编译会会报错

static方法就是没有this的方法,在static方法内部不能调用非静态方法,反之则是可以的,而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法, 这实际上正是static方法的主要用途。简单来说,就是方便在没有创建对象的情况下来进行调用。
static成员变量: 如果一个成员变量使用了static关键字,那么这个成员变量不再属于这个类的某个对象,而是属于所在的类,被类中的所有对象共享。被static修饰的成员变量成为静态成员变量,静态变量存放在java内存区域的方法区,方法区与java堆一样,是所有线程共享的内存区域,它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。


1 public class TestFunction {
2     static String sing= "唱歌";
3     String sleep= "睡觉";
4              public static void updateStaticValue(){
5   				 System.out.println(sing);
6        			 System.out.println(sleep); (不能直接调用,对象才可以调用非static变量)
7         		 	 TestFunction  testFunction=new TestFunction();//调用static变量必须先进行实例化
8                    System.out.println(testFunction.sleep);
9               }
10 }

static方法: 用static修饰的方法称为静态方法,静态方法可以被继承但是不可以被重写,静态方法可以不实例化而直接访问,非静态方法可以访问静态方法和非静态方法,静态方法只能访问静态数据方法;非静态方法既可以访问静态数据成员,也可以访问非静态数据成员。因为静态方法和静态数据成员会随着类的定义而被分配和装载入内存中,而非静态方法和非静态数据成员只有在类对象创建时在对象的内存中才会有这个方法的代码段。引用静态方法时,可以用类名.方法名或者对象.方法名的形式。

public class TestStatic {
    public static void main(String[]args){
        System.out.println(S.getStatic());//使用类名加前缀访问静态方法
        S s=new S();
        System.out.println(s.getStatic());//使用实例化对象名访问静态方法
        System.out.println(s.get());
    }
    public static class S
    {
        private static int a;
        private int t=0;

        //静态初始器:由static和{ }组成,只在类装载的时候(第一次使用类的时候)执行一次,往往用来初始化静态变量。
        static{
            a=10;
        }

        //静态方法只能访问静态数据成员
        public static int getStatic()
        {
            return a;
        }

        public int getT()
        {
            return t;
        }

        //非静态方法可以访问静态方法和非静态方法
        public int get()
        {
            getT();
            getStatic();
            t=a;//非静态方法可以访问非静态数据成员和静态数据成员
            return t;
        }
    }
}  

static代码块 使用static{ }包起来的代码块,在类加载的时候执行,且只执行一次。同时静态初始化块只能给静态变量赋值,不能初始化非静态变量。
执行顺序:父类静态>子类静态>父类非静态>父类构造方法>子类非静态>子类构造方法

四、 “==”和equals的区别

‘==’ 比较的是两个变量或者实例是否指向同一个内存空间,如果比较的是基本数据类型,则直接比较其存储的值是否相等,如果比较的是引用数据类型,则比较的是所指对象的内存地址。
equals比较的是两个变量或者实例指向同一个内存空间的值是否相同。在object类型的equals方法是直接通过‘= =’比较的,和“= =”是没有区别的;

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

如果比较的是基本数据类型,则equals和==是没有区别的,但是如果比较的是引用数据类型,需要对equals方法进行重写

    public String code;
    public String category;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof StaffManagementForm)) return false;
        StaffManagementForm that = (StaffManagementForm) o;
        return code.equals(that.code) &&
                category.equals(that.category) &&
                Objects.equals(describe, that.describe) &&
                Objects.equals(state, that.state) &&
                Objects.equals(value1, that.value1) &&
                Objects.equals(value2, that.value2) &&
                Objects.equals(value3, that.value3) &&
                Objects.equals(editFlg, that.editFlg) &&
                Objects.equals(categoryList, that.categoryList) &&
                Objects.equals(stateList, that.stateList) &&
                Objects.equals(categoryNameList, that.categoryNameList);
    } 

五、面向对象思想

java是面向对象的编程语言,对象就是面向对象设计程序的核心。
类与对象
类是一个抽象的概念,而对象是真实存在的实体,例如学生是一个抽象的类,而具体的某个学生为一个实际存在的对象,对象有自己的属性和行为,例如学生A的属性有年龄、性别、身高、爱好等等,对象是一个具体的个体,对象也称为实例。在java中,一切皆对象。
面向对象的三大特征:继承、封装、多态
1、继承:子类继承父类的属性和行为,使得子类具有和父类相同的属性和行为(除去父类中用private修饰属性行为),子类也可以对父类的方法进行重写,可以对父类的方法进行扩展,子类可以拥有自己的属性和方法,一个子类只能继承一个父类,子类继承父类关键字:extend,子类实现接口的关键字:implement,一个类可以同时实现多个接口。接口的关键字:interface
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
2、封装:封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。
3、多态:多态是同一个行为具有多个不同表现形式或形态的能力。多态就是同一个接口,使用不同的实例而执行不同操作。

重写与重载的区别:

重写重载
子类继承父类的子类中同一个类中
方法名相同,参数列表相同,方法返回值相同方法名相同,参数列表不同,方法返回值任意
访问修饰符要大于等于父类方法的修饰符访问修饰符任意

访问修饰符

访问修饰符本类同包子类其他
privateO
默认OO
protectedOOO
publicOOOO
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值