Java基础
Java属于解释型语言,源代码不直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。
1.继承
实现: public class 子类 extends 父类{}
final 关键字:当不希望一个类被继承是则在class前加上关键字final
子父类存在同名成员时,子类中默认访问子类的成员,可通过super指定访问父类的成员。格式:super.xx
创建子类对象时,默认会调用父类的无参构造方法,可通过super指定调用父类其他构造方法,格式:super(yy)
2.封装
实现: private String name;
3.多态
实现:instanceof
4.抽象
实现:abstract class Ceshi
一个类中有抽象方法则必须申明为抽象类,抽象类和接口中都可以包含静态成员常量。抽象类除了不能实例化对象之外,类的其它功能依然存在。
5.接口
实现:interface
public class 类名 extends 父类名 implements 接口名
接口是一种特殊的抽象类,其中全都是抽象方法,不能被实例化;默认权限就是public(只能是)
接口中没有static类型。
实现接口并重写其中的方法,使得上层对于下层仅仅是接口依赖,而不依赖具体类,有利于实现多态,且不用了解详细的实现。
Volatile
volatile是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取。volatile仅能用在变量上,不会造成线程的阻塞。
方法重写
两个方法返回值、方法名、参数列表必须完全一致;子类中的权限不能小于父类的权限;发生在继承类中
方法重载
参数名相同,参数列表可以不同;发生在一个类中,本质是以相同的方法处理不同的数据
构造方法
构造方法就是与类同名的那个方法,它的作用是可以用来初始化
构造方法必须跟类名相同,普通的类方法能与类同名的,但是要返回值。
一个类可以定义多个构造方法
constructor;构造函数。
class Person {
public Person(String n,int a) //构造方法
{
name = n; age = a;
}
private string name;
private int age;
}
static void main(String[] args){
Person p = new Person("菜包",22);//这就是作用
}
//Person("菜包",22);其中这个就是构造函数,“菜包”为构造方法的形参;
abstract方法
abstract方法可以用来修饰一个类或者一个方法。 修饰一个方法时,表示该方法只有特征签名(signature),没有具体实现,抽象方法只可以被public 和 protected修饰
static方法
static方法一般称作静态方法,静态方法,可以直接通过类名调用而不需要通过对象调用。静态方法不能被abstract修饰
在静态方法中不能直接访问实例方法和实例变量,需要new一个实例来访问。
native方法
Native可以和其他一些修饰符连用,但是abstract方法和Interface里的方法不能用native来修饰。因为native暗示这个方法是有实现体的
泛型
泛型,即参数化类型,简而言之将类型由原来的具体的类型参数化 例如ArrayList可以存放任意类型,先添加一个String类型,再添加一个Integer类型,再以String的方式使用,会导致程序崩溃 如果我们以 List<String> arrayList的方式创建,编译器会在编译阶段就会发现错误
String
String简介:定义不可变(final)的字符串常量,常量池中生成一个字符串
new String:在堆上创建字符串对象
String.Intern():将字符串添加到常量池
String str=”JA”+”VA” == String str=”JAVA”(如果在println(b+c)中为new)
常量字符串和变量拼接时(String str3=Str + “01”)会调用stringBuilder.append()在堆上创建新的对象。
String[] ss=s.spilt(" "):根据括号中的内容将字符串划分为单词
s.indexOf('e'):判断元素是否在字符串中存在,找到返回第一个下标,找不到返回-1
s.contains("name"):判断字符串中是否包含子串
s.cancat('!!!'):拼接一段字符串'!!!'
s.startWith() & s.endWith():判断是否以此为开头或结尾
s.replace('a','b',3):把字符串中的a替换成b,且不超过3次
s.valueof(n):把n转换成字符串类型
s.toUpperCase():小写转大写
s.toLowerCase():大写转小写
sb.append('a'):同concat
sb.reverse():把stringbuffer里面的字符串翻转
Arrays.sort(int[] a):对一个数组的所有元素按从小到大排序
for(String obj : strList)print(obj);
Integer
为Integer赋值的时候,java编译器会将其翻译成调用valueOf()方法。比如Integer i=127翻译为Integer i=Integer.valueOf(127)
然后我们来看看valueOf()函数的源码:
public static Integer valueOf(int i)
{
//high为127
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127并且大于等于-128时才可使用常量池,因为他们至占用一个字节(-128~127)
Date date=new Date():读取当前时间
date.getTime()
DateFormat df=new SimpleDateFormat("yyyy-mm-dd"):根据格式打印时间
Random random=new Random();
random.setSeed():随机种子
int x=random.nextInt():获取随机数
UUID.randomUUID():生成随机字符串
线程
线程的概念
进程是运行的的程序,而线程是进程的一次执行,线程又叫做轻量级进程。进程是资源分配的单元,线程是cpu调度的最小单位。线程上下文切换比进程上下文切换要快得多。
线程的状态
线程包括5种状态:新建,就绪,运行,堵塞,死亡
新建:顾名思义创建了一个线程对象
就绪:线程对象创建后,某个线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
运行:线程获得了cpu时间片,开始执行程序代码。
堵塞:线程因为某种原因放弃了cpu使用权,主要包括:
等待阻塞:运行中的线程执行o wait()方法,JVM 会把该线程放入等待队列(waitting queue )中。
同步阻塞:运行中的线程获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池( lock pool )中。
其他阻塞:运行中的线程执行Thread.sleep()或t.join ()方法,或者发出了 I / O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
死亡:线程run ()、 main()方法执行结束,或者因异常退出了run ()方法,则该线程结束生命周期。
线程的创建启动
实现并启动线程有四种方法:继承Thread类:写一个类继承自Thread类,重写run方法。用start方法启动线程
public class MyThread extends Thread {
@Override
public void run() {
for (int i ; i < 10; i++) {
System.out.println("\t"+i);
}
}
Thread myThread1 = new MyThread();
myThread1.start();
实现Runnable接口:写一个类实现Runnable接口,实现run方法。创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
Runnable接口有以下优点:避免点继承的局限,一个类可以继承多个接口;适合于资源的共享。但在执行完任务之后无法获取执行结果。
Public class SonThread implement Runnable{
@Override
public void run(){
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
SonThread my = new SonThread();
Thread thread1 = new Thread(my);
thread1.start();
实现Callable接口:使用Callable接口创建线程。具体是创建Callable接口的实现类,并实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。
class Target implements Callable<Integer> {
int i=0;
public Integer call() throws Exception {
for (; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+""+i);
}
return i;//call方法有返回值
}
}
线程池
一般我们进行线程的操作时,往往要创建一个新的线程,执行完毕后再销毁,等到有新的执行命令时,又得重新创建线程,如此一来显得十分繁琐,如果我们将之前执行过的线程不销毁而是放入一个池子中,当需要执行时直接引用它,这就能省下许多操作的步骤与时间,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存而失败,提高线程的可管理性。
线程池的组成
一般一个简单线程池至少包含下列组成部分。
1. 线程池管理器(ThreadPoolManager):用于创建并管理线程池
2. 工作线程(WorkThread): 线程池中线程
3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行。
4. 任务队列:用于存放没有处理的任务。提供一种缓冲机制。
线程池管理器至少有下列功能:创建线程池,销毁线程池,添加新任务
创建启动线程
定义线程—>实例化线程—>启动线程。
定义线程:
1、扩展java.lang.Thread类。 2、实现java.lang.Runnable接口。
实例化线程:
1、如果是扩展java.lang.Thread类的线程,则直接new即可。
2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法。
public class ThreadPoolManager {
private int threadCount; //启动的线程数
private WorkThread[] handlers; //线程数组
private ArrayList<Runnable> taskVector = new ArrayList<Runnable>(); //任务队列
ThreadPoolManager(int threadCount) {
this.threadCount = threadCount;
for (int i = 0; i < threadCount; i++) {
handlers[i] = new WorkThread();
handlers[i].start();
}
}
void shutdown() { //关闭打开的线程和清空还没有执行的任务
synchronized (taskVector) {
while (!taskVector.isEmpty())
taskVector.remove(0); //清空任务队列
}
for (int i = 0; i < threadCount; i++) {
handlers[i] = new WorkThread();
handlers[i].interrupt(); //结束线程
}
}
void execute(Runnable newTask) { //增加新任务
synchronized (taskVector) {
taskVector.add(newTask);
taskVector.notifyAll();
}
}
private class WorkThread extends Thread {//实际的工作线程
public void run() {
Runnable task = null;
for (;;) {
synchronized (taskVector) {//获取一个新任务
if (taskVector.isEmpty())
try {
taskVector.wait();
task = taskVector.remove(0);
}
catch (InterruptedException e) {
break;
}
}
task.run();
}
}
}
}
ThreadPoolExecutor类
public class ThreadPoolExecutor extends AbstractExecutorService { ... }
public abstract class AbstractExecutorService implements ExecutorService { ... }
public ThreadPoolExecutor(int corePoolSize,//线程池的基本大小
int maximumPoolSize,//最大值
long keepAliveTime,//存活时间
TimeUnit unit,//参数keepAliveTime的时间单位,有7种取值
BlockingQueue<Runnable> workQueue,//一个阻塞队列,存储等待执行的任务
ThreadFactory threadFactory,//创建新线程时使用的工厂
RejectedExecutionHandler handler//表示当拒绝处理任务时的策略
){ ... }
execute()方法:是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
submit()方法:是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果(Future相关内容将在下一篇讲述)。
shutdown()和shutdownNow()是用来关闭线程池的。
线程相关
ThreadLocal类属于Thread类中的Map类,用于存储每一个线程的变量的副本。ThreadLocal保证各个线程的数据互不干扰,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。 可以保证线程安全又可以让性能不会太低。但是ThreadLocal的缺点是占用了较多的空间。
sleep():让线程睡眠一段时间,在此期间线程不消耗CPU资源。但会占着CPU不工作,增加时间限制。
suspend():使得线程进入阻塞状态,并且不会自动恢复,必须其对应的 resume()被调用,才能使得线程重新进入可执行状态
Java虚拟机
Java虚拟机(Java Virtual Machine)是java的核心和基础,是处于java编译器和os平台之间的虚拟处理器。它是一种利用软件方法实现的抽象的计算机基于下层的操作系统和硬件平台,是一个可以执行Java字节码的虚拟机进程。
要知道Java被称为跨平台的语言,其中核心就在于Java虚拟机,Java是解释型语言,不能被计算机直接理解,需要通过Javac编译器编译成二进制的.class字节码文件,再通过jvm当中的java解释器将.class文件解释成对应平台的机器码执行,也就是说靠Java虚拟机来实现Java的跨平台功能。
Java虚拟机内存
1.程序计数器
程序计数器(又称PC寄存器)是用于存储每个线程下一步将执行的JVM指令,如该方法为native的,则PC寄存器中不存储任何信息,每个线程都有自己独立的程序计数器。是Java内存中读取最快的部分,也是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2.Java虚拟机栈
JVM栈是线程私有的,每个线程创建的同时都会创建JVM栈,每个方法被执行时都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。栈的存储空间和堆一样是不需要连续的。
局部变量表:各种数据类型、对象引用(refrence类型,可能是指向地址的指针),64位long和double占2个局部变量空间。
3.本地方法栈
本地方法栈与虚拟机栈十分相似,虚拟机栈执行Java方法,而本地方法栈执行本地方法(Native Method,就是一个java调用非java代码的接口),本地方法栈也会抛出和虚拟机栈一样的异常。
4.Java堆
堆是JVM用来存储对象实例以及数组值的区域,在虚拟机启动之初就创建。是JVM中内存中最大的一块,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收,堆是JVM中所有线程共享的。
堆是垃圾收集器管理的主要区域,也被称作GC区。
栈是运行时单位,解决程序该如何执行的问题,而堆是存储的单位, 解决数据存储的问题,且不需要连续的物理内存。
5.方法区域
方法区域存放了所加载的类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的。
在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
5.运行时常量池
常量池空间从方法区域中分配,存放的为类中的固定的常量信息、方法和Field的引用信息等。
垃圾回收GC
引用计数法:
在对象上添加一个引用计数器,每当有一个对象引用它时,计数器加1,当使用完该对象时,计数器减1,计数器值为0的对象表示不可能再被使用。
public class GCtest {
private Object instance = null;
private static final int _10M = 10 * 1 << 20;
// 一个对象占10M,方便在GC日志中看出是否被回收
private byte[] bigSize = new byte[_10M];
public static void main(String[] args) {
GCtest objA = new GCtest();
GCtest objB = new GCtest();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
可达性分析算法GC ROOT对象:
1.虚拟机栈中引用的对象(栈帧中的本地变量表);
2.方法区中类静态属性引用的对象;
3.方法区中常量引用的对象;
4.本地方法栈中JNI(Native方法)引用的对象。
对于可达性分析算法而言,未到达的对象并非是“非死不可”的,若要宣判一个对象死亡,至少需要经历两次标记阶段。
1. 如果对象在进行可达性分析后发现没有与GCRoots相连的引用链,则该对象被第一次标记并判断是否有必要执行该对象的finalize方法,若对象没有覆盖finalize方法或finalize方法已经被虚拟机执行过了,该对象将会被回收。反之这个对象会被放置在一个叫F-Queue的队列中,之后会由虚拟机自动建立的、优先级低的Finalizer线程去执行。
2.对F-Queue中对象进行第二次标记,如果对象在finalize方法中拯救了自己,即关联上了GCRoots引用链,如把this关键字赋值给其他变量,那么在第二次标记的时候该对象将从“即将回收”的集合中移除,如果对象还是没有拯救自己,那就会被回收。
4种引用类型
1、强引用
代码中普遍存在的类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
2、软引用
描述有些还有用但并非必需的对象。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。Java中的类SoftReference表示软引用。
3、弱引用
描述非必需对象。被弱引用关联的对象只能生存到下一次垃圾回收之前,垃圾收集器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。Java中的类WeakReference表示弱引用。
4、虚引用
这个引用存在的唯一目的就是在这个对象被收集器回收时收到一个系统通知,被虚引用关联的对象,和其生存时间完全没关系。Java中的类PhantomReference表示虚引用。
年轻代回收算法
所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。
回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。
JVM内存配置参数:-Xmx10240m -Xms10240m -Xmn5120m -XXSurvivorRatio=3
-Xmx:最大堆大小
-Xms:初始堆大小
-Xmn: 年轻代大小
-XXSurvivorRatio:年轻代中Eden区与Survivor区的大小比值
故Survivor区为2048m
-Xms初始堆大小即最小内存值为10240m
年老代的回收算法
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。
内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
持久代的回收算法
用于存放静态文件,如Java类、方法等,即方法区
Serial收集器:新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式。
ParNew收集器:Serial收集器的多线程版本,在多核CPU环境下比Serial更好表现。
Scavenge GC
当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式不会影响到年老代。
Full GC
对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:
1.年老代(Tenured)被写满;
2.持久代(Perm)被写满;
3.System.gc()被显示调用;