天天看点

学习记录-《码出高效》

2020/08/31

1、逻辑或、逻辑与运算只能针对布尔类型的条件表达式进行运算。

2、计算机定义了两种小数:定点数和浮点数

3、IEEE754浮点数标准,规定了4种浮点数类型:单精度、双精度、延伸单精度、延伸双精度

4、浮点数使用:

  在使用浮点数的时候推荐使用双精度,使用单精度由于表示区间的限制,计算结果会出现微小的误差。

在要求绝对精确表示的业务场景下,比如金融行业的货币表示,推荐使用整型存储器最小单位的值,展示时可以转换成该货币的常用单位,比如人命币使用分存储,美元使用美分存储。在要求精确表示小数点n位的业务场景下,比如圆周率要求存储小数点后1000位数字,使用单精度和双精度浮点数类型保存都是难以做到的,这时推荐使用数组保存小数部分的数据。

在比较浮点数时,会存在误差。禁止通过判断两个浮点数是否相等来控制业务流程。在数据库中保存小数时,推荐使用decimal类型,禁止使用float和double类型,因为这两种类型在存储的时候会有精度损失。

综上所述,在要求绝对精度表示的业务场景中,在小数保存、计算、转型过程中要谨慎对待。

5、cpu由控制器和运算器组成,内部寄存器使这两者协同更加高效。

2020/09/01

TCP三次握手创建连接(二次会导致脏连接)

TCP四次挥手断开连接

从经验来看,数据库层面的请求应答时间必须在100ms内,秒级sql查询通常存在巨大的性能提升空间。有如下应对方案:

1)建立高效且合适的索引

2)排查连接资源未显式关闭的情形

3)合并短请求

4)合理拆分多个表join的SQL,若是超过三个表则禁止join

5)使用临时表

6)应用层优化

7)改用其他数据库

SQL注入解决办法:

1)过滤用户输入参数中的特殊字符

2)禁止通过字符串拼接的SQL语句,严格使用参数绑定传入的sql参数

3)合理使用数据库访问框架提供的防止注入机制,mybatis禁止使用${}

防范XSS攻击:前端使用innerText 而不是innerHtml

防范CSRF漏洞主要通过以下方式:

1)CSRF token验证

2)人机交互,比如短信验证

2020/09/02:

1、安全套接字层:Secure socket layer - SSL

2、HTTPS ->HTTP OVER SSL

3、对称加密算法:DES

4、非对称加密算法:RSA

5、优秀的程序员至少需要掌握3门语言,这有助于知晓不同语言的各自特性,更重要的是洞悉语言共性和编程语言思想,跨域语言的抽象思维和架构掌控力。但是掌握不等于精通,真正的大师,需要醉心于某种语言,不断研究、不断打磨、不断回炉,才能达到炉火纯青、登峰造极的境界。我们写的每一行代码都是站在巨人的肩膀上,使我们看得更远。虽然任何编程语言的结构都是顺序、条件、循环,任何编程语言的本质都是输入输出,但是0与1的世界一定会因为编程变得更智能,更美好。

6、慎用object的clone()方法,因为对象的clone()方法是浅拷贝,若想实现深拷贝,则需要覆写clone()方法实现引用对象的深度遍历式拷贝。

7、设计模式七大原则之一的迪米特法则就是对于封装的具体要求,即A模块使用B模块的某个接口行为,对B模块中除此之外的其他信息知道得尽可能少。

8、继承的is-a判断标准:是否符合里氏代换原则(LSP)任何父类能出现的地方子类都能够出现。

9、提倡组合优先的原则来扩展类的能力,即优先采用组合或者聚合的类关系来复用其他类的能力,而不是继承。

10、JDK5~JDK8的重要特性:

JDK5新特性:foreach迭代方式,可变参数,枚举,自动拆装箱,泛型,注解等重要特性(历史意义的版本)

JDK6新特性:。。。

JDK7新特性:switch支持字符串作为匹配条件、泛型类型自动推断、try-with-resource资源关闭技巧,Objects工具类,ForkJoinPool等重要类与特性。

JDK8新特性:lambda表达式、函数式编程、引入Optional避免空指针

11、抽象类是模版式设计,接口是契约式设计。

12、定义包内可见静态内部类的方式很常见,这样做的好处是:

1)作用域不会扩散到包外

2)可以通过“外部类.内部类”的方式直接访问

3)内部类可以访问外部类中所有的静态属性和方法

13、在定义类时,推荐访问控制级别从严处理:

1)如果不允许外部直接通过new创建对象,构造方法必须是private

2)工具类不允许有public和default构造方法

3)类非static成员变量并且与子类共享,必须是protected

4)类非static成员变量并且仅在本类使用,必须是private

5)类static成员变量,如果仅在本类使用,必须是private

6)若是static成员变量必须考虑是否为final

7)类成员方法只供类内部调用,必须是private

8)类成员方法只对继承类公开,那么限制为protected

14、访问权限控制符:无 不为default

2020/09/21

1、尽量不使用可变参数。如果一定要使用,则只有相同参数类型,相同业务含义的参数才可以。并且一个方法只能有一个可变参数,且为该方法的最后一个参数。不推荐使用Object作为可变参数类型。

2、正确使用参数,参数预处理,包含两种情况:

1)入参保护。常见于批量接口。如:数据量过多导致内存占满。

2)参数校验

3、构造方法:

1)构造方法名称必须与类名相同。

2)构造方法没有返回类型,void也不能有。它返回对象地址,并赋值给引用变量。

3)不能继承,不能覆写,不能被直接调用。调用途径有三种:new、super、通过反射方式获取并调用。

4)定义类时提供了无参的构造方法。如果定义了有参数构造方法,还需要原始构造方法,则需要显式定义。

5)构造方法可以私有

注意:构造方法应遵循单一职责原则,不应在构造方法中引入业务逻辑。

4、类对象创建:

先执行父类和子类的静态代码块,然后再执行父类和子类的构造方法,静态代码块只运行一次,在第二次对象实例化时不会运行。

2020/10/17:

1、类内方法

除构造方法外类中还可以有三类方法:实例方法、静态方法、静态代码块

2、静态方法:

1)静态方法中不能使用实例成员变量和实例方法

2)静态方法不能使用this和super关键字,这两个关键字指代的都是需要被创建出来的对象

3、静态代码块

1)静态代码块是先于构造方法执行的特殊代码块

2)不能存在于任何方法体内,包括类静态方法和属性变量

4、getter和setter

一般不包含任何业务逻辑

好处:

1)满足面向对象语言封装的特性

2)有利于统一控制。如对对象属性修改作权限控制可在setter处做处理

易出错点:

1)在setter/getter中添加业务逻辑很难排查

2)同时定义iSXxxx()和getXxxx(),两者同时存在会在iBATIS和Json序列化引起冲突

3)相同的属性名容易带来歧义

非getter/setter参数名不能语成员变量相同

5、覆写-动态绑定

如果父类的方法不能满足子类的期望,那么子类可以重新实现方法覆盖父类的实现,因为有些子类是延迟加载,甚至是网络加载的,所以最终的是现实需要在运行期判断,这就是-动态绑定

通过父类执行子类方法时需要注意以下两点:

1)无法调用到子类中存在而父类本身不存在的方法

2)可以调用到子类中覆写了父类的方法,这是一种多态实现

想要成功覆写父类方法,需要满足4个条件(转型满足LSP李氏替换原则):

1)访问权限不能变小

2)返回类型能够向上转型为父类的返回类型

3)异常也要能向上转型为父类的异常

4)方法名和参数列表必须完全一致

总结为“一大两小两同”

6、重载-静态绑定

1)在编译器眼里,方法名+参数列表组成一个唯一键,称为方法签名

2)JVM在重载方法中选择合适的目标方法的顺序如下:

a、 精确匹配

b、如果是基本数据类型,自动转换为更大表示范围的基本类型

c、通过自动拆箱与装箱

d、通过子类向上转型继承路线依次匹配

e、通过可变参数匹配(优先级最低)

2020/10/15:

1、Java 9种基本数据类型:

boolean,byte,char,short,int,long,float,double,refvar(引用变量,也叫引用句柄)

2、不能用双引号的方式对char类型进行赋值

3、引用分为两种数据类型:引用变量本身,和引用指向的对象

4、基本数据类型int占用4个字节,而对应的包装类Integer实例对象占用16个字节

5、类定义中的方法代码不占用实例对象的任何空间

1)对象头:对象头最小占用12字节空间(包括对象标记8字节和类元信息)4字节

2)实例数据:实例成员变量和所有可见的父类成员变量

3)对齐填充:对象的存储空间分配单位是8个字节,如果需要占用的内存不为8的倍数,则补充为相近最小8的倍数

6、各个包装类的缓存区间:

Boolean:使用静态final变量定义,valueOf就是返回这两个静态值

Byte:表示范围是-128~127,全部缓存

Short:表示范围是-32767~32767,缓存范围是-128~127

Character:表示范围是0~65535,缓存范围是0~127

Long:表示范围是-2的63次方~2的63次方-1,缓存范围是-128~127

Integer:表示范围是-2的31次方~2的31次方-1,缓存范围是-128~127。在VM options加入参数-XX:AutoBoxCacheMax=7777即可设置最大缓存值为7777

7、在选择使用包装类还是基本数据类型时,推荐使用如下方式:

1)所有的POJO类属性必须使用包装类型

2)RPC方法的返回值和参数必须使用包装数据类型

3)所有的局部变量推荐使用基本数据类型

8、命名规约

1)命名符合本语言特性

2)命名体现代码元素特征

3)最好望文知意

9、常量:常量是在作用域内保持不变的值,一般使用final关键字进行修饰。根据作用域区分为3类:

1)全局常量:指的是类的公开静态属性,使用public static final 修饰

2)类内常量:是私有静态属性,使用private static final 修饰

3)局部常量:分为方法常量和参数常量,前者是在方法或者代码块内部定义的常量,后者是在定义形式参数时,增加final标志,表示此参数值不能被修改

10、一般类型用枚举表示,状态可用枚举也可用不能被实例化的抽象类的全局常量来表示,从而避免魔法值。

11、控制语句:

1)在if,else,for,while,do-while等语句中必须使用大括号

2)在条件表达式中不能有赋值操作,也不要在判断表达式中出现复杂的逻辑组合

3)多层嵌套不能超过3层

4)避免采用反逻辑运算符

JVM

1、类加载过程:

1)Load-加载:读取类文件产生的二进制流,并转化为特定的数据结构,初步校验cafe babe魔法数、常量池、文件长度、是否有父类等,然后创建对应的java.lang.Class实例

2)Link-链接:包括验证、准备、解析三个步骤。验证是更加详细的校验,比如final是否合规、类型是否正确、静态变量是否合理等;准备阶段是为静态变量分配内存,并设置默认值,解析类和方法确保类与类之间的额相互引用正确性,完成内存结构布局

3)Init-初始化:执行类构造器<clinit>方法,如果赋值运算是通过其他类的静态方法来完成的,那么马上会解析另外一个类,在虚拟机栈中执行完毕后通过返回值进行赋值。

2、在什么情况下需要自定义类加载器?

1)隔离加载类

2)修改类加载方式

3)扩展加载源

4)防止代码泄露

3、内存布局:堆、元空间、虚拟机栈、本地方法栈、程序计数器

1)堆是OOM的主要产生地,分为两大块:新生代和老年代

2)栈帧在整个JVM体系中地位颇高,包括局部变量表、操作栈、动态连接、方法返回地址等

总结:从线程共享的角度来看,堆和元空间是所有线程共享的,而虚拟机栈、本地方法栈、程序计数器是线程内部私有的。

2020/11/09:

并发与多线程:

1、线程状态:NEW (新建状态)、RUNNABLE(就绪状态)、 RUNNING (运行状态)、BLOCKED(阻塞状态) DEAD(终止状态)五种状态

(1)、NEW 新建状态,是线程被创建且未启动的状态。创建线程的方式有3种:

第一种是继承自Thread类,第二种是实现Runnable接口,第三种是实现Callable接口。相比第一种推荐第二种方式,因为继承自Thread类往往不符合里氏代换原则。而实现Runnable接口可以使编程更加灵活,对外暴露的细节比较少,让使用者专注于实现线程的run()方法上。

Callable与Runnable有两点不同:第一可以通过call()返回值,前两种方式都不能在任务执行完成后,直接获取执行结果,需要借助共享变量等来获取,而Callable和Future则很好地解决了这个问题,第二,call()可以抛出异常,而Runnable只有通过setDefaultUncaughtExceptionHandler()的方式才能在主线程中捕捉到子线程异常。

(2)、Runnable,就绪状态,是调用start()之后运行之前的状态。

(3)、RUNNING,运行状态,是run()正在执行时线程的状态。

(4)、BLOCKED,进入此状态,有以下三种情况:

同步阻塞:锁被其他线程占用

主动阻塞:调用THread的某些方法,主动让出CPU执行权,比如sleep(), join()等

等待阻塞:执行了wait()

(5)DEAD,终止状态,是run()执行结果,或因异常退出后的状态,此状态不可逆转

2、为保证线程安全,在多个线程并发地竞争共享资源时,通常采用同步机制协调各个线程的执行,以确保得到正确的结果。

3、线程安全问题只在多线程环境下才出现,单线程串行执行不存在此问题。保证高并发场景下的线程安全,可以从以下四个维度考量。

(1)、数据单线程内可见

(2)、只读对象

(3)、线程安全类

(4)、同步与锁机制

4、线程安全的核心理念就是“要么只读,要么加锁”

5、Java并发包(Java.util.concurrent,JUC)主要分成以下几个类族:

(1)线程同步类

(2)并发集合类

(3)线程管理类

(4)锁相关类

6、计算机锁也是从开始的悲观锁,发展到后来的乐观锁、偏向锁、分段锁等。锁主要提供了两种特性:互斥性和不可见性

7、Java中实现锁的方式有两种

(1)用并发包中的锁类

Lock是JUC包的顶层接口,它的实现逻辑并未使用synchronized而是利用了volatile的可见性

(2)是利用同步代码块

同步代码块一般使用Java的synchronized关键字来实现,有两种方式对方法进行加锁操作:

第一在方法签名处加synchronized关键字

第二使用synchronized(对象或者类)进行同步。

这里的原则是锁的范围尽可能小,锁的时间尽可能短,即能锁对象就不要锁类,能 锁代码块就不要锁方法。

8、JDK6以后不断优化使得synchronized提供三种锁的实现,包括偏向锁、轻量级锁、重量级锁,还提供了自动的降级和升级机制。JVM就是利用CAS在对象头上设置线程ID,表示这个对象偏向于当前线程,这就是偏向锁。

9、偏向锁是为了在资源没有被多线程竞争的情况下尽量减少锁带来的性能开销。在锁对象的对象头重有一个ThreadId字段,当第一个线程访问锁时,如果该锁没有被其他线程访问过,即ThreadId字段为空,那么JVM让其持有偏向锁,并将ThreadId字段的值设置为该线程的ID,当下一次获取锁时,会判断它们是否一致,如果一致,那么该线程不会重新获取锁,从而提高了程序的运行效率。如果出现锁的竞争情况,那么偏向锁会被撤销,并被升级为轻量级锁,如果竞争非常激烈会升级为重量级锁。偏向锁可以减低无竞争开销,它不是互斥锁,不存在线程竞争的情况,省去再次同步判断的步骤,提升了性能。

10、资源共享的两个原因是资源紧缺和共建需求。线程共享cpu是从资源紧缺的维度来考虑的,而多线程共享同一变量,通常是从共建需求的维度来考虑的。

11、原子性:指不可分割的一系列操作指令,在执行完毕前不会被其他任何操作中断,要么全部执行,要么全部不执行。

2020/11/10:

线程池:

1、线程池的好处

线程使应用能够更加充分合理地协调利用CPU、内存、网络、I/O等系统资源。

线程池的作用包括:

(1)利用线程池管理并复用、控制最大并发数等。

(2)实现任务线程队列缓存策略和拒绝机制。

(3)实现某些与时间相关的功能,如定时执行、周期执行等

(4)隔离线程环境

2020/11/11:

ThreadPoolExecutor的构造方法如下:

public ThreadPoolExecutor(
            int corePoolSize,
            int maximumPoolSize,
            long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory,
            RejectedExecutionHandler handler) {
        // maximumPoolSize必须大于或等于1也要大于或等于corePoolSize(第一处)
        if(corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0){
            throw new IllegalArgumentException();}
        // 第二处
        if(workQueue == null || threadFactory == null || handler == null){
            throw new NullPointerException();
        }
        // 其他代码 ...
    }      

第一个参数:corePoolSize表示常驻核心线程数。如果等于0,则任务执行完之后,没有任何请求进入时会销毁线程池的线程;如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。这个值的设置非常关键,设置过大会浪费资源,设置过小会导致线程频繁地创建或销毁。

第二个参数:maximumPoolSize表示线程池能容纳同时执行的最大线程数。从上方示例代码中的第一处看来,必须大于或者等于1。如果maximumPoolSize与corePoolSize相等,即是固定大小线程池。

第三个参数:keepAliveTime表示线程池中的线程空闲时间,当空闲时间达到keepAliveTime值时,线程会被销毁,直到只剩下corePoolSize个线程为止,避免浪费内存和句柄资源。在默认情况下,当线程池的线程数大于corePoolSize时,keepAliveTime才会起作用。但是当ThreadPoolExecutor的allowCoreThreadTImeOut变量设置为true时,核心线程超时后也会被回收。

第四个参数:TimeUnit 表示时间单位。keepAliveTime的时间单位通常是TimeUnit.SECONDS。

第五个参数:workQueue表示缓存队列。当请求的线程数大于corePoolSize时,线程进入BlockingQueue阻塞队列,BlockingQueue队列缓存达到上限后,如果还有新任务需要处理,那么线程池会创建新的线程,最大线程数为maximumPoolSize。

第六个参数:threadFactory 表示线程工厂。它用来生产一组相同任务的线程。线程池的命名是通过给这个factory增加组名前缀来实现的。在虚拟机栈分析时,就可以知道线程任务是由哪个线程工厂生产的。

第七个参数:handler表示执行拒绝策略的对象。当第五个参数workQueue的任务缓存区达到上限后,并且活动线程数大于maximumPoolSize的时候,线程池通过该策略处理请求,这是一种简单的限流保护。

友好的拒绝策略可以是如下三种:

(1)保存到数据库进行削峰填谷。在空闲时再提取出来执行。

(2)转向某个提示页面

(3)打印日志

1、Executor接口有且只有一个方法execute,通过参数传入待执行线程的对象

2、使用线程池要注意如下几点:

(1)合理设置各种参数,根据实际的业务场景来设置合理的工作线程数

(2)线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

(3)创建线程或线程池时请指定有意义的线程名称,方便出错时的回溯

线程池不允许使用Executors,而是通过ThreadPoolExecutor的方式创建,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险。

3、引用类型,对象在堆上创建之后所持有的引用其实是一种变量类型,引用之间可以通过赋值构成一条引用链

4、引用可分为:强引用,软引用,弱引用,虚引用

强引用:Strong Reference,最为常见。如new出来的对象。只要对象有强引用指向,并且GC Roots 可达,那么Java内存回收时,即使内存耗尽也不会回收该对象。

软引用:Soft reference,引用力度弱于“强引用”,是用在非必须对象的场景。在即将OOM之前,垃圾回收器会把这些软引用指向的对象加入回收范围,以获得更多的内存空间。主要用来缓存服务器中间计算结果及不需要保存的用户行为等。

弱引用: Weak Reference,引用较前两者更弱,用来描述非必须对象。如果弱引用指向的对象只存在弱引用这一条线路,则在下一次YGC时会被回收。由于YGC时间的不确定性,弱引用被回收也具有不确定性。弱引用主要用于指向某个易消失的对象,在强引用断开后,此引用不会劫持对象。调用WeakReference.get()可能返回null,注意空指针异常。

虚引用:Phantom Reference,是极弱的一种引用关系,为一个对象设置虚引用的唯一目的就是希望能在这个对象被回收时收到一个系统通知。

5、ThreadLocal

线程使用ThreadLocal有三个重要方法:

(1)set(): 如果没有set操作的ThreadLocal容易引起脏数据问题。

(2)get(): 始终没有get操作的ThreadLocal对象是没有意义的。

(3)remove(): 如果没有remove()操作,容易引起内存泄露。

ThreadLocal通常用于同一个线程内,跨类、跨方法传递数据

2020/11/15:

1、使用Thread Local和InheritableThreadLocal透传上下文时,要注意线程间的切换、异常传输时的处理,避免在传输过程中因处理不当而导致上下文丢失。

2、ThreadLocal可能会导致:脏数据、内存泄漏。解决这两个问题,需要在每次用完ThreadLoca时,必须要调用remove()方法清理。

JVM:

3、Java的类加载器是一个运行时的核心基础设施模块。主要在启动之初进行类的Load、Link和Init,即加载、链接、初始化

加载:Load阶段读取类文件产生的二进制流,并转化为特定的数据结构,初步校验cafe babe魔法数、常量池、文件长度、是否有父类等,然后创建对应的Java.lang.class示例

链接:link阶段包括验证、准备、解析三个步骤。验证是更详细的校验。比如final是否合规、类型是否正确、静态变量是否合理等;准备阶段是为静态变量分配内存,并设定默认值,解析类和方法确保类与类之间的相互引用正确性,完成内存结构布局。

初始化阶段:执行类构造器<clinit>方法,如果赋值运算是通过其他类的静态方法来完成的,那么马上会解析另外一个类,在虚拟机栈重执行完毕后通过返回值进行赋值。

4、类加载是一个将.class字节码文件实例化成Class对象并进行相关初始化的过程。在这个过程中,JVM会初始化继承树上还没有被初始化过的所有父类,并且会执行这个链路上所有未执行过得静态代码块、静态变量赋值语句等。某些类在使用时,也可以按需由类加载器进行加载。

5、内存布局

(1)Heap(堆区),是OOM故障最主要的发源地。

堆分成两大块:新生代和老年代

新生代=一个Eden区+2个Survivor区。绝大部分对象在Eden区生成,当Eden区装填满的时候,会触发Young Garbage Collection,即YGC。在Eden区实现清除策略,没有被引用的对象则直接回收,依然存活的对象会被移送到Survivor区。

如果Survivor区无法放下该对象,或者超大对象的阀值超过上限,则尝试在老年代中进行分配,如果老年代也无法放下,则会触发Full Garbage Collection,即FGC。如果依然无法放下,则抛出OOM

(2)Metaspace(元空间)

在JDK8版本中元空间前生Perm区已经被淘汰。Perm区中的所有内容中字符串常量移至堆内存,其他内容包括类元信息、字段、静态属性、方法、常量等都移至元空间内。

(3)JVM Stack(虚拟机栈)

栈帧在整个JVM体系中的地位颇高,包括局部变量表、操作栈、动态链接、方法返回地址等

a、局部变量表是存放方法参数和局部变量的区域

b、操作栈

c、动态链接

d、方法返回地址:方法执行时有两种退出情况:正常退出,异常退出。

方法退出的过程相当于弹出当前栈帧,退出可能有3种方式:

1)返回值压入上层调用栈

2)异常信息抛给能够处理的栈帧

3)pc计数器指向方法调用后的下一条指令

(4)Native Method Stacks(本地方法栈)

(5)程序计数器

最后:从线程共享的角度来看,堆和元空间是所有线程共享的,而虚拟机栈、本地方法栈、程序计数器是线程内部私有的

6、对象创建过程:

(1)、确认类元信息是否存在。当Java接收到new指令时,首先在metaspace内检查需要创建的类元信息是否存在。若不存在,那么在双亲委派模式下,使用当前类加载器以,ClassLoader+包名+类名为KEY进行查找对应的.class文件。如果没有找到则抛出ClassNotFoundException异常,如果找到生成对应class类对象。

(2)、分配对象内存。首先计算对象占用空间大小,如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小,接着在堆中划分一块内存给新对象。

(3)、设定默认值。成员变量的值都需要设置为默认值(即不同形式的0值)

(4)、设置对象头。设置新对象的hash码,GC信息,锁信息,对象所属的类元信息

(5)、执行init方法。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量。

2020/11/17:

1、垃圾回收,为了判断对象是否存活,JVM引入GC Roots。如果一个对象与GC Roots之间没有直接或者间接的引用关系,比如某个失去任何引用的对象,或者两个互相环岛循环引用的对象等。

2、垃圾回收算法:

(1)标记-清除算法,会带来大量的空间碎片,导致需要分配一个较大连续空间时容易触发FGC

(2) 标记-整理算法,可以避免产生空间碎片

(3)Mark- copy算法,堆内存空间分为较大的Eden和两块较小的Survivor,每次只使用Eden和Survivor区的一块。(作为主流的YGC算法进行新生代的垃圾回收)

3、垃圾回收器:Serial、CMS、G1三种

Serial主要用于YGC。

CMS回收器(Concurrent Mark Sweep Collector)是回收停顿的时间比较短、目前比较常用的垃圾回收器(采用标记-清除算法,因而产生大量的空间碎片)

G1(Garbage- First Garbage Collector),JDK7推出。采用“Mark- Copy”算法,有非常好的空间整合能力,不会产生大量的空间碎片。

异常:

4、throw和throws区别:

throw是方法内部抛出具体异常类对象的关键字,throws则用在方法的signature上,表示方法定义者可以通过此方法声明向上抛出的异常对象。

5、无论采用哪种方式处理异常,都严禁捕获异常后什么都不做或打印一行日志了事。如果在方法内部处理异常,需要根据不同的业务场景进行定制处理,如重试、回滚等操作。如果向上抛出异常。需要在异常对象中加入上下文参数、局部变量、运行环境等信息,有利于问题的排查,示例代码如下:

try{}catch(Exception e){

throw new DaoException("Sequence error, userId, userId="+userId, e);

}

6、Exception分为checked异常(受检异常)和unchecked异常(非受检异常)

checked异常是需要在代码中显示处理的异常,否则会编译出错

unchecked异常可分为3类:

(1)可预测异常,如Index Outof Bounds Exception、Null Pointer Exception,基于对代码性能和稳定性要求,此类异常不应该被产生或者抛出,而应提前做好边界检查,空指针判断等处理。显示地声明或者捕获此类异常会对程序的可读性和运行效率产生很大的影响。

(2)需捕捉异常(Caution Exception),如dubbo框架进行RPC调用时产生的远程服务超时异常,此类异常是客户端必须显示处理的异常,不能因服务端的异常导致客户端不可用,此时处理方案可以是重试或者降级处理等。

(3)可透出异常(Ignored Exception),主要是指框架或系统产生的切回自行处理的异常,而程序无需担心。

7、try代码块

如果没有进入try代码块,可能有三种情况:

(1)没有进入try代码块

(2)进入try代码块,但是代码运行中出现了死循环或者死锁状态

(3)进入try代码块,但是执行了System.exit()操作。

注意⚠️:

(1)finally是在return表达式运行之后执行的,此时将要return的结果已经被暂存起来,待finally代码块执行结束后再将之前暂存的结果返回。

(2)finally的职责不在于对变量进行赋值等操作,而是清理资源、释放连接、关闭管道流等操作,此时如果有异常也要做try-catch。

2020/11/18

1、异常,综合考虑,日志文件推荐保存15天,可以根据日志文件的重要程度、文件大小及磁盘空间再自行延长保存时间

2、5种不同级别的日志按照重要程度由低到高排序:

(1)DEBUG级别日志记录对调试程序有帮助

(2)INFO级别日志

(3)WARN级别日志

(4)ERROR级别日志

(5)FATAL级别日志

3、注意事项:

(1)日志打印使用占位符{}形式

(2)避免无效日志打印,生产环境禁止输出DEBUG日志且有选择地输出INFO日志

(3)区别对待错误日志,WARN、ERROR都是与错误有关的日志级别。ERROR级别只记录系统逻辑错误、异常或者违反重要的业务规则,其他错误都可以归为warn级别。

(4)保证记录内容完整(需要记录现场上下文信息与异常堆栈信息)

a: 记录异常时一定要输出异常堆栈,例如:logger.error("xxx"+e.getMessage(),e)

b: 日志中如果输出对象实例,要确保实例类重写了toString方法,否则只会输出对象的hashCode值,没有实际意义

4、记录日志时一定要思考3个问题:

(1)日志是否有人看

(2)看到这条日志能做什么

(3)能不能提升问题的排查效率

5、日志框架:分为3大部分,包括日志门面、日志适配器、日志库。利用门面设计模式,即Facade来进行解耦,使日志使用变得更加简单。

(1)日志门面:slf4j和commons- logging

(2)日志库:logback是最晚出现的,它与log4j出自同一个作者,是log4j的升级版本且本身就实现了slf4j的接口

(3)如果是新工程,推荐使用slf4j+logback模式。因为logback本身实现了slf4j接口,无需额外引入适配器。

6、private static final Logger logger = LoggerFactory.getLogger(Abc.class);

注意,logger被定义为static变量是因为这个logger与当前类绑定,避免每次都new一个新对象,造成资源浪费 ,甚至引发OutOfMemoryError问题。