文章目录
- ---------------------------------java部分难点-----------------------------------------
- 1 .抽象类和接口
- 2 .内部类、静态内部类、局部内部类、匿名内部类
- 3 .AtomicStampedReference类
- 4.复制算法的问题?
- 5.String的“+”符号的理解
- 6.JIT简单理解
- 7.split 分割 字符串(分隔符如:* ^ : | , .) 及注意点
- ---------------------------------java基础------------------------------------------
- 1.一次编译,多次运行
- 2.JVM
- 3.JVM内存区域
- ---------------------------------java调优------------------------------------------
- 1.java占用cpu过高怎么排查?
- 2.OOM的排查?
---------------------------------java部分难点-----------------------------------------
1 .抽象类和接口
抽象类:体现的是一种模板的思想
接口:体现的是一种规范的思想
相同点:
- 1:两者都不能实例化
- 2:一个类实现了某个接口或者继承了某个抽象类,必须对其中所有的抽象方法全部实现,否则仍然需要声明为抽象类。
- 3:都可以作为引用,多态的思想。
不同点:
- 1.方法:
抽象类:可以有构造函数、普通方法、静态方法;
包含抽象方法的一定是抽象类,但是抽象类不一定含有抽象方法;
接口:只能有public abstart方法,不能含有普通方法,1.8之后可以有default方法并且可以有静态方法。
public interface Person{
public static final int a=10;
//JDK1.8
default void sayHello(){
System.out.println("Hello World");
}
public void say();
}
-
2.访问权限
抽象类可以是public、protected、default等权限,而接口只能是public static final?
为什么?
public:接口可以被不同包的不同类实现,是公有的,
static:一个类继承多个接口时,即使存在同名变量也不会混淆。如果每个接口都含有一个名为a的成员变量,那么在该类中可以通过 接口1.a, 接口2.a, 接口3.a来调用a,
final:该变量不可被修改。如果一个接口被多个类实现时,在类1中修改了该变量的值,那么其他类中该变量的值也会变化,要防止该情况的发生。 -
3.继承/实现
抽象类单继承、接口可以被多实现。
2 .内部类、静态内部类、局部内部类、匿名内部类
-
内部类:
对象的创建需要依赖父对象才能创建;
可以访问类的所有成员;
不能有静态方法,因为其是懒加载的,所以如果直接用类名访问,会报错。class Circle { private double radius = 0; public static int count =1; public Circle(double radius) { this.radius = radius; } class Draw { //内部类 public void drawSahpe() { System.out.println(radius); //外部类的private成员 System.out.println(count); //外部类的静态成员 } } public static void main(String[] args) { Circle outter = new Circle(); Circle.Draw inner = outter.new Draw(); //必须通过r对象来创建 } }
-
静态内部类:
对象的创建不需要有外部类的对象;
只能访问父类的静态成员;
内部可以有静态、非静态方法。class Circle { private double radius = 0; public static int count =1; public Circle(double radius) { this.radius = radius; } static class Draw { //内部类 public void drawSahpe() { /// System.out.println(radius); //外部类的private成员 System.out.println(count); //外部类的静态成员 } } public static void main(String[] args) { Circle.Draw inner = new Circle.Draw(); } }
-
局部内部类:
方法中声明一个类;
直接可以访问外部类的所有成员,同非静态内部类;
方法中的变量必须是final的,因为如果局部内部类对象引用了该变量,而该方法在虚拟机栈弹出(一个栈帧),所以会清除,而常量会保持在堆中(常量池),不会随着栈的方法的弹出而销毁。class People{ public People() { } } class Man{ public Man(){ } public People getWoman(){ final int x = 5; class Woman extends People{ //局部内部类 int age =x; } return new Woman(); } }
-
匿名内部类
匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。interface Person(){ //可以有多个方法 void play(); } class Test(){ public static void main(){ //匿名内部类,创建匿名对象 Person p = new Person(){ @override public void play(){ System.out.println("123"); } //函数式接口 Person p = ()-> System.out.println("123"); } }
3 .AtomicStampedReference类
-
用于解决ABA问题
-
代码结构:
//构造方法, 传入引用和戳 public AtomicStampedReference(V initialRef, int initialStamp) //返回引用 public V getReference() //返回版本戳 public int getStamp() //如果当前引用 等于 预期值并且 当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存 public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) //如果当前引用 等于 预期引用, 将更新新的版本戳到内存 public boolean attemptStamp(V expectedReference, int newStamp) //设置当前引用的新引用和版本戳 public void set(V newReference, int newStamp)
-
原理:在AtomicInteger的基础上增加了一个版本号,用来进行进一步的标识,当然整体是由乐观锁操作CAS实现的,然后又加了一个版本号的判断。
4.复制算法的问题?
- 仅针对新生代效果好,浪费内存(只能用一半);
- 转移存活对象时会导致用户线程无法定位引用对象。(这点之前没有想到)
5.String的“+”符号的理解
-
源码
String s1 = "ouyangjun"; String s2 = "p812438109"; System.out.println(s1 + s2); // ouyangjunp812438109
-
反编译
System.out.println(new StringBuilder(String.valueOf(s1)).append(s2).toString());
-
结论:
编译时,先new一个StringBuilder对象,再把参数append到对象中,最后再toString。
在用"+"号拼接时,不管是什么方式拼接,都是会不断new StringBuilder对象。如循环次数越多,该拼接方式性能会越低,最终可能导致内存溢出等问题。现在程序中都不建议用该方式拼接字符串了、 -
“+”原本是运算符,为什么可以用来拼接字符串?
运算符重载。(这里是有争议的,Java本身是不支持运算符重载的,String的+操作实际上String类作者设计的语法糖。还可以追问为啥Java不给支持运算符重载?我猜是性能问题)
参考链接:https://blog.csdn.net/p812438109/article/details/103107624 -
语法糖
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
参考链接:https://www.cnblogs.com/qingshanli/p/9375040.html
6.JIT简单理解
即时编译器:对象不一定会分配至堆上
- 逃逸分析:
当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
public static StringBuffer craeteStringBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb;
}
但是如果这样的话,就不会逃逸出去
public static String createStringBuffer(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
所以此时编译器会有优化:
将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
而栈分配的下一步会是:标量替换
static void allocate() {
MyObject myObject = new MyObject(2019, 2019.0);
}
替换
static void allocate() {
int a = 2019;
double b = 2019.0;
}
总:1.对象不会逃逸出方法 2.然后进行标量替换 完成栈分配!!!
7.split 分割 字符串(分隔符如:* ^ : | , .) 及注意点
-
split表达式,其实就是一个正则表达式。【 * ^ | 】等符号在正则表达式中属于一种有特殊含义的字符,如果使用此种字符作为分隔符,必须使用转义符即【 \ 】加以转义。
String address=”上海|上海市|闵行区|吴中路”; String[] splitAddress=address.split(“\\|”); //如果以竖线为分隔符,则split的时候需要加上两个斜杠【\\】进行转义 System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]); String address=”上海*上海市*闵行区*吴中路”; String[] splitAddress=address.split(“\\*”); System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]); String address=”上海:上海市:闵行区:吴中路”; String[] splitAddress=address.split(“\\:”); System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]); String address=”上海.上海市.闵行区.吴中路”; String[] splitAddress=address.split(“\\.”); System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]); String address=”上海^上海市^闵行区^吴中路”; String[] splitAddress=address.split(“\\^”); System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]); String address=”上海@上海市@闵行区@吴中路”; String[] splitAddress=address.split(“@”); System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]); String address=”上海,上海市,闵行区,吴中路”; String[] splitAddress=address.split(“,”); System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]);
-
如果使用多个分隔符则需要借助 | 符号,但需要转义符的仍然要加上分隔符进行处理。
String address=”上海^上海市@闵行区#吴中路”; String[] splitAddress=address.split(“\\^|@|#”); System.out.println(splitAddress[0]+splitAddress[1]+splitAddress[2]+splitAddress[3]);
参考文章:
https://blog.csdn.net/w372426096/article/details/80333657
https://segmentfault.com/a/1190000022778791
https://blog.csdn.net/u010002184/article/details/81347559
https://www.cnblogs.com/latter/p/5665015.html
https://www.cnblogs.com/that-jay/p/13280995.html
---------------------------------java基础------------------------------------------
1.一次编译,多次运行
- javac hello.java 生成.class字节码文件
- 加载到JVM中
- 1.执行引擎进行解释执行,编译成机器码
- 2.执行引擎进行JIT(编译执行),编译成机器码
- 3.上面用哪个,看虚拟机的心情
- 4.解释和编译共存:
- 解释执行有个缺点就是每次都要解释字节码然后才生成机器码让计算机执行,解释的过程会占用很多的时间。于是JVM中诞生了编译器,编译器可以通过热点代码探测技术,找到运行次数最多的代码,把这些代码及时编译为机器码,在下次调用这些代码时跳过解释的步骤,直接执行编译好的机器码,以达到加速运行时间的目的。
- 为什么跨平台?
- 不同的平台的解释器是不同,但是JVM实现是相同的。
2.JVM
- 运行时区域:
- 公有:堆、方法区
- 私有:虚拟机栈、本地方法栈、程序计数器
- 执行引擎:
- JIT(即时编译器)
- GC
- 解释器
- 本地库接口(JNI)
3.JVM内存区域
- 堆
- 99%的垃圾回收;new的对象会存在这;新生代+老年代;静态变量+字符串常量池+常量池(运行时常量池)(原来在方法区,现在在堆中)
- -Xms -Xmx
- OOM:
- 设置参数:dumpOnOutOfMemory;jConsole;jmap 生成dump文件
- 分析对象快照:jhat、map两种工具进行分析
- 方法区
- 1%的垃圾回收:类的卸载
- 由永久代到了元空间,也会出现OOM,但是在元空间使用的是直接存储,所以几率降低了。
- OOM主要是加载的类过多
- 直接内存
- 不属于运行时区域,但也会频繁使用,引入NIO避免java堆和Native堆之间来回复制数据。
- 程序计数器
- 唯一一个不会OOM的,其两点作用
- 1是对于线程的顺序执行;2是线程切换时的上下文保护
- java虚拟机栈
- -Xss
- 主要是一个个的栈帧(方法),包含局部变量表、操作数、动态链接、方法出口
- 会出现栈溢出也会出现OOM,在刚开始无法申请到空间的时候。
- java本地方法栈
- 同java虚拟机栈
---------------------------------java调优------------------------------------------
1.java占用cpu过高怎么排查?
- top,查看进程pid
- top -Hp pid 找到线程(99%的占用量)
- jstack 进程id>thread.log
- 分析thread.log文件,查看每个线程的状态、名称等等,找到上面的线程进行分析哪行代码消耗cpu。
2.OOM的排查?
- 堆,可以设置参数 -Xms -Xmx来设置大小
- 方法区,可以设置参数 -XX:PermSize =64m -XX:MaxPermSize = 256m 来设置大小
- 元空间,可以设置参数 -XX:MetaspaceSize
- 栈溢出 ,可以设置参数 -Xss
- 分析:
1)生成dump文件(对象内存快照)
-XX:+HeapDumpOnOutOfMemoryError 生成dump文件
jmap -dump:fotmat=b,file = -/heap.hprof 进程号
Jconsole工具生成dump文件
2)分析dump文件
jhat工具
mat工具