Java-base

Integer数值==比较

Integer a = 1000,b=1000;
Integer c = 100,d=100;
System.out.println(a==b); // false
System.out.println(c==d); // true
自动装箱操作,将基本数据类型转换为Integer 对象,而Integer 中缓存了 -128到127 之间的数字。
Integer a = new Integer(1000);
int b = 1000;
Integer c = new Integer(10);
Integer d = new Integer(10);
System.out.println(a == b); // true
System.out.println(c == d); // false
自动拆箱操作,a对象拆箱,比对数值自然相等。而后面则是新建对象,没有使用缓存所以不想等。

进程、线程和协程

进程是操作系统调度和分配内存和时间片的最小独立单元。
进程状态:就绪、执行、阻塞。
进程间通信:管道、队列、信号量、套接字。

线程是进程管理的下属子进程,也是cpu调度的最小单位。
线程状态:新建、就绪、运行、阻塞、死亡。

协程也成微线程,它是一种比线程更加轻量级的存在,不由操作系统负责调度,完全由用户态管理也就是程序管理。
协程的优点:无需上下文切换。无需原子锁的锁定及同步开销。方便切换控制流。

进程和线程的区别:
调度:进程是拥有CPU资源的最小单位。而线程则是调度和分配的基本单位。
所属:进程拥有自己的资源空间,一个进程包含若干个线程,线程与CPU资源无关,多个线程共享同一个线程内的CPU资源。
切换:线程的切换比进程要快的多。

try-catch-finally

trycatchfinally机制的另一种实现方式trycatchresource机制。

先提条件是必须实现AutoCloseable接口。

final-finally-finalize区别:final是java关键字,用于修饰变量、方法、类。finally是try-catch-finally模块,表示在总是执行的代码部分。finalize表示在对象被回收时第一次被标记时可以执行的方法。

transient

transient 关键字的作⽤是:阻⽌实例中那些⽤此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和⽅法。

string

string:String 不是基本数据类型,而是引用数据类型。String 是 final 类型的,不可变,所以大多字符串为常量形式存在,如果存在 new String时,也是一个对象,一个常量。 比较字符串的值是否相同用 equals,比较字符串对象是否同一个用==。

String 的实现在jdk 1.8之前是char[] 数组,而在jdk 1.9 中变成了 byte[] 数组。

上述修改的原因在于char 占用两个字节存储,而byte仅占用一个字节。其中为了解决中文字符的问题,使用了coder属性作为编码字节的标识符,分为 LATIN1 与 UTF16,是不可变,不可重写的。同时还有一个属性放在了构造器中起到开关的作用,尝试压缩汉字到byte[] 数组中。

String、StringBuffer、StringBuilder 最大的不同是 String 不可变,后者可变。StringBuffer 是线程安全的,StringBuilder 线程不安全速度较快。

1、String 一旦创建不可变,如果修改即创建新的对象,StringBuffer 和 StringBuilder 可变,修改之后引用不变。

2、String 对象直接拼接效率高,但是如果执行的是间接拼接,效率很低,而 StringBuffer 和 StringBuilder 的效率更高,同时 StringBuilder 的效率高于 StringBuffer。

3、StringBuffer 的方法是线程安全的,StringBuilder 是线程不安全的,在考虑线程安全的情况下,应该使用 StringBuffer。

传递

值传递:在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

引用传递:”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向同一块内存地址,对形参的操作会影响实参的真实内容。

数据类型

byte:8位,最大存储数据量是255,存放的数据范围是-128~127之间。short:16位,最大数据存储量是65536,数据范围是-32768~32767之间。int:32位,最大数据存储容量是2的32次方减1,数据范围是负的2的31次方到正的2的31次方减1。long:64位,最大数据存储容量是2的64次方减1,数据范围为负的2的63次方到正的2的63次方减1。 float:32位,数据范围在3.4e-45~1.4e38,直接赋值时必须在数字后加上f或F。double:64位,数据范围在4.9e-324~1.8e308,赋值时可以加d或D也可以不加。boolean:只有true和false两个取值。char:16位,存储Unicode码,用单引号赋值。

基本数据类型和引用类型的区别主要在于基本数据类型是分配在栈上的,而引用类型是分配在堆上的。其中引用类型中“引用”是存储在有序的内存栈上的(引用),而对象本身的值存储在内存堆上的(对象)。

拷贝

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。”里面的对象“会在原来的对象和它的副本之间共享。即浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象

深拷贝:深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。即深拷贝把要复制的对象所引用的对象都复制了一遍。

线程

线程的创建方式: 1、继承Thread。然后实现run() 方法即可。调用start() 启动线程。 2、实现Runnable接口。然后实现run() 方法即可。调用start() 启动线程。 3、实现Clllable接口,并结合 FutureTask 实现。 4、线程池创建线程。 5、本质上创建线程就只有一种方式,就是构造一个 Thread 类,然后调用thread 类的start() 间接去调用run() 方法。

Thread类中run()和start()方法的区别如下: run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用,就相当于当作普通的方法的方式调用,并没有创建线程,程序中依旧只有一个主线程。 start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程,其中调用start() 后的这个线程处于就绪状态,当得到CPU 的时间片后就会执行其中的run() 方法。

Thread 和Runnable 实现线程的区别: 1、Runnable 实现接口可以避免Java 的单继承问题。2、实现接口更加符合面向对象的思想。3、继承Thread 会导致线程对象与线程任务耦合在一起,而Runnable 接口就没有这种情况。

虚拟线程

JDK19 新特性 虚拟线程

虚拟线程具有和 Go 语言的 goroutines 和 Erlang 语言的进程类似的实现方式,它们是用户模式(user-mode)线程的一种形式。

在过去 Java 中常常使用线程池来进行平台线程的共享以提高对计算机硬件的使用率,但在这种异步风格中,请求的每个阶段可能在不同的线程上执行,每个线程以交错的方式运行属于不同请求的阶段,与 Java 平台的设计不协调从而导致:
堆栈跟踪不提供可用的上下文
调试器不能单步执行请求处理逻辑
分析器不能将操作的成本与其调用方关联。

而虚拟线程既保持与平台的设计兼容,同时又能最佳地利用硬件从而不影响可伸缩性。虚拟线程是由 JDK 而非操作系统提供的线程的轻量级实现:
虚拟线程是没有绑定到特定操作系统线程的线程。
平台线程是以传统方式实现的线程,作为围绕操作系统线程的简单包装。

虚拟线程的接口和普通线程一样,唯一区别在于创建虚拟线程只能通过特定方法。
// 传入Runnable实例并立刻运行:
Thread vt = Thread.startVirtualThread(() -> {
System.out.println("Start virtual thread...");
Thread.sleep(10);
System.out.println("End virtual thread.");
});

反射

Java中可以实现的反射的反射共有两种,一种是JDK动态代理,另一种是CGLIB动态代理。
其中最大的差别在于JDK动态代理只能反射实现接口的类,而CGLIB动态代理则没有这种限制。
究其原因在于JDK的动态代理自身实现使用了代理模式,proxy类只能允许做一些额外的拦截处理,实际处理还是依旧在原始类的。如果允许动态代理一个类,那么代理对象也会继承原始对象的字段,而这些字段是没有使用的,这是对内存的浪费,另外还有就是如果原始对象存在final 变量,那么动态生成的类是无法覆盖到的。

泛型

泛型的本质就是类型参数化。
编译时使用通配符占位,而到运行时时可以传递不同的类型来实现复用。

Java三大特征

Java三大特征:
1、封装。一种将抽象性函式接口的实现细节部份包装、隐藏起来的方法。
2、继承。子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法。
3、多态。多态是同一个行为具有多个不同表现形式或形态的能力。

Java应用程序CPU100

Java应用程序CPU100检查步骤:
1、通过使用 top 命令查看cpu 最高的PID。 topas (可以使用 ps -aux | grep PID来详细查看这个哪个项目)
2、top -H -p PID 查看CPU 使用较高的线程tid。
3、将线程 tid 转化为 16 进制格式。 printf "%x\f" PID
4、通过jstack 查看当前 tid 执行的内容。jstack PID | grep tid -A 50。
或者也可以使用阿里的arthas快速定位。

分析解决OOM问题

OOM:全称Out Of Memory,翻译为中文就是“内存使用完了”,来源于java.lang.OutOfMemoryError。官方释义如下,当JVM 中因为没有足够的内存来为对象分配空间并且垃圾回收器也没有空间可回收时,就会抛出这个异常(OutOfMemoryError)。
产生OOM 的原因:
1、分配的内存不足。虚拟机本身可使用的内存空间不足。
2、应用用的内存太多,并且使用完成后没有释放内存,造成浪费。此时就会造成内存泄漏或者内存溢出。
Java 中的内存泄漏不同于 C++ 中忘记delete ,更多的是逻辑上的原因导致内存泄漏。

内存泄漏:申请使用完的内存没有释放,导致虚拟机不能再使用该内存,此时这一段内存就会泄漏,因为申请者不再使用,而虚拟机也不能分配给别人使用。
内存溢出:申请的内存超出了虚拟机提供的内存大小,称之为内存溢出。

常见的OOM 情况如下:
1、Java heap space:java堆内存泄漏,此种情况出现最常见,一般原因在于内存泄漏或者堆的大小设置不合理所引起。
对于内存泄漏,需要通过查看内存监控软件查找程序中的内存泄漏代码,而堆大小则可以通过虚拟机参数-Xms、-Xmx 等修改。
2、PermGen space:java永久代溢出,即方法区溢出,一般出现于大量Class 或者jsp 页面,或者采用cglib 等反射机制的情况,因为上述情况会产生大量的Class 信息存储于方法区。
这种情况可以通过更改方法去的大小来解决,使用-XX:PermSize=64M 、-XX:MaxPermSize=256M 的形式修改。另外过多的常量(尤其是字符串)也会导致方法区溢出。
3、StackOverflowError:不会抛出OOM Error,但也是常见的Java 内存溢出。Java 虚拟机栈溢出,一般是由于程序中存在的死循环或者深度递归造成的,栈大小设置太小也会导致出现这种溢出。
可以通过设置好虚拟机参数-Xss 来设置栈的大小。

Java OOM 分析 - heapdump:
dump 堆的内存镜像,可以采用如下两种方式:
1、设置 JVM 参数:-XX:HeapDumpOutOfMemoryError,设定当发生OOM 时自动 dump 出堆的信息,不过设置该参数需要 JDK5 以上。
2、使用 JDK 自带的命令,jmap -dump:format=b,file=heap.bin PID ,其中PID 通过jps 获取。
dump 堆内存信息后,需要查看dump 出的文件中的OOM 信息,常用的工具如下:
1、mat:基于eclipse RCP 的工具。
2、jhat:JDK 知道的java heap analyze tool,可以将堆中的对象以 html 的形式进行展示,包括对象的大小、数量等,并支持对象查询语言 OQL,分析相关的应用后,查看本地的7000 端口查看分析结果。

因为JVM规范没有对dump出的文件的格式进行定义,所以不同的虚拟机产生的dump文件并不是一样的。因此在针对不同的虚拟机输出的dump 文件需要采用不同的分析工具。
针对JVM运行时的分析与诊断,则需要掌握分析基本方法,针对具体情况,运用虚拟机的原理,具体分析。

Object的方法分析

1、Object()。无参构造函数。
2、registerNatives()。注册本地函数,即将一个本地方法与另外一个C 函数进行绑定的操作,可以实现程序的动态加载和动态卸载。
3、clone()。返回原有对象的引用,使用的是浅拷贝,其中具体实现方法由之类继承Cloneable 接口实现,如果没有实现接口而直接调用clone() 方法,就会抛出CloneNoSupportedException 异常。
4、getClass()。返回该对象运行时的类对象。
5、equals()。判断两个对象是否相等。值比较和引用比较。
6、hashCode()。返回一个整数值表示该对象的哈希码值。如果两个对象相等(euqals),则两个对象的哈希码值一定相同。反之,哈希码值相同而这两个对象不一定相同。
7、toString()。返回该对象的字符串表示。
8、wait()。调用此方法所在的线程等待,直到其他线程上调用此方法的notify() 或者notifyAll() 方法。
9、notify()。唤醒在此对象监视器上等待的单个线程。
10、notifyAll()。唤醒在此对象监视器上等待的全部线程。这三个方法均只能在同步代码块中使用。
11、finalize()。JVM 准备回收该对象所占的内存空间进行垃圾回收时,该方法将会被调用。