java知识点总结

本文列举一下java的重要知识点,做一下知识总结和沉淀。

1. JRE、JDK、J2SE、Java SE

  • JRE仅包含运行java程序的必须组件,包括java虚拟机以及java核心类库。

  • JDK同样包含JRE,并且还附带一系列开发、诊断工具,例如jstat、jinfo、jmap、jstack等。

  • J2SE和Java SE,他们指同一个东西,J2SE,J2EE, J2ME更名为Java SE, Java EE, Java ME。J2SE 是java三大技术体系的一个,其他两个是J2EE,J2ME:1)J2SE(Java 2 Standard Edition),标准版技术体系,包含构成了java语言核心的类库,例如,数据库连接,网络编程,接口定义等,主要用于桌面应用软件的开发,包含了JDK的核心类。2)J2EE(Java 2 Enterprise Edition),企业版技术体系,除了包含J2SE中的类,还包含用于开发企业级应用的类,比如,ServerLet,JSP, EJB,主要用于分布式的网络程序开发。3) J2ME(Java 2 Micro Edition),包含J2SE的一部分类,主要用于消费类电子产品的软件开发,比如手机,pad等。

2. 一个java源文件只能有一个public类,源文件的名字必须和public类的名字相同,可以有多个非public类。

3. java的内置类型和其对应的包装类

  • byte:8位,有符号、以二进制补码表示的整数,[-128, 127], 默认值0,对应包装类java.lang.Byte。

  • short:16位,有符号、以二进制补码表示的整数,[-32768, 32767], 默认值0,对应包装类java.lang.Short。

  • int:32位,有符号、以二进制补码标识的整数,[-2147483648, 2147483647], 默认值0,对应包装类java.lang.Integer。

  • long:64位,有符号、以二进制补码表示的整数,[-9223372036854775808,9223372036854775807],默认值0L,L不分大小写,小写容易混淆,对应包装类java.lang.Long。

  • float:单精度,32位浮点数,默认值0.0f,对应包装类java.lang.Float。

  • double:双精度,64位浮点数,浮点数默认类型是double,默认值0.0d,对应包装类java.lang.Double。

  • boolean:表示一位的信息,true/flase, 默认值是false。

  • char:单一的16位的Unicode字符,这点和C++不一样,最小值是\u0000(即为0), 最大值\uffff(即为65536),可以存储任何字符,默认值是'u0000',对应包装类是java.lang.Character。

数据类型从低到高的顺序是byte,short,char—> int —> long—> float —> double,在把大容量的类型转换为小容量类型的时候,必须使用强制类型转换。

4. volatile

volatile修饰的成员变量,在每次被线程访问的时候,都强制从主内存重新读取该成员变量的值,而且当成员变量发生变化的时候,会强制线程将变化值写到主内存,这样任何时刻两个不同的线程总是看到某个成员变量的同一个值。

5. String,StringBuffer,StringBuilder

String类的对象是不可改变,若需要对字符串做多次修改,应该用StringBuffer(线程安全),StringBuilder(线程不安全,从而更快)。

6. String的getBytes方法

byte[]getBytes(),使用平台默认字符集将此String编码为byte序列,

byte[] getBytes(String charsetName),使用指定字符集将此String编码为byte序列。

7. 可变参数

typeName...parameterName ,一个方法中只能指定一个可变参数,且必须是方法的最后一个参数,可以传多个typeName的参数,或者传typeName的数组过来,函数内部都是当作数组使用。

public class VarargsDemo{      
    public static void main(String[]args){      
        printMax(34, 3, 3,2, 56.5);      
        printMax(new double[]{1,2,3});      
    }


    public static void printMax(double...numbers){      
        if(numbers.length == 0)      
        {      
            System.out.println("no argument passed");      
            return;      
        }      
        double result = numbers[0];      
        for (int i = 1; i < numbers.length; ++i)      
        {      
            if(numbers[i] > result){      
                result = numbers[i];      
            }      
        }      
        System.out.println("The max value is " + result);      
    }      
}

8. java的==和equals的用法区别

== 用于检测他们是否是同一个对象,即reference equality;equals 用于检测他们的value是否相等,即逻辑上是否相等:

  • new String("test").equals("test") // --> true;

  • new String("test") == "test" // --> false

  • "test" == "test" // --> true ,这里,常量字符串已经被编译器设置,所以指向同一个对象;

  • "test" == "te" + "st" // --> true,string 常量被编译器设置,所以指向同一个对象

可以用java.util中的Objects程序类,他会检测null:

  • Objects.equals("test", new String("test")) // --> true;

  • Objects.equals(null, "test") // --> false;

  • Objects.equals(null, null) // --> true

绝大多数场景应该用equals, 极少数你认为不会发错的情况下用==(比如就是比较是否是同一个对象,或者被编译器设置的常量)。

9. java内存模型

线程对变量的所有操作(读取、复制)都必须在工作内存中进行,不能直接读写主内存中的变量。不同线程之间不能直接访问对方工作内存中的变量,线程之间变量值的传递均需要在主内存来完成。

10. java线程池

  • newSingleThreadExecutor,如果这个唯一的线程因为异常结束,那么会有一个新的线程代替他,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  • newCachedThreadPool,工作线程的创建数量几乎没有限制(integer.MAX_VALUE),这样可灵活的往线程池中添加线程,如果长时间没有往线程池中提交任务,工作线程空闲了1分钟(默认值),该线程将自动终止,终止后若有提交任务,则重新创建工作线程。采用SynchronousQueue装等待的任务,这个阻塞队列没有存储空间,这意味着只要有请求到来,就必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程。

  • newFixedThreadPool,创建一个定长线程池,可控制线程最大并发数,超出的任务(Thread对象)会在队列中等待,阻塞队列采用了LinkedBlockingQueue,它是一个无界队列,不会拒绝任务。

  • newScheduledThreadPool,创建一个定长线程池,支持定时及周期任务执行。

11. 字节码翻译成机器码的三种方式:

  • 解释执行,逐条将字节码翻译成机器码并执行,优点:无需等待编译。

  • 即时编译,将一个方法中包含的所有字节码编译成机器码再执行。优点:实际运行速度更快。

  • 混合模式,综合了解释执行和即时编译,会先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。

即时编译后的java程序执行效率可能是会超过C++的,这是因为与静态编译相比,即时编译拥有程序的运行时信息,并且能根据这个信息做相应优化。

12. C1、C2编译器

  • C1:client编译器,面向的是对启动性能有要求的客户端GUI程序,优化手段相对简单,编译时间短。

  • C2:server编译器,面向的是对峰值性能有要求的服务端程序,优化手段复杂,编译时间长,生成的代码执行效率高。

热点方法首先会被C1编译,而后热点方法中的热点会进一步被C2编译,为了不干扰应用的正常运行,即使编译是放在额外的编译线程进行的。在计算资源充足的情况下,字节码的解释执行和即时编译可同时进行,编译完成的机器码会在下次调用该方法时启用,以替换原本的解释执行。

13. 几个有名的VM

  • Sun HotSpot VM, 默认的java虚拟机,很强大,武林盟主。

  • BEA JRockit, 专注于硬件服务器和服务端应用场景的虚拟机,针对服务端场景做了很多优化,因此其不太关注程序启动速度。JRockit虚拟机内部不包含解释器实现,全部代码都靠即使编译器编译后执行。

  • IBM J9 VM:通用的虚拟机,从服务端到桌面应用,再到嵌入式都可以。

  • Azul VM 和 BEA Liquid VM 的专用商业及虚拟机:只运行在特定硬件平台,要求比较高,性能也很强悍,可以管理至少数十个CPU和数百GB的内存,还提供巨大内存范围内实现可控GC时间的垃圾收集器。

14. java虚拟机执行class字节码的过程

  • 加载:把代码数据加载到内存中,接着为这个类在JVM的方法区创建一个对应的class对象,这个class对象就是这个类各种数据的访问接口。

  • 验证:JVM规范校验,代码逻辑校验等。

  • 准备:Java 中的变量有「类变量」和「类成员变量」两种类型,「类变量」指的是被 static 修饰的变量,而其他所有类型的变量都属于「类成员变量」。在准备阶段,JVM 只会为「类变量」分配内存,而不会为「类成员变量」分配内存。「类成员变量」的内存分配需要等到初始化阶段才开始。

  • 解析:JVM针对类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符7类引用进行解析,将常量池中的符号引用替换成直接其在内存中的直接引用。

  • 初始化:JVM 会根据语句执行顺序对类对象进行初始化。

  • 使用:JVM完成初始化后,从入口方法开始执行用户的程序代码。

  • 卸载:当用户代码执行完毕后,JVM便开始销毁创建的class对象,最后负责运行的JVM也退出内存。

15. 直接内存

NIO(New Input/Output)类,引入基于通道(Channel)和缓冲区(Buffer)的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,能显著提升性能,避免了在java堆和native堆中来回复制数据。本机直接内存的分配不会受到java堆大小的限制,但是会受到本机总内存的大小以及处理器寻址空间的限制。在-Xmx等参数的设置,需要考虑到直接内存,避免两者总量大于本机内存,从而导致动态扩展的时候出现OutOfMemoryError异常。

16. 如何判断对象是垃圾

  • 引用计数法,对象被引用时加1,被去引用时减1,通过判定引用计数是否为0来判断对象是否是垃圾,这有个缺陷,对循环引用没有办法。

  • GC Root Tracing,从GC Root 出发,所有可达的对象都是存活的,所有不可达的对象都是垃圾。Golang采用了三色标记法,思路差不多:1)起初所有对象都是白色的,2)从根出发扫描所有可达对象,标记为灰色,放入待处理队列。3)从队列取出灰色对象,将其引用对象(从白色里面挑选)标记为灰色放入队列,自身标记为黑色。4)重复3,直到灰色对象队列为空,此时白色对象即为垃圾,进行回收。

17. 垃圾回收算法

  • 标记清除算法:分标记、清除两个阶段,标记阶段标记所有GC Root可达的对象,清除阶段清理掉所有未标记的对象,存在空间碎片问题,影响后续对象的分配效率。

  • 复制算法:将原有空间分成2块,每次只使用一块,每次垃圾回收的时候将存活的对象拷贝到另外一块,然后将原先的一块清理掉,然后交换2块的角色,空间只能使用1块,折损太大。

  • 标记压缩算法:分标记、压缩两个阶段,标记阶段标记所有存活的对象,压缩阶段,将所有存活的对象压缩到内存的一边,然后清理边界外的所有空间,移动和压缩会影响应用程序。

18. 新生代、老年代各自采用的回收算法

  • 新生代,大部分是存活很短的,若用标记清除算法则会有大量碎片,采用复制算法较好,但是也要注意,并不是对半分的,采用Eden:From Suvior0:To Suvior1=8:1:1分发,对象优先分配在Eden、From Survior0中,少量存活的复制到To Suvior1 中, 这种情况,只浪费了10%的空间,若ToSuvior1放不下Eden、From Survior0的存活对象,则会借用老年代的内存(分配担保)。

  • 老年代,标记清除算法、标记压缩,因为对象存活时间长,只有少量的对象需要清除或者标记,不会有大量的内存碎片;反过来,若用复制算法,则需要移动大量的对象,会严重影响应用程序。

19. 垃圾回收器

  • 串行回收器,用单线程进行垃圾回收的回收器,每次只有一个线程,在并发能力较弱的计算机上,其专注性和独占性的特点可以让其有较好的性能表现,可以在新生代和老年代使用,分为新生代串行回收器和老年代串行回收器。

  • 并行回收器,用多线程处理,对于并行能力强的机器,可以有效缩短垃圾回收所用时间:1)新生代ParNew回收器,将串行回收器多线程化,其回收策略、算法、参数和新生代串行回收器一样,在并行能力强的机器可以缩短垃圾回收时间,在并行能力弱的机器,由于存在线程切换,性能不一定比串行好。2)新生代ParallelGC回收器,与新生代ParNew回收器类似,也采用复制算法,多线程、独占式的,会导致stop-the-world,但是不同点是:注重系统的吞吐量,自适应调节策略,-XX:UseAdaptiveSizePolicy, 打开此策略,新生代的大小、Eden和Survior的比例、晋升老年代的对象的年龄等参数会自动调节,达到堆大小、吞吐量、停顿时间的平衡。-XX:MaxGCPauseMillis:设置最大垃圾收集器停顿时间,在ParallelGC工作期间,自动调整相应参数,将停顿时间控制在设置范围内,为完成此目标,会用较小的堆,从而也会导致频繁GC,-XX:GCTimeRatio:设置吞吐量大小,0~100的整数,假设为n,则系统将不花费超过1/(1+n)的时间用于垃圾收集,默认值是99,即不超过1%的时间用于垃圾收集。3)老年代ParallelOldGC回收器,注重吞吐量的回收器,多线程并发,因为是作用在老年代,算法用的标记压缩算法。

  • CMS回收器,主要关注系统停顿时间,Concurrent Mark Sweep, 标记清除法(会造成内存碎片),多线程并行回收的垃圾回收器。

  • G1 回收器,使用了分区算法,从而使得Eden区、From区、Survior区、老年代等各内存可以不用连续。分为初始标记,根区域扫描,并发标记,重新标记,独占清理,并发清理6个阶段,其中初始标记、重新标记、独占清理是独占式的,会stop-the-world,所有待回收的内存放到Collection Set中,首先针对Collection Set中的内存进行回收,当内存不足的时候,可能会触发FullGC。

20. HashMap和ConcurrentHashMap

  • HashMap,非线程安全,不能用于并发,在JDK1.7,采用数组+链表的方式,根据Key,Hash之后得到应该存放的桶的位置,若桶是空,则直接放进去,若桶是链表,则顺着链表进行查找,若没有则在最后添加,若有,则直接覆盖。当Key冲突较大的情况下,链表的长度会很长,降低了读写的效率。JDK1.8版本,有一个阈值,默认值是8,用于判定是否需要将链表转换成红黑树。

  • ConcurrentHashMap,JDK1.7版本采用分段锁技术,比Hashtable锁的粒度要细,支持Segment数量的线程并发。Hashtable不支持多线程间增加、迭代访问,迭代器会失效。ConcurrentHashMap支持此种场景,迭代器不会失效。用自旋锁+阻塞锁机制,先自旋锁,超过一定的重试次数后,改为阻塞锁。自旋锁一般用于竞争不频繁的场景,使用不当会造成CPU的浪费,优势是没有发生用户态、内核态的切换,效率高。与HashMap不同的是,ConcurrentHashMap最外层不是一个大的数组,而是一个Segment的数组。每个Segment包含一个与HashMap数据结构差不多的链表数组。JDK1.8版本摒弃了分段锁的方案,而是直接使用一个大的数组,对于PUT操作,如果Key对应的数组元素为NULL,则通过CAS操作将其设置为当前值。如果Key对应的数组元素(也即链表表头或者树的根元素)不为NULL,则对该元素使用synchronized关键字申请锁,然后进行操作。和HashMap一样,如果链表长度超过一定阈值,则会将其转成红黑树。

// JDK1.7版本结构
static final class HashEntry<K,V>{    
    final int hash;    
    final K key;    
    volatile V value;    
    volatile HashEntry<K, V> next;    
}    
static final class Segment<K, V> extends ReentrantLock implements Serializable{        
      private static final long serialVersionUID = 2249069246763182397L;        
      transient volatile HashEntry<K,V>[] table;        
      transient int count;        
      transient int modCount;        
      transient int threshold;        
     transient int loadFactor;        
}        
 final Segment<K, V> [] segments

21. synchronized的用法以及底层实现

  • 可以锁静态方法(含static),其锁的是类的class对象(不同于类的实例对象),可以控制这个类的多个静态方法的互斥访问。这时若有一个synchronized 锁的是非static方法(锁的是类的实例对象),两者是不互斥的,多线程可以同时访问,因为锁的对象是不一样的。

  • 可以锁代码块,锁的对象可以指定,比如某个实例对象,或者类的class对象。

java虚拟机中的同步(synchronized)基于进入和退出管程(monitor)对象实现。包括显示同步(同步代码块,有明确monitorenter和monitorexit指令)和隐示同步(synchronized 修饰的同步方法,无monitorenter和monitorexit,由方法调用指令读取运行时常量池中的方法的ACC_SYNCHRONIZED标志来隐示实现)。

在JVM中,对象在内存中的布局分为三块区域:实例数据,对齐填充,对象头。

  • 实例数据:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分,还包括数组的长度,这部分内存按4字节对齐。

  • 填充数据:虚拟机要求对象的起始地址是8字节的整数倍,故有时候需要对齐字节。

  • 对象头:这是实现synchronized的锁对象的基础,主要由Mark Word和Class Metadata Address组成。

synchronized 是可重入的,比如,一个synchronized(锁的是实例的对象)方法可以调用另外一个synchronized方法。

synchronized与等待唤醒:

  • 对象的wait/notify/notifyall 必须在synchronized 方法(或者代码块中)执行。

  • 和sleep方法不同,调用sleep方法,只是让对应线程休眠,并未释放对应的锁。

  • 调用wait方法后,会释放对应的锁,直到有人调用notify/notifyall方法才会继续。

  • 调用notify/notifyall方法后不会立即释放锁,等相应的方法和代码块执行完毕才会释放。

java对synchronized的优化:

  • 锁的状态:无锁状态,偏向锁,轻量级锁,自旋锁,重量级锁,随着锁的竞争,锁可以从低到高单向升级。

  • 偏向锁:如果一个线程获得了锁,那么锁就进入偏向模式,当这个线程再次请求锁时,无需做任何同步操作,即省去了大量有关锁的申请操作,提高了程序的性能。若失败则升级为轻量级锁。

  • 轻量级锁:适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为自旋锁。

  • 自旋锁:先尝试自旋几次,若得到锁,则进入临界区,若不能得到锁,则升级为为重量级锁。

  • 重量级锁:即真正意义上的锁,会将线程在操作系统层面挂起(发生用户态到内核态的切换)。

锁消除:由JIT编译器识别,部分逻辑代码完全不会存在线程共享访问,可以将对应的锁逻辑去掉。比如下面的StringBuffer类,append是同步方法(含synchronized),但是sb变量是局部变量,完全不会有数据共享问题,所以会将对应的锁消除掉。

public void add (String str1, String str2){
    StringBuffer sb = new StringBuffer();
    sb.append(str1).append(str2);
}

22. lombook

lombook是一个开源项目,能够通过添加注解自动生成一些方法,这个包是在编译阶段起作用,免去一些常规小函数的编写,例如,@Getter/@Setter注解可以针对类的属性字段自动生成Get/Set方法。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.8</version>
</dependency>

23. JDK性能监控工具

jstat -<option> [-t]  [-h<lines>  <vmid>  [<interval> [<count>]]

option 可以由以下值构成:

-class, 监视类装载、卸载数量、总空间、以及类装载所耗费的空间            
-gc,监视java堆的情况,包括Eden区、两个Survior区、老年代、永久代的容量、已使用空间、GC时间合计等信息            
-gccapacity,监视内容基本和-gc相同,增加输出java堆各个区的最大、最小空间            
-gcutil,基本和-gc相同,主要关注已使用空间占总空间的百分比            
-gccause, 与-gcutil相同,额外输出导致上一次GC的原因            
-gcnew, 监视新生代的GC状况            
-gcnewcapacity,基本与-gcnew一样,主要关注使用到的最大、最小空间            
-gcold, 监视老年代GC状况            
-gcoldcapacity, 与-gcold基本一样,关注使用到的最大、最小空间            
-gcpermcapacity,输出永久代的最大、最小空间            
-compiler, 输出JIT编译器编译过的方法、耗时信息            
-printcompilation 输出JIT编译过的方法  

-t ,表示输出时间戳

-h,表示在多少行后输出一个表头

vmid, 虚拟机的进程ID

interval,输出间隔, 单位是毫秒

count,输出次数

举例,

[root@pod-1039715-5c823e0ec83234-90451761-6-0 /usr/local/services/jdk1_8_0_111-1.0/jdk1.8.0_111/bin]# ./jstat -gcutil  -h1000  -t 32160 10000 1                
Timestamp         S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT                
       172641.0   0.00  31.79  50.93  13.92  97.22  95.73   1977    8.763     0    0.000    8.763


S0:Survior 0 ,还未使用                
S1:Survior 1, 使用了31.79%,                 
E:Eden, 使用了50.93%                
O:老年代使用了13.92%                
M: 元空间使用了97.22%                              
YGC、YGCT:程序运行以来,共发生1977次Minor GC,总共耗时8.763秒                
FGC/FGCT: 程序运行以来,共发生0次FullGC,耗时0秒                
GCT:总的GC的耗时,这里是YGCT+FGCT=8.763秒

24. 堆栈空间的设置参数

  • -Xms:堆得初始空间大小。

  • -Xmx:堆的最大空间大小。

  • -Xmn:设置年轻代的内存大小, ms(mx) - mn的大小就是老年代的大小。

  • -XX:SurviorRatio= eden/from =eden/to,from和to, 同一时间只有一个可用,循环作为from或者to,即始终有一个是浪费的。

  • -Xss:栈空间大小。

  • -XX:PermSize:永久代初始大小,一般是用来存放方法的,1.8用元空间代替。

  • -XX:MaxPermSize:永久代的最大大小。

  • -XX:MetaSpaceSize:元空间发生发生GC的阈值。

  • -XX:MaxMetaSpaceSize:设置元空间的最大大小,默认基本是机器的物理内存大小。

  • -XX:MaxDirectMemorySize, 指定本机直接内存的容量,若不指定,则默认与java堆的最大值保持一致(-Xmx), NIO的方法可能会用到此块内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值