备战大半年秋招资料分享-Java基础篇

备战秋招大半年,目前已经拿到offer上岸,将半年来的笔记分享给大家。更多笔记涵盖 MYSQL、Elasticsearch、Kafka、设计模式、JVM、Java语言基础、集合原理、并发技术
需要的同学可以加我vx:uukiinternet 私发给你们哦。


Assert 断言
断言一般用于程序调试时候适用,前提需要主动打开断言功能(默认关闭),断言条件不满足则抛出异常。
注意:断言不可用于正式环境条件判断,因为代码发布的时候断言不会被包括在内。仅用于调试程序使用。
static {
// high value may be configured by property
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

类加载顺序
父类静态属性+静态代码块(static)> 子类静态属性+静态代码块(static)> 父类构造块(直接用{}包括的代码片段) > 父类构造方法 > 子类构造块 > 子类构造方法
存在继承的情况下,初始化顺序为:
* 父类(静态变量、静态语句块)
* 子类(静态变量、静态语句块)
* 父类(实例变量、普通语句块)
* 父类(构造函数)
* 子类(实例变量、普通语句块)
* 子类(构造函数)

IntegerCache(Float、Double没有缓存池)
JVM启动时候初始化,会初始化一个大小为high-(-low)+1的cache数组,存放着从-128到指定的hign值(默认127),在发生自动拆箱的时候调用Integer.valueOf(i),如果i属于区间内,则返回buffer中对应的值,否则new Integer(i)。
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);

字符串常量池(JVM)
在JDK1.6之后(1.7),字符串常量池从方法区移到Java堆中,因此不受方法区大小限制。两种字符串创建方式:
1、通过赋值创建:先去检查字符串常量池,是否有该字符串,若无则在常量池创建一个对象然后返回引用;若有则直接返回常量池对象引用。(不会在堆内存创建对象)
2、通过new方式创建:首先检查字符串常量池是否存在引号内容对象,若无则在常量池创建,然后在堆内存创建一个对象。

String.intern:如果常量池存在这个对象,则不作变化;若不存在(首次出现),则常量池里记录着该对象堆内存的引用,下次请求常量池相同对象时候直接返回记录的对内存对象的引用
public static void main(String[] args) {
String s = new String("1");  // 常量池创建"1",堆内存创建s
s.intern(); // 常量池已经有 1
String s2 = "1"; // 引用的常量池的1
System.out.println(s == s2); // false
String s3 = new String("1") + new String("1"); // 常量池创建 1,堆内存创建11
s3.intern(); // 常量池没有11,常量池记录着s3(11)的引用,供下次访问
String s4 = "11"; // 返回常量池11对应的引用
System.out.println(s3 == s4); // true
}

JVM运行时数据区域
JVM运行时数据区域主要分为两类,一类是线程共享区域:堆、方法区,第二类是线程独立区域(每个线程都有):程序计数器、虚拟机栈、本地方法栈。

程序计数器:用于存储当前线程运行的字节码的地址;字节码解释器就是通过改变这个计数器的值来取下一条所需要执行的字节码。

虚拟机栈:用于存储多个栈帧用,每个栈帧用于存储局部变量表(局部变量表直接存储已知的基本数据类型int、short等等,以及对象类型的引用)、操作数、方法接口等信息,方法的调用到结束就对应着栈帧在虚拟机栈里入栈以及出栈。

本地方法栈:类似虚拟机栈,只不过调用的是本地方法(比如C++函数库等)。

Java堆:大多数对象、数组内存空间都在此分配。
方法区:用于存储已加载的类信息(其中包括运行时常量池),常量、静态变量。其中运行时常量池里的常量除了包括编译期间就生成好的,也可以是运行时产生的比如String.intern()。

New对象过程
1、首先检查类是否已经被加载、解析、初始化。若无,则先执行类加载工作,然后进行堆内存分配。
2、堆内存分配牵扯到堆内存是否“规整”(是否连续)取决于垃圾回收器是否有压缩过程(CMS使用“空闲列表”,Serial使用碰撞指针)。如果堆内存规整,则使用“指针碰撞”(挪动一段与对象大小相等的内存距离),若不规整,则JVM维护一个“空闲列表”通过查找列表找出合适大小的内存空间进行分配。由于内存分配是一个非常频繁的操作,因此考虑并发问题。JVM有两种方案解决:a) 采用失败重试的方式保证更新操作的原子性 b) 在Java堆中预先为每个线程分配一个本地线程缓冲(TLAB),每个线程都从TLAB上分配,用完需要向堆申请分配的时候才同步锁定。
3、内存分配完成后,对内存空间初始化零值。
4、虚拟机对对象一些必要信息设定(对象属于哪个类实例,元数据信息等)
5、对象init方法

操作系统为每个进程分配的内存有限(比如2G),减去最大堆内存容量,最大方法区容量,剩下就供线程瓜分。每个线程栈大小越大,可以生成的线程数量越小,达到上限会抛出OOM。解决方法:增大操作系统分配进程内存、减小堆内存等等。

函数参数传递:值传递。对于基础数据类型,等于拷贝值;对于对象类型,等于复制一个对象的引用给参数,可以访问到对象的属性,但是更改参数引用对象对原对象无影响。

Final:final关键字修饰的变量引用地址不能变(不能引用别的对象,引用的对象本身可以变);final修饰的类不能被继承;final修饰的函数不能被重写。

内部类
1、普通内部类:内部类存在的场景是仅与其外部类有数据访问(字段、函数等),与其他类没有关联,存在的目的是在隔离与其他类访问前提下简化外部类与其数据访问(把内部类成员设置成public,省去getter、setter,内部类也可以直接访问外部类字段、函数)
2、匿名内部类:常用于监听事件、线程run方法实现。

HashCode:在实现对象equals方法的时候,应该同时实现hashCode方法,因为在一些集合类HashSet、HashMap是根据hashCode的值来判断对象是否相等。因此如果认为定义两个对象相等的话,其hashCode也必须相等。但是HashCode相等的两个对象并不一定相等。String类的hashCode计算就把每一个字符放到一个31进制数的每个位上,计算31进制数的值作为hash值。
// s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;


for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}

Clone:Object类的clone()为protected修饰,不能直接从类外调用。需要实现Clonable接口以及重写Clone方法。Object的Clone方法默认是浅拷贝(引用类型变量引用同一个对象),深拷贝(引用类型变量引用不同对象)需要自己实现。不建议使用clone方法来拷贝对象,避免出现不必要差错,建议使用拷贝构造函数或者拷贝工厂
public CloneConstructorExample(CloneConstructorExample original) {
arr = new int[original.arr.length];
for (int i = 0; i < original.arr.length; i++) {
arr[i] = original.arr[i];
}
}

访问修饰符:private、public、protected、不加就表示包内可见(可以修饰类、函数、变量)

重载:方法名相同,方法签名不同(参数类型、个数、顺序至少一个不同)注意:只有返回值不同,其他相同不算重载。

反射:在编码、编译期不需要知道对象类型,在运行时确定对象,可以获取类信息、以及调用对应的方法。一般用于框架开发使用
尽管反射非常强大,但也不能滥用。如果一个功能可以不用反射完成,那么最好就不用。在我们使用反射技术时,下面几条内容应该牢记于心。
* 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
* 安全限制 :使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。
* 内部暴露 :由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

泛型:

Transient:(对象实现Serializable接口)被transient修饰的对象属性不会被序列化到磁盘上。一般适用于只希望在内存里读取,不希望记录保存的属性,比如银行卡卡号之类的。注意,静态变量存储在方法区,不会被序列化。

序列化:1. 实现Serializable接口  2. 实现Externalizable接口(需要重写writeExternal和readExternal方法,决定哪些属性可以被序列化,即便是transient修饰的)

运算符:| -位或(二进制数每一位,都是0出0,否则出1) &-与 (二进制对应每一位,都是1出1,否则出0)

Volatile:JVM在运行的时候变量都是存储在主存里,线程也有自己的工作内存。当线程要使用某个变量的时候,先从主存复制一份变量到工作内存里,然后进行操作,注意写操作并不保证一定会立马回写主存,所以在并发编程的环境下,并不能保证变量的修改立马对其他线程可见(可见性问题)。Volatile关键字就是解决并发编程下可见性问题的。Volatile变量禁止指令重排,当一个共享变量被volatile修饰,且被某一个线程修改后,会强制把值回写主存,且其他线程工作内存里该变量的地址失效,使得其他线程需要重新从主存读取变量,从而保证了可见性。但是要注意:volatile并不能保证原子性!比如多个线程读取变量进行自增操作的时候。https://www.cnblogs.com/dolphin0520/p/3920373.html
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1)  //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc); // 结果是 < 100000的数
}
}

CAS:compareAndSwap,接收三个参数compareAndSwap(i, j, k),判断i与j的值是否相等,相等的话将k值写入i,返回true,否则返回false。常用于并发编程的一种手段/工具。该操作由操作系统来保证原子性。ConcurrentHashmap.initTable里示例(其中包含了双重检测,也是并发编程手段之一):
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 双重检测
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}

自定义类加载器主要是继承ClassLoader类并实现findClass方法,在findClass方法里读取Class文件字节流,然后通过defineClass返回一个Class对象,然后通过getDeclaredMethod调用里面的函数。
protected Class<?> findClass(String name) throws ClassNotFoundException {


File f = new File("G:/itworkspace/WangWanliang.class");
//        String myPath = "file://G:/itworkspace/basicJava/out/production/basicJava/classLoader/WangWanliang.class";
//        System.out.println(myPath);
try{
byte[] bytes = getClassBytes(f);
//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
return c;
}
catch (Exception e)
{
e.printStackTrace();
}
return super.findClass(name);
}

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
MyClassLoader mcl = new MyClassLoader();
Class clz = mcl.findClass("executor.WangWanliang") ;
Object obj = clz.newInstance();
Method helloMethod = clz.getDeclaredMethod("hello", null);
helloMethod.invoke(obj);

System.out.println(obj.getClass().getClassLoader());//打印出我们的自定义类加载器
WangWanliang wwl = new WangWanliang();
System.out.println(wwl.getClass().getClassLoader());
}

重写

自定义类加载器使用场景:
1、加密编码、解密编码Class文件。在加载时候解密decode
2、加载不同版本的类库。因为类加载器和类名唯一确定一个类对象,使用不同类加载加载不同版本的类库可以使用同名不同对象。

Java中创建对象的四种方式
1.用new语句创建对象,这是最常见的创建对象的方法。
2.运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.调用对象的clone()方法。
4.运用反序列化手段,调用java.io.ObjectInputStream对象的 readObject()方法。

Tips:使用JProfiler进行内存泄露分析,找到占用最多内存的对象,分析对象必要性,是否存在是内存泄露,若是,通过查找GC Root引用链找出没有被垃圾回收的原因。


#互联网求职##Java##学习路径##字节跳动##腾讯#
全部评论
实用!!已加微信求通过✅
点赞 回复
分享
发布于 2021-09-24 08:54
博主,继续分享!爱了爱了
点赞 回复
分享
发布于 2021-09-24 09:26
乐元素
校招火热招聘中
官网直投
已加好友,求通过
点赞 回复
分享
发布于 2021-09-24 09:50
借楼发个招聘,富途,港美股互联网券商, 内推链接:富途校园招聘 (futunn.com),内推码:NTAFTD0 内推链接如下 手机版复制到微信打开(网页版可以直接打开) app.mokahr.com/recommendation-apply/futu5/1699?sharePageId=821019&recommendCode=NTAFTD0&codeType=1#/recommendation/page/821019
点赞 回复
分享
发布于 2021-10-01 20:17
感谢
点赞 回复
分享
发布于 2021-10-06 07:34
如果加v没通过的小伙伴可能是忘记看了,请求过期了,可以再起发一下请求哦!
点赞 回复
分享
发布于 2021-11-25 10:56

相关推荐

5 42 评论
分享
牛客网
牛客企业服务