日常梳理-Java原理

垃圾回收

jdk8:垃圾回收算法

哪些垃圾需要回收

  • 有三个区是不需要进行垃圾回收的,分别是:程序计数器、JVM栈、本地方法栈。
  • 需要进行回收垃圾的区:堆和方法区

常用算法

标记-清除算法(Mark-Sweep)

分为两个阶段,标记和清除。首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象(也可以反过来,标记存活的对象,统一回收所有未被标记的对象。)

缺点有两个:

第一个执行效率不稳定,如果Java堆中包含大量对象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过程的执行效率都随对象数量增长而降低;

第二个内存碎片化严重,导致之后程序运行过程中需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一个垃圾收集动作。

标记-复制算法(copying)

按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。

最大的问题是可用内存被压缩到了原本的一半,且存活对象增多的话,Copying 算法的效率会大大降低。

标记-整理算法(Mark-Compact)

标记后不是清理对象,而是将存活对象移向内存的一端,然后清除掉边界以外的内存。

分代收集算法
新生代与复制算法

因为新生代中每次垃圾回收都要 回收大部分对象,即要复制的操作比较少,但通常并不是按照 1:1 来划分新生代。一般将新生代 划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用
Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另 一块 Survivor 空间中。

老年代与标记整理算法

老年代因为每次只回收少量对象,因而采用 Mark-Compact 算法。

GC 垃圾收集器

Serial 收集器(单线程+复制算法)–新生代收集器

Serial 是一个单线程的收集器,它的“单线程”的意义不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

它不但只会使用一个 CPU 或一条线程去完成垃圾收集工 作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。Stop The World

ParNew收集器(Serial+多线程)

官方已不推荐使用,希望ParNew+CMS收集器组合被G1所取代。

Parallel Scavenge收集器(多线程+复制算法)–新生代收集器

Parallel Scavenge 收集器的特点是它的关注点与其它收集器不同。

CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。所谓吞吐量就是处理器用于运行用户代码的时间与处理器总消耗时间的比值。

SerialOld收集器(单线程+标记整理算法)–老年代收集器
ParallelOld收集器(多线程+标记整理算法)–老年代收集器
CMS收集器(多线程+标记清除算法)
G1收集器

java查看垃圾收集器

java -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -version

-XX:UseParallelGC
使用的垃圾收集器是:新生代(Parallel Scavenge),老年代(Ps MarkSweep)组合。

简述对象的分配规则

  • 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次YGC。并将还活着的对象放到from/to区,若本次YGC后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。

  • 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。

  • 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次YGC那么对象会进入Survivor区,之后每经过一次YGC那么对象的年龄加1,直到达到阀值对象进入老年区。默认阈值是15。可以通过-XX:MaxTenuringThreshold参数来设置。

  • 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。无需等到-XX:MaxTenuringThreshold参数要求的年龄。

  • 空间分配担保。每次进行YGC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,(如果小于检查HandlePromotionFailure设置,如果true则只进行YGC,如果false则进行Full GC,jdk7之后该参数已经没用)。

JVM内存模型:

概念

Java内存模型的主要目的是定义程序中各种变量的访问规则(即关注在虚拟机中把变量值存储到内存和从内存中取出变量值这样的底层细节)。

此处的变量与Java编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数组对象的元素,但是不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题

所以Java内存模型中的变量,为共享变量

Tip:

  • 如果局部变量是一个reference类型,它引用的对象在Java堆中可被各个线程共享,但是reference本身在Java栈的局部变量表中是线程私有的。
  • 主内存主要包括堆和方法区。

工作内存

每个线程都有一个工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不是直接读写主内存中的数据。
  
工作内存中主要包括两个部分:

  • 一个是属于该线程私有的栈;
  • 对主存部分变量拷贝的寄存器(包括程序计数器PC和CPU工作的高速缓存区)。

线程、主内存、工作内存三者的交互关系

在这里插入图片描述

运行时数据区

在这里插入图片描述

内存间交互操作

我们接着再来关注下变量从主内存读取到工作内存,然后同步回工作内存的细节,这就是主内存与工作内存之间的交互协议。Java内存模型定义了以下8种操作来完成,它们都是原子操作(除了对long和double类型的变量)。

  • lock(锁定)
    作用于主内存中的变量,它将一个变量标志为一个线程独占的状态。
  • unlock(解锁)
    作用于主内存中的变量,解除变量的锁定状态,被解除锁定状态的变量才能被其他线程锁定。
  • read(读取)
    作用于主内存中的变量,它把一个变量的值从主内存中传递到工作内存,以便进行下一步的load操作。
  • load(载入)
    作用于工作内存中的变量,它把read操作传递来的变量值放到工作内存中的变量副本中。
  • use(使用)
    作用于工作内存中的变量,这个操作把变量副本中的值传递给执行引擎。当执行需要使用到变量值的字节码指令的时候就会执行这个操作。
  • assign(赋值)
    作用于工作内存中的变量,接收执行引擎传递过来的值,将其赋给工作内存中的变量。当执行赋值的字节码指令的时候就会执行这个操作。
  • store(存储)
    作用于工作内存中的变量,它把工作内存中的值传递到主内存中来,以便进行下一步write操作。
  • write(写入)
    作用于主内存中的变量,它把store传递过来的值放到主内存的变量中。

在将变量从主内存读取到工作内存中,必须顺序执行read和load;要将变量从工作内存同步回主内存中,必须顺序执行store和write。注意,Java内存模型只要求上述两个操作必须按顺序执行,但不要求是连续执行。除此,Java内存模型还规则了在执行上述8种基本操作时必须遵循以下规则:

  • 1,不允许read和load、store和write操作之一单独出现。即不允许一个变量从主内存被读取了,但是工作内存不接受,或者从工作内存回写了但是主内存不接受。
  • 2,不允许一个线程丢弃它最近的一个assign操作,即变量在工作内存被更改后必须同步改更改回主内存。
  • 3,工作内存中的变量在没有执行过assign操作时,不允许无意义的同步回主内存。
  • 4,在执行use前必须已执行load,在执行store前必须已执行assign。
  • 5,一个变量在同一时刻只允许一个线程对其执行lock操作,一个线程可以对同一个变量执行多次lock,但必须执行相同次数的unlock操作才可解锁。
  • 6,一个线程在lock一个变量的时候,将会清空工作内存中的此变量的值,执行引擎在use前必须重新read和load。
  • 7,线程不允许unlock其他线程的lock操作。并且unlock操作必须是在本线程的lock操作之后。
  • 8,在执行unlock之前,必须先把此变量同步回主内存中(执行store、write操作)。

java变量分类

按声明的位置划分

1、分为局部变量和成员变量

  • 局部变量:方法和语句块内定义的的变量。(在定义局部变量时,必须对其进行初始化。)
  • 成员变量:方法外部,类的内部定义的变量。(成员变量可以是java语言中任何一种数据类型(包括基本类型和引用类型))

2、成员变量又分为实例变量和类变量(被static修饰的变量)

类由成员变量和成员方法构成。而成员变量又分为实例变量和类变量(被static修饰的变量)。

  • 使用static修饰的成员变量是类变量,属于该类本身;
  • 没有使用static修饰的成员变量是实例变量,属于该类的实例。

在同一个JVM内,每个类只对应一个Class对象,但每个类可以创建多个Java对象。
由于同一个JVM内每个类只对应一个Class对象,因此同一个JVM内的一个类的类变量只需一块内存空间;但对于实例变量而言,该类每创建一次实例,就需要为实例变量分配一块内存空间。也就是说,程序中有几个实例,实例变量就需要几块内存空间。

按所属的数据类型划分

在这里插入图片描述

  • 基本数据类型
    内存分析:
    例如’int i=0;’,在内存中只分配一个空间,名字是i,里面的值是0。只要使用了i这个名字就能找到其中的值。
  • 引用数据类型:java中的对象是通过对其引用进行操作的。
    内存分析:引用类型占两块内存,引用变量占一块,new出来的对象占一块内存。

数据具体存储示例

public class Demo2 {
  public static void main(String[] args) {
    Person p1 = new Person();
    p1.name="尾田荣一郞";
    Person.country="日本";
    Person p2 = new Person();
    p2.name="宫崎骏";
    p1.speak();
    p2.speak();
//    输出
//    尾田荣一郞...日本
//    宫崎骏...日本

  }
}

class Person{
  String name;
  static String country;

  public void speak(){
    System.out.println(name+"..."+country);
  }
}

在这里插入图片描述

java

java的三大特性

封装,继承和多态

【public、private、protected区别】-访问权限

作用域当前类同一package子孙类其他package
public
protected×
friendly××
private×××
 注:不写时默认为friendly

重载与重写区别

重载:

1、方法名必须一样,必须具有不同的参数列表(参数类型和个数)

2、可以有不同的返回值类型

3、可以有不同的访问修饰符

4、可以抛出不同的异常

重写:

1、参数列表必须完成与被重写的方法相同。

2、返回的类型必须与被重写的方法的返回类型相同

3、访问修饰符的权限一定要大于或等于被重写方法的访问修饰符

4、重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常。例如:

父类的一个方法申明了一个检查异常IOException,在重写这个方法是就不能抛出Exception,只能抛出IOException的子类异常,可以抛出非检查异常。

Throwable类、Error与Exception关系

Throwable 类是 Java 语言中所有错误或异常的超类。
Error类和Exception类都继承自Throwable类。
Exception分为两类:运行时异常(非检查异常unchecked)和非运行时异常(检查异常checked)。
常见的运行时异常:
NullPointerException(空指针异常)
IndexOutOfBoundsException(数组越界异常)
ClassCastException(类转换异常)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
还有IO操作的BufferOverflowException异常

serialVersionUID的作用

serialVersionUID的作用

内存泄露和内存溢出区别

可以用两种方式解释:

内存泄露是指你的应用使用资源之后没有及时释放,导致应用内存中持有了不需要的资源,这是一种状态描述
内存溢出是指你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出,这是一种结果描述
而且通常都是由于内存泄露导致堆栈内存不断增大,从而引发内存溢出。

内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory

内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak会最终会导致out of memory!

java中static作用

static变量

按照是否是静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。

两者的区别:

对于静态变量而言,它在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名.的方式调用。

而对于实例变量来说,每创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响。

静态方法

静态方法只能访问静态成员变量和成员方法。而静态方法可以被任何实例调用。

对于静态方法来说有以下几条限制:

只能调用其他的static方法。

只能访问static数据。

不能以任何方式引用this或super。

static代码块(仅在类被加载时执行一次。)

static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

利用静态代码块可以对一些static变量进行赋值。

static final

static final 用来修饰成员变量和成员方法。

修饰变量时,只能赋值一次,不能修改。

修饰方法时,表示不可覆盖,即不能在子类中重写父类的final方法。

接口与抽象类

接口与抽象类的区别

参数抽象类接口
默认的方法实现它可以有默认的方法实现接口完全是抽象的。它根本不存在方法的实现
实现子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器抽象类可以有构造器接口不能有构造器
与正常Java类的区别除了你不能实例化抽象类之外,它和普通Java类没有任何区别接口是完全不同的类型
访问修饰符抽象方法可以有publicprotecteddefault这些修饰符接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法抽象方法可以有main方法并且我们可以运行它接口没有main方法,因此我们不能运行它。
多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其它接口
速度它比接口速度要快接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

什么时候使用抽象类和接口?

(1) 如果你拥有一些方法并且想让它们中的一些有默认实现,那么使用抽象类吧。

(2) 如果你想实现多重继承,那么你必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。因此你就可以使用接口来解决它。

(3) 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值