java基础的个人理解总结【一】

一、java?面向过程、面向对象的定义区别?面向对象特征及理解。

java:           

          一种跨平台性的面向对象语言,它的跨平台性表现在JVM实现了一次编译、到处运行。

一次编译:   

          将java代码通过编译器转换成字节码文件(.class文件)

到处运行:   

           相同的字节码在不同的操作系统内部的JVM解析出来的结果是一致的。

面向过程: 

           核心是过程两个字,过程即解决问题的步骤,从上往下步步求精,最重要的是模块化的思想。

面向对象

           面向对象离不开面向过程。例如造车,面向对象则是研究车这个对象是由什么组成的,需要的哪些小零件对象,强调的是对象组成,而每一个小零件的制作过程对应的就是一个个过程要经过一道道工序才能做出来,强调的是面向过程。

       面向对象的特征

    封装

    把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 

    {高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;低耦合是仅暴露少量的方法给外部使用,尽量方便外部调用。}

    实现-通过访问修饰符来控制访问的权限,java有4种访问修饰符,public、private、protected、default

修饰符 同一个类 同一个包 子类 所有类
public
protected ×
default × ×
private × × ×

 

                                              1.public表示可以被该项目的所有包中的所有类访问

                                              2.protected表示可以被同一个包的类以及其他包中的子类访问

                                              3.default表示没有修饰符修饰,只有同一个包的类能访问

                                              4.private 表示私有,只有自己类能访问

class A{
   public String name;   //所有类、包均可访问该属性,通过类名调用
   private int grade;    //必须创建该类,通过类自己调用,其他类不可访问,也不可调用
   protected double height;  //所有子类,共同的包下均可调用,其他不可访问
   default String place;       //默认情况下调用
  
}

   继承

    可以让某个类型的对象获得另一个类型的对象的属性的方法。

    它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。

 子类继承父类,可以得到父类的全部属性和方法 (除了父类的构造方法),但不见得可以直接访问(比如,父类私有的属性和方法)。

     继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用父类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;

讲到继承,不得不提一下抽象类与接口,面试中常被问到,抽象类与接口的区别,这里就提前总结一下吧  区别

参数 抽象类 接口
默认的方法实现 有默认方法的实现,抽象方法可有可无 java8之后接口有了默认方法的实现,java8之前没有
实现 extend,可继承实体类(必须有明确的构造方法),只能单继承 implement,多重继承(一个类可实现多个接口)
构造器 可有构造方法 不可有构造方法
与正常java类的区别 除了不能实例化,其他差不多一样 完全不同的类型
访问修饰符 public、protected、default 默认public,不可以用其他的
main方法 可有,可运行 不可有
多继承 继承一个类实现多个接口 可以继承多个接口
速度 比接口快 寻找在类中实现的方法
添加新方法 提供默认的实现 必须改变实现接口的类

 

//  抽象类与实体类
package packa;

import packb.C;
import packb.D;

public class A implements C,D{

	private String aa;

	public A() { //空构造
		
	}
	
	public A(String aa) {
		this.aa = aa;
	}


	public void aaa() {
		System.out.println("实体类的方法。。。。");
	}


	@Override
	public void dd() {
		System.out.println("实体类实现D接口的方法");
		
	}


	@Override
	public void cc() {
		System.out.println("实体类实现C接口的方法");
		
	}
}



package packa;

import packb.C;
import packb.D;

public abstract class B extends A implements C,D{

	private String bb;     //抽象类的成员变量
	
	public B(String aa) {   //构造方法
		super(aa);
	}
	
	public abstract void bbb();    //抽象方法
	
	
	public void bbbb() {      //默认方法实现
		System.out.println("抽象类B的非抽象方法");
	}
	
	public void cc() {
		System.out.println("实现的C接口");
	}
	
	public void dd() {
		System.out.println("实现的D接口");
	}
	
}
package packb;

public interface C {
 public static final double PI = 3.14;
 
 public void cc();
}

package packb;

public interface D extends C{

	public static final double PI = 3.22;
	
//	@Override
//	public void cc();
	
	
	public void dd();
}

package packb;

public class E implements C,D{		//多重实现接口

	@Override
	public void dd() {
		
		System.out.println("实现的C接口");
	}

	@Override
	public void cc() {
		System.out.println("实现的D接口");
	}
 
}

   多态

     一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

    实现多态的方式:继承和接口

1. 多态是方法的多态,不是属性(变量)的多态(多态与属性无关)。

​ 2. 多态的存在要有3个必要条件:继承,方法重写,父类引用指向子类对象。

​ 3. 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。

总结:所谓封装,就是通过定义类并且给类的属性和方法加上访问控制来抽象事物的本质特性。所谓继承,就是代码重用,而多态,就是一个行为(方法)在不同对象上的多种表现形式。

package packs;

import packa.A;
import packa.B;
import packb.C;

public class theme {

	public static void main(String[] args) {
		C c = new A();       // 多态的提现,父类引用指向子类对象
		c.cc();
	}

}

 说完面向对象的三大特征,有必要提一下最常说的重载与重写

重载:

          发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。

重写:

           发生在子类中,是子类对父类的允许访问的方法的实现过程进行重新编写,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,私有方法(private修饰)不可被重写。一句话总结,方法提供的行为改变,方法的外貌没有改变

package packa;


public class A{                            //重载

	private String aa;
	private int aaa;
	private double aaaa;
	private char a;
    
	public void aq(String as) {
		
	}
	
	public int aq(int ass,double asp) {
	return aaa;
	}
	
	public String aq(int psa,double spa,char ac) {
		return aa;
	}
	
	public double aq(String as,double asp,int psa,char spp) {
		
		return aaaa;
	}
}
package packa;


public class A{                //重写
 
	public void aq() {
		System.out.println("父类方法");
	}
	

}



package packa;

public class Aa extends A {


	@Override
	public void aq() {

		System.out.println("子类方法");
	}
	
}

二、有关JDK、JRE、JVM

我们刚开始看到这三个东西,跟入门小白一样,恐怕只知道JDK包含JRE,JRE也包含JVM, 之前就曾有面试官问过这样的问题,问之前一脸懵逼,问之后无所不ji。。。开玩笑而已,入行之初,接触的就是这个东西。

看到这张JDK体系结构图,估计很多人是懵逼的,一开始看这种图,都是晕 的,多看几遍,说不定可以看出点什么道道。

JDK :Java SE Development Kit,java开发工具包,它提供了编译、运行Java程序所需的各种工具和资源,包括Java编译器、Java运行时环境,以及常用的Java基础的类库等。

JRE: Java Runtime Environment,顾名思义,java运行时环境,主要包含jvm 的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。

JVM:Java Virtual Machine,我们常提到的java虚拟机,它是Java程序跨平台的关键部分,当使用Java编译器编译Java程序时,生成的是与平台无关的字节码,这些字节码只面向JVM。不同平台的JVM都是不同的,但它们都提供了相同的接口。

三、JVM浅学

 

程序运行时,虚拟机类加载子系统会load程序的.class文件到虚拟机内存里面,并由执行引擎去执行程序,准确地说,jvm的组成分为3大块,类装载子系统、运行时数据区、执行引擎。我们所说的运行时数据区只是程序运行时,JVM工作的一方面。

类的生命周期:加载、连接、初始化。

加载:

        读取.class文件,从磁盘读进内存区,通过全限定名获取这个类的二进制流。

连接:

<1>验证:包含源文件格式的验证。验证class文件是不是符合JVM独特的可执行文件的格式要求。

<2>准备:各类的静态变量分配内存,并赋予虚拟机默认的一个初始值值。

<3>解析:类装载器装载一个类,和它其它引用的类。

初始化:

         为类的静态变量赋予真正的初始值,并将执行静态代码块。

如何确定一个类是唯一的? 

    通过全限定名来加载一个类,确定类的唯一。。。通过类加载器也可以。

类加载器包含启动类加载器、扩展类加载器、系统类加载器(应用类加载器)。

类加载机制

全盘负责委托机制:

当一个classloader加载一个类的时候,除非显示的使用另一个loader,该类所依赖的和引用的类也由这个Classloder载入。

双亲委派机制:

先委托父类加载器寻找目标类,找不到的情况下下载自己的路径中查找并载入目标类。【加载一个类时,首先会先检查是否已经被加载过了,如没有加载则调用父加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父类加载失败,则抛出ClassNotFoundException异常,在调用自己的findClass()方法进行加载。】

一层一层向上委托,当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载都是如此,因此所有的加载请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径里找不到这个所需要加载的类),子类加载器才会尝试自己去加载。

当一个HelloWorld.class文件被加载时.不考虑自定义类加载器,首先会在AppClassLoader中检查是否已加载过,如果有就无需再加载.如果没有,那么会拿到父加载器,然后调用父加载器的loadClass().父类中同样会先检查自己是否已经加载过,如果没有再往上.直到到达BootstrapClassLoader之前,都是没有哪个加载器自己选择加载的.如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException.

优点

1:安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String
2:避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)
这种设计有个好处是,如果有人想替换系统级别的类:String.在双亲委派机制下这些系统类已经被Bootstrap classLoader加载过了,不会再去加载,从一定程度上防止了危险代码的植入.

打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法 。

关于Tomcat的类加载机制:
Tomcat的类加载机制并不遵循父委派模型。下图是Tomcat类加载器的结构:



1.CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用。
2.CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离。
3.WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离。
4.JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能。

Tomcat违背了父委派模型
tomcat违背了父委派模型,因为双亲代理机制也就是父委派模型要求除了顶层启动类加载器之外,其余的类加载都应该由其父类加载器加载。而Tomcat不是这样实现的,Tomcat为了实现隔离性,没有遵循这个约定,每个WebappCLoader加载自己目录下的class文件,不会传递给父类加载器。

 

运行时数据区(内存结构)

程序计数器:很小的一段内存空间,记录将要运行的下一行代码的序号,确保线程正常执行。

虚拟机栈:方法对应栈帧,

一个栈帧有局部变量表(变量、引用类型)

操作数栈临时存放操作的数值放到栈顶,即变量的初值,然后将这个值存储到局部变量表的一个槽里面,直到使用才开始加载这些数据;;;加载时,仍然将此数据加载到操作数栈)、

动态链接在程序运行期间,由符号引用【对象存储的一个位置】转换为直接引用【将该对象的引用指向堆内存里该对象的实例】

返回地址确保程序正常运行,一个是程序计数器,另一个是返回地址(可以看做是一个内存地址的索引),谁调用返回给谁

线程私有的,与线程在同一时间创建。管理JAVA方法执行的内存模型。每个方法执行时都会创建一个桢栈来存储方法的的变量表、操作数栈、动态链接方法、返回值、返回地址等信息。栈的大小决定了方法调用的可达深度(递归多少层次,或嵌套调用多少层其他方法,-Xss参数可以设置虚拟机栈大小)。栈的大小可以是固定的,或者是动态扩展的。如果请求的栈深度大于最大可用深度,则抛出stackOverflowError;如果栈是可动态扩展的,但没有内存空间支持扩展,则抛出OutofMemoryError。

本地方法栈:jvm执行native方法服务

堆:分为新生代(1/3)、老年代(2/3).一个新生对象,如果比较小,放在eden区,如果对象较大,直接放在老年代【分配担保机制】。新生代区域如果内存满了,该如何?

yGC,发生在Eden区。可达性分析法,判断一个对象为垃圾之后,回收这些垃圾对象,还剩下一些对象,这些对象还在用,转移到Survivor区域。Survivor区域分为两块,一块from区,一块to区。若Eden区存活的对象都往from区存放,存满之后,发生一次ygc,from存活的会放到to区域,to区域会变成from区域,原来的from区域会被清空,自动变成to区域,这样from与to进行了一次区域交换,这类存活的对象年龄+1,当加到15的时候,会被转入老年代。

Full GC,回收所有的区域,不只是老年代。

根据不同对象的不同生命周期,来进行不同的垃圾回收

方法区:存在于本地内存(非堆内存)

储存的是静态变量+常量+类信息(构造方法)+运行时常量池,class模板文件。规范的叫法。对规范的实现的方法区,持久带/永久带(java1.8之前),元空间

堆里面的实例对象根据方法区里面的class对象创建出来的,虚拟机栈引用的对象是根据堆里面的实例对象来的。

 

GC算法和收集器:

如何判断对象可以被回收?

引用计数法:

局部变量表里面的一个引用,被另外一个引用类型指向堆里面的实例,它的引用计数器就+1,当它计数器为0时,证明可以被回收,会将它的标记状态由0改为1,证明它可以被回收。缺点是无法解决循环引用的问题。

可达性分析法:

通过一系列被称为“GC Roots”的对象作为起点,从这些节点向下搜索,节点走过的路径成为引用链,当一个对象到“GC Roots”没有任何引用链,证明它是没用的。

解决循环引用,是通过固定一些根节点,常见的例如【虚拟机栈的局部变量表、方法区的类静态属性引用的对象、方法区常量引用的对象、本地方法引用的对象】

如何判断一个常量是废弃常量?

没有任何一个对象是指向这个引用常量的,这时候它就是废弃常量,如果此时内存不够,它会被清理出常量池,如果内存足够,则 不需要清理。

如何判断一个类是无用的类?

1、该类所有实例都被回收,没任何引用。

2、加载这个类的classloader已经被回收。

3、该类对应的对象没在任何地方被引用,也没有任何通过反射区访问这个类的。

垃圾回收算法:

        标记-清除算法:两个阶段,标记和清除,首先标记出所有需要回收的对象,标记完成后统一回收被标记的对象。

                不足之处:

                效率问题:标记和清除两个过程效率都不高。

                空间问题:标记清除后产生大量不连续的碎片。

        复制算法:把内存一分为二,称之为A区域,B区域,其中A清空,B存储,B标记之后清除,存活的转移到A区域,并整理,A剩下的区域就是连续的。

                       最好应用在Survivor区,刚好一分为二,from,to

        标记-整理算法:先标记出需要回收的对象,然后清除掉,标记完成后幸存的对象经过整理,整理后的空间又开始连续,又可以存储大的对象。

       分代收集算法:不同的区域使用不同的垃圾收集器,在一个垃圾收集器又可用不同的垃圾回收算法。

垃圾收集器:

Serial(串行)收集器:新生代采用复制算法,老年代采用标记-整理算法。

应用程序线程,进行一次GC,之后将应用线程停掉,之前都是单线程去执行一个程序,没太多的线程资源,此时GC垃圾回收线程就专门执行GC,应用程序线程不能跑停掉了,会产生STW(Stop the world,无法解决的问题,停顿时间,即垃圾收集器做垃圾回收中断应用执行的时间,JVM调优指标之一),【假如你写了一个web端,此时JVM执行GC,web端卡个一两秒,用户体验特别不好】,只适用于程序简单,对并发要求也不高,可以考虑它,执行效率是比较高的。

ParNew收集器:Serial收集器的多线程版本(不是真正意义上的多线程,是相对于Serial而言多了几条线程)新生代采用复制算法,老年代采用标记-整理算法。

Parallel Scavenge收集器(JDK1.8):并行收集器。

{并行:多条垃圾收集线程并行工作,此时用户线程仍然处于等待状态。}

{并发:用户线程跟垃圾收集线程同时执行(可能非并行,可能会交替执行),用户程序在继续运行,垃圾收集器运行在另一个CPU上。}

Parallel Scavenge收集器关注点是吞吐量[垃圾收集时间和总时间的占比],1/(n+1),吞吐量为1-1/(n+1)。

CMS(Concurrent Mark Sweep)收集器:以获取最短回收停顿时间为目标的收集器。

应用程序线程运行期间产生GC,先进行初始标记不回收,等再次发生GC的时候再来进行下一步,进入到并发标记阶段,同时开启用户线程与GC线程,GC线程会对运行中产生的浮动垃圾【用户线程在运行的过程中还会产生一些垃圾】进行标记,标记之后进入下一个阶段。重新标记,此时将所有的线程都停掉进行专门标记,将所有产生浮动垃圾或者没注意到的垃圾都进行重新标记,结束之后进行到清理阶段,此时开始开启用户线程,标记阶段线程跑得比较快,找内存里面没用的对象是找的非常快的,因为只需要找标记状态,扫一遍,但不进行回收,一旦进行回收,它会变得慢起来,把用户线程启动,此时,用户线程与GC同时运行,对用户来说,此时STW是比较短暂的。最后一个步骤则是并发清除,用户线程开启,GC线程对标记的区域进行清除。一般来讲新生代的stw是比老年代的stw短暂很多。应用在老年代

优点:需要很多次回收,时间停顿短,清理慢,把多次停顿做了一个分解。并发收集

缺点:

1、对CPU资源敏感

2、没有办法处理浮动垃圾

3,使用标记清除算法,导致收集结束时产生大量空间碎片。

接下来介绍最热的G1回收器

G1回收器{为高并发诞生的}

堆区域分代,但G1不分代,分块,把内存分成一块一块的,Region区域,随机把有一些区域设为Eden,如果你的对象是比较小的,那找一块小的区域放进去即可;如果你的对象比较大,存活时间比较长,找一个比较大点的内存空间放进去,这个时候它就会变成Old区域。

G1收集器的特点:

并行与并发:充分利用CPU、多核环境下硬件优势,发挥每一个CPU核心的作用,来缩短STW停顿时间。

分代手机:从整体来看基于标记-整理算法,从局部来看是基于复制算法。

可预测停顿:对我们的JVM设置一个参数,比如说G1过程的延时,可以设置为10ms,即每一次垃圾收集的过程都会在10ms内完成,但是它的次数会增多。

G1收集器的过程:(前面三个步骤类似于CMS)

初始标记

并发标记

最终标记

筛选回收:在Region区域做一个优先级的设置,比如说这个区域某对象占比几百M,其他对象占比几百K,这个时候该对象会被标记为可回收对象,它就会被优先回收掉,因为回收它的价值是最大的。筛选回收就是为了实现可预测的停顿。

假如在10ms回收我们的内存,本来时间是1s回收,现在就有很多对象回收不了,这个时候会进行垃圾回收筛选,将优先列表里面最优先级别的Region区域回收掉,这也是为什么G1称之为Garbage-First了。

怎么选择垃圾收集器?

1.优先调整堆的大小让服务器自己选择

2.如果服务器单核,没有停顿时间的要求,串行或者JVM自选

3.如果允许停顿时间超过1s,并行或者JVM自选

4.如果内存小于100M,选串行收集器。

5.如果响应时间最重要,不能超过1s,选并发收集器

 

 


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章