JRE:运行java字节码的环境。它是运行已编译java程序所需要的所有内容的集合,主要包括java虚拟机JVM和java基础类库(class library)
JVM:是Java Virtual Machine的缩写,即Java虚拟机。它是一个虚构的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现。JVM的作用是在不同的平台上提供一个统一的运行环境,使得Java程序能够在任何安装了JVM的设备上运行,而不需要针对每个平台进行修改
JDK:提供给开发者使用的工具,它包含了JRE,同时还包含了编译java、源码的编译器javac以及一些其它工具,如javadoc(文档注释工具)、jdb(调试器)、jconsole(基本JMX的可视化监控工具)、javap(反编译工具)等等。
JMM:java内存模型
JMM就是java内存模型,因为在不同的CPU厂商下,CPU的实现机制都有一些不同,在内存和一些指令上都会存在一些差异。所以JMM就是为了屏蔽掉硬件和操作系统带来的差异,放Java程序可以在不同硬件和操作系统下,实现并发编程的原子性、可见性、禁止指令重排。简单地说,就是CPU内存和JVM内存之间的一个规则,这个规范可以将JVM的字节码指令CPU能够为CPU能够识别的指令。
Doug Lea工作站对内存模型的描述中可以看到JMM帮我们做了哪些事情,其中重要的是基于三大特性(原子性、可见性、禁止指令重排),对应不同的CPU做不同的实现。
https://gee.cs.oswego.edu/dl/jmm/cookbook.html
面向对象
对象的加载过程:
1、检查加载:首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用(符号引用:符号引用以一组符号来描述所引用的目标),并且检查类是否已经被加载、解析和初始化过。
2、分配内存
接下来虚拟机将为新生对象分配内存。为对象分配空间的任务等同于把一块确定大小的内存从java堆中划分出来。
分配方式:指针碰撞,空闲列表。解决并发安全问题:CAS失败重试机制,本地线程分配 缓冲(ThreadLocal Allocation Buffer,TLAB)。指针碰撞:如果Java堆中内存是绝对规整的,所有用过的内存都放在一边,空闲的内存存放在另一边,中间放一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离,这就是指针碰撞。空闲列表:如果Java堆中的内存并不是规整的,已使用的内存和空闲的内存相互交错,那就没有办法简单地指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。选择哪种分配方式由Java堆是否规则决定,而Java堆是否规整又由采用的垃圾收集器是否带有压缩整理功能决定。如果是Serial、ParNew等带有压缩的整理的垃圾回收器的话,系统采用的是指针碰撞,既简单又高效。如果是使用CMS这种不带压缩(整理)的垃圾回收器的话,理论上只能采用较复杂的空闲列表。
3、内存空间初始化
不是构造方法,内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。这一步操作保证了对象的实例字段在java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4、设置,接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息(Java classes在Java hotspot VM内部表示为类元数据)、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。
5、对象初始化。在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从java程序的视角来看,对象创建才刚刚开始,所有的字段都还为零值。所以,一般来说,执行new指令之后会接着把对象按照程序员的意愿进行初始化(构造方法),这样一个真正可用的对象才算真正产生出来。
异常
java的异常处理会影响我们程序的性能,创建一个异常非常慢,抛出一个异常又得消耗1-5ms
的时间,当一个异常在应用程序的多个层级之间进行传递时,会拖垮我们整个应用程序。
仅在异常情况下使用异常,在可恢复的异常情况下使用异常,心得使用异常有利于java开发,但是在应用中最好不要捕获太多的调用栈,因为在很多情况下都不需要打印调用栈知道哪里出错了。
使用try-with-resources是JDK1.7中提供的一个异常处理机制,它能够很容易地关闭try-catch语句中的资源。所谓的资源是指在程序结束后,必须关闭的对象。try-with-resources语句确保了每个资源在语句结束时关闭。所有实现了java.lang.AutoCloseable接口(也包括实现了java.io.Closeable的所有对象)的对象可以作为资源。
public static void main(String[]args]{ try(Resource res = new Resource()){//TODO 业务逻辑处理}catch(Exception e){System.out.print(e.printStackTrace());}}class Resource implements AutoCloseable{void doSome(){System.out.print("do something");}@Overridepublic void close() throw Exception{System.out.print("resource is closed");}}
以上代码编译后为自动生成finally代码块,里面会对try里面的Resource资源进行判空,如果非空就对其进行关闭。
在JDK9里面可以直接在try前面声明final类型的Resource,然后try-with-sources里面写上变量名即可。
集合
线程
IO
网络通信
锁
java中的锁可分为乐观锁和悲观锁,乐观锁和悲观锁是两个概念,而不是特指的两种锁。java中针对这两个概念作了落地。乐观锁认为操作的时候没有线程与我并发,正常地去写,但是如果有并发可能会失败,返回false,成功返回true,不会阻塞和等待,失败了就再试一次,直到成功为止。悲观锁则认为在操作的时候有线程与我并发,所以在操作之前就一定得先去竞争锁资源,如果拿不到这个资源,线程就挂起、阻塞等待。乐观锁使用CAS实现,在java中使用UnSafe类中的native方法形式存在的,到了底层,就是CPU支持的原子指令,比如x86下的cmpxchg操作指令。悲观锁使用synchronized、Lock锁实现。它们的核心区别就是是否会挂起你的线程,线程在用户态时不能这么做,需要用户态转换到内核态,让OS操作系统去唤醒挂起的线程,这个用户态和内核态的切换就比较耗时。
虽然乐观锁有一定的优势,但也不是所有场景都适合。如果竞争特别激烈,导致乐观锁有一直失败,那CPU就需要一直去调度它,但是又一直失败,就会很浪费CPU资源,从而导致CPU占用率飙高。在操作系统下,线程就只有一个阻塞状态BLOCKED,java中为了更好地排查问题,给线程提供了三种阻塞的状态,BLOCKED、WAITING、TIMED_WAITING.
CAS在java层面上来说CAS是不涉及到锁的情况,因为它不涉及线程的挂起和唤醒操作。可以为是无锁操作。但CAS在CPU内部是基于cmpxchg指令去做的。而且CPU也是多核的。那么在多个核心都去对一个变量进行CAS操作时,x86的CPU中,会添加lock前缀指令,可能会基于缓存行锁,或者是基于总线锁,只让一个CPU内核执行这个CPU操作。一般在平时的开发中,99%的场景下都涉及不到,除非开发一些中间件、框架之类时,可能会涉及到,一般看到的都是在JUC包下的并发工具里涉及到。比如ReentrantLock、synchronized、ThreadPoolExecutor,CountdownLatch等。
java中锁的底层实现CAS,主要就是cmpxchg指令;synchronized:主要是对象头里面的MarkWord中的锁升级机制,无锁、偏向锁、轻量级锁、重量锁;Lock锁中的AQS
反射
java反射是java的一个特性,它允许程序在运行时对自身进行检查,并且能够操作类、接口、字段 、方法等。反射提供强大的功能,但也带来一些技术难点。
基本原理
1、类的加载:java反射始于类的加载。当使用Class.forName()或其它类加载器加载类时,JVM会读取类的字节码文件,并将其转化为Class对象,这个对象包含了元数据信息,如类名、包名、父类、实现的接口、字段、方法等。
2、:获取类的信息:通过Class对象,我们可以获取类的各种信息,例如,使用getMethod()方法猎取类的所有公共方法,使用getDeclaredFields()方法猎取类的所有字段(包括私有字段)。
3、动态调用:反射不仅能允许我们获取类的信息,还允许我们动态地创建对象、调用方法、修改字段值等。通过newInstance()方法可能创建类的实例,通过getMethod()猎取方法并使用invoke()方法调用它。
技术难点:
1、性能:比直接操作代码更慢,因为反射涉及到动态解析和类型检查等。
2、安全性:反射允许访问类的私有成员,这可能导致安全漏洞。如果反射被恶意代码使用,可能会破坏系统的安全性。
3、复杂性:反射的操作相对比较复杂,需要深入理解java的类型系统和类加载机制。另外反射可能会带来一些安全性的问题。可以通过以下措施来尽量减少安全问题:
1、访问控制:java提供了访问控制修饰符来控制对类的成员的访问。虽然反射可以安全码这些限制,但我们应该避免在不需要时这样做。在设计API时, 应该只暴露必要的公共接口,并隐藏敏感的内部实现。
2、代码签名和验证:JVM在加载类时会对类的字节码进行验证,以确保符合java语言的规范。此外,还可以使用代码签名来验证类的来源是否可信,这可防止恶意代码被加载到JVM中。
3、最小权限原则:在编写使用反射的代码时,应遵循最小权限原则,则只请求执行所需任务所需的最小权限,例如,如果只需要可以读取某个字段的值,就不要请求修改该字段的权限。
4、安全管理器:java提供了一个安全管理器(SecurityManager)类,它允许应用程序定义自己的安全策略。通过实现自定义的安全管理器,可以限制反射的使用,例如禁止加载来自不受信息的源的类。
5、代码审计和测试:对使用反射的代码进行严格的审计和测试是确保安全性的重要步骤。这包括检查是否有不安全的反射使用,验证输入数据的有效性以及确保代码符合安全最佳实践等。
泛型