把前段時間看過的內容,結合面試經歷,做以整理,持續改進:Dhtml
emmmm,爲何把JVM放第一個咧……java
主要是由於以前某次面試的時候被問到「從宏觀角度怎麼看Java」才發現用了N年的Java居然都沒好好看過這個mysql
回想一下也是這感受,從大二學Java以來一直沒把JVM當回事,回過來看才發現錯過了一個億orzgit
Java跨平臺的特性,主要歸功於Java虛擬機JVM——Java virtual machinegithub
如圖,經過編譯.java源碼文件得到.class字節碼文件,而後JVM經過類加載器加載字節碼文件,在虛擬機中運行面試
運行時的數據區主要有兩塊:線程共享區和獨佔區/非共享區算法
例如一塊psvm裏面Idea黨運行的代碼:sql
String s = new String("123");
複製代碼
將它拆分爲數個部分並對照上圖可得:編程
String s = xxxx
→ 對象s是保存字符串的引用的本地變量 → 本地方法棧Stack(獨佔區)new String(xxxx)
→ 經過帶參數的構造器實例化String對象並返回其引用 → 堆內存Heap(共享區)"123"
→ 字符串常量 → 常量池 → 方法區Non-Heap(共享區)這樣一解析,運行時數據存儲的區域就清晰不少了:Dubuntu
恰好前面恆生羣面的時候有同窗被問到了,這裏也趁這機會總結一下
標記可回收的對象,而後進行清除。
如圖,清理了紅色標記的部分,清理結束後多出綠色部分可用。存在問題:
預留一樣大小的一塊內存,進行GC時複製存活的對象到複製用區而且順序放置(不留空隙)。
優點:避免碎片化問題;
問題:複製需保留一塊內存,致使內存浪費
相似於標記-清除,可是需對存活者進行整理
優點:避免碎片化問題避免遺漏,經過類型區分,由小到大排序:
represents 1 bit of information, but its "size" isn't something that's precisely defined.
基礎類型都有對應的包裝類型(Integer、Char等
在基礎類型和包裝類型之間經過自動裝箱拆箱完成賦值
Integer i = 3;
裝箱int j = i;
拆箱默認值存在差別
類型比較( ==和equals() )需注意
基本類型 | == | equals |
---|---|---|
字符串變量 | 對象在內存中的首地址 | 字符串內容 |
非字符串變量 | 對象在內存中的首地址 | 對象在內存中的首地址 |
基本類型 | 值 | 不可用 |
包裝類 | 地址 | 內容 |
String, StringBuffer, StringBuilder, StringJoiner
String 內容不可變(賦值改變的是引用)
String s = "123"; //s只保存「123」的引用地址
s = "456"; // s保存了「456」的引用地址,"123"還鴿在內存裏等回收
複製代碼
StringBuffer 線程安全Synchronized
StringBuilder 非線程安全
StringJoiner Java 1.8新寶貝,非線程安全(實際經過StringBuilder實現), [文檔]
字符串拼接問題
衆所周知String在循環裏作拼接會耗時間耗內存,就想看看耗到什麼程度
代碼paste,如有問題歡迎指正qwq
List<String> stringList = new ArrayList<>();
for (int i = 0; i < N; i++) {
stringList.add(""+i);
}
for (int i = 1; i <= 5; i++) {
runTestWithClock(i, stringList);
// Thread.sleep(10000L); //用於查看內存時分隔每種方法
}
複製代碼
private static void runTestWithClock(int n, List<String> stringList){
String result = null;
long clock = System.currentTimeMillis(); // 記錄運行前時間
long timer = 0;
System.out.println("--------"+n+"-------");
switch (n){
case 1:
result = sTest(stringList);
break;
case 2:
result = sBufferTest(stringList);
break;
case 3:
result = sBuilderTest(stringList);
break;
case 4:
result = sJoiner(stringList);
break;
case 5:
result = sjStreamTest(stringList);
}
timer = System.currentTimeMillis() - clock ; // 計算時間差
System.out.println("Timer: "+timer);
System.out.println(result);
}
複製代碼
String result = "";
for (String s: stringList){
result = result + s + ",";
}
return result;
// sTest:0,1,2,3,4,5,6,7,8,9,10,11,12......
複製代碼
StringBuffer sBuffer = new StringBuffer();
for (String s: stringList){
sBuffer.append(s).append(",");
}
return sBuffer.toString();
// sBufferTest:0,1,2,3,4,5,6,7,8,9,10,11.....
複製代碼
StringBuilder sBuilder = new StringBuilder();
for (String s: stringList){
sBuilder.append(s).append(",");
}
return sBuilder.toString();
// sBuilderTest:0,1,2,3,4,5,6,7,8,9,10,11......
複製代碼
/* StringJoiner Since Java1.8 * @param:分隔符,前綴,後綴 * Docs: https://docs.oracle.com/javase/8/docs/api/java/util/StringJoiner.html */
StringJoiner sj = new StringJoiner(",", "[", "]");
for (String s: stringList){
sj.add(s);
}
return sj.toString();
// sJoiner:[0,1,2,3,4,5,6,7,8,9,10,11......]
複製代碼
return stringList.stream().
map(String::toString).
collect(Collectors.joining(",", "[", "]"));
// sjStreamTest:[0,1,2,3,4,5,6,7,8,9,10,11......]
複製代碼
--------1-------String
Timer: 26841
--------2-------StringBuffer
Timer: 14
--------3-------StringBuilder
Timer: 11
--------4-------StringJoiner
Timer: 10
--------5-------stream+joining
Timer: 43
複製代碼
public class User {
private String userName;
private String password;
public User() {}
public User(String userName, String password) {
this.userName = userName;
this.password = password;
}
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
複製代碼
對象的屬性和方法封裝起來,不可直接暴露給外部訪問(例如對象實例user的password屬性不能用user.password直接獲取或修改),只能經過實例化對象的方法訪問內部屬性(user.getPassword())
public class VIP extends User{
private int vipId;
private int accessType;
public VIP() {}
public VIP(int vipId, int accessType) {
// super(); // 隱藏
this.vipId = vipId;
this.accessType = accessType;
}
public VIP(String userName, String password, int vipId, int accessType) {
super(userName, password);
this.vipId = vipId;
this.accessType = accessType;
}
// 省略getter, setter
}
複製代碼
VIP繼承自User,包含User全部屬性和方法,在此基礎上又擁有本身獨立的屬性和方法
User user = new VIP();
有個疑問,使用List list = new ArrayList<>();
算不算向上轉型,一方面ArrayList繼承自抽象類AbstractList,另外一方面實現了List接口
class User {
public void communicate(){ System.out.println("User is communicating."); }
}
class Teacher extends User{ //繼承自User
public void communicate(){ System.out.println("Teacher is communicating."); } //重寫超類的方法
}
class Student extends User{ //繼承自User
public void communicate(){ System.out.println("Student is communicating."); } //重寫超類的方法
}
public class Communication {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new Teacher()); //向上轉型
users.add(new Student()); //向上轉型
for( User user: users ){
user.communicate(); // 執行子類重寫的方法
}
}
}
// 繼承→重寫→向上轉型
複製代碼
public class Student{
public Course searchCourse(int courseId){...}
public Course searchCourse(String courseName){...}
public List<Course> searchCourse(String instructorName, String courseType){...}
}
複製代碼
abstract class User {
public abstract void communicate();
}
class Teacher extends User{ //繼承自User
public void communicate(){ System.out.println("Teacher is communicating."); } //實現超類的抽象方法
}
class Student extends User{ //繼承自User
public void communicate(){ System.out.println("Student is communicating."); } //實現超類的抽象方法
}
public class Communication {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new Teacher()); //向上轉型
users.add(new Student()); //向上轉型
for( User user: users ){
user.communicate(); // 執行實現類實現的方法
}
}
}
// 繼承→實現→向上轉型
複製代碼
這種實現方式在MVC裏挺經常使用, 業務層Service和數據處理層Dao(或Repository)都會應用, 此處以Service爲例,不一樣的實現使用的是不一樣的數據處理層
public interface UserService { User findUserById(Integer userId); }
public class UserServiceImplByRepository implements UserService {
private UserRepository userRepository;
@Override
public User findUserById(Integer userId) { return userRepository.findUserByUserId(userId); }
}
public class UserServiceImplByDao implements UserService {
private UserDao userDao;
@Override
public User findUserById(Integer userId) { return userDao.findUserByUserId(userId); }
}
public class UserController {
public String user(int id){
if( id < 1000 ){
// 假設1000如下的使用Repository的實現
UserService us = new UserServiceImplByRepository();
// UserService接口類型的變量引用指向UserServiceImpl實現類的對象
return us.findUserById(id);
// 此處使用的是UserServiceImplByRepository的方法
}else{
// 其餘使用Dao的實現
UserService us = new UserServiceImplByDao();
// UserService接口類型的變量引用指向UserServiceImplByDao實現類的對象
return us.findUserById(id) ;
// 此處使用的是UserServiceImplByDao的方法
}
}
}
複製代碼
一開始用JDBC的時候還不知道,回過頭來才發現早已在用了……
寫完Class.forName("com.mysql.jdbc.Driver")
就能用Connection conn = DriverManager.getConnection(DB_URL,USER,PASS);
來拿Connection鏈接
能夠看看JDBC例子回顧一下:D www.tutorialspoint.com/jdbc/jdbc-s…
實際上手看看Class.forName
和java.lang.reflect
怎麼用:
public class ReflectTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> userClass = Class.forName("cn.magiklau.java.basic.model.User");
// 輸出Class名
System.out.println("Class name: "+userClass.getName());
Collector<CharSequence, ?, String> joining = Collectors.joining(", ", "[", "]"); // 方便格式化輸出
System.out.print("DeclaredFields: ");
// 使用getFields()只能取得public屬性,這裏要取private屬性須要用getDeclaredFields()
System.out.println(Arrays.stream(userClass.getDeclaredFields()).map(Field::getName).collect(joining));;
System.out.print("DeclaredMethods: ");
// 使用getMethods()也會取得超類的方法,這裏仍是隻取自己的
System.out.println(Arrays.stream(userClass.getDeclaredMethods()).map(Method::getName).collect(joining));
// 取一個實例
User user = (User)userClass.newInstance();
user.setUserName("testUserName");
System.out.println("Username: "+user.getUserName());
}
}
運行結果:
Class name: cn.magiklau.java.basic.model.User
DeclaredFields: [userName, password]
DeclaredMethods: [getPassword, setUserName, getUserName, setPassword]
Username: testUserName
複製代碼
參考文章
深刻理解 Java 反射和動態代理 github.com/dunwu/blog/…
深刻解析Java反射(1) - 基礎 www.sczyh30.com/posts/Java/…
orz爲了看反射居然把類加載機制都看了一圈
加載(Loading)
讀取二進制內容
驗證(Verification)
驗證class文件格式規範、語義分析、引用驗證、字節碼驗證
準備(Preparation)
分配內存、設置類static修飾的變量初始值(此時除了final修飾之外的其餘static變量均先給0或null值,只有final值是在此時肯定的)
解析(Resolution)
類、接口、字段、類方法等解析
初始化(Initialization)
靜態變量賦值(對,static的值如今纔給賦上),執行靜態代碼塊
使用(Using)
建立實例對象
卸載(Unloading)
從JVM方法區中卸載(條件:① 該Class全部實例都已經被GC;② 加載該類的ClassLoader實例已經被GC)
除了頂層ClassLoader之外,其餘類加載器都須要有超類加載器,在超類加載失敗時纔會交給子類加載
// 居然還不支持MarkDown畫圖……簡單記一下先
Bootstrap ClassLoader
頂層啓動類加載器
↑委託 ↓查找
Extension ClassLoader
拓展類庫類加載器
↑委託 ↓查找
Application ClassLoader
用戶應用程序類加載器
↑委託 ↓查找
Custome ClassLoader
自定義類加載器
複製代碼
又看到一些個神奇的作法,備用 www.cnblogs.com/chanshuyi/p…
參考CSDN-Sodino-blog.csdn.net/sodino/arti…
建立階段(Created)
應用階段(In Use)
對象至少被一個強引用持有
不可見階段(Invisible)
超出做用域
for(int i = 0; i < 10; i++){ // in loop
// i is visible
}// out of the loop
// i is invisible
複製代碼
不可達階段(Unreachable)
無任何強引用持有
收集階段(Collected)
gc已經對該對象的內存空間從新分配作好準備
終結階段(Finalized)
當對象執行完finalize()方法後仍然處於不可達狀態時,則該對象進入終結階段。在該階段是等待垃圾回收器對該對象空間進行回收
對象空間重分配階段(De-allocated)
圖來自:runoob - Java 流(Stream)、文件(File)和IO - www.runoob.com/java/java-f…
// 讀操做
String filePath = "C:\\WorkSpace\\JavaProject\\JavaLearning\\src\\ioTestFile.txt";
// 字節流輸入,FileInputStream提供文件處理功能
InputStream fis = new FileInputStream(filePath);
// BufferedInputStream提供緩存功能
InputStream bis = new BufferedInputStream(fis);
while( bis.available() > 0 ){
System.out.println((char) bis.read());
}
// 同理進行寫操做
OutputStream fos = new FileOutputStream(filePath);
OutputStream bos = new BufferedOutputStream(fos);
bos.write("MagikIOTest~".getBytes());
bos.flush();
複製代碼
和流操做基本類似,也是在Reader裏疊上File和Buffered裝飾
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String gotLine;
while ((gotLine = bufferedReader.readLine()) != null) {
System.out.println(gotLine);
}
// 裝飾者模式使得 BufferedReader 組合了一個 Reader 對象
// 在調用 BufferedReader 的 close() 方法時會去調用 Reader 的 close() 方法
// 所以只要一個 close() 調用便可
bufferedReader.close();
FileWriter fileWriter = new FileWriter(filePath);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
bufferedWriter.write("MagikIOTest writer~");
bufferedWriter.close();
複製代碼
Server: ServerSocket(port, timeout)
Client: Socket(host, port)
Server: accept()
S/C: InputSteam->OutputSteam
S/C: close()
非阻塞Non-Block IO
三大核心組件:
1. Buffer 緩衝區
2. Channel 通道
3. Selector 選擇器
複製代碼
使用:
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(n); // 申請容量n
byteBuffer.put((byte) 1); // 寫
byteBuffer.flip(); // 轉換讀寫
byte b = byteBuffer.get(); // 讀
byteBuffer.compact(); // 清除已閱讀的數據。轉爲寫入模式
byteBuffer.clear(); // 清除整個緩衝區
複製代碼
三屬性
倆模式
以上方的使用爲例解析: 容量c,位置p,限制l
[Write mode] 初始
0 1 2 3
↑ ↑↑
p lc
===
put(x)
===
[Write mode] 完成put
0 1 2 3
↑ ↑↑
p lc
===
flip()
===
[Read mode] 轉換模式
0 1 2 3
↑ ↑ ↑
p l c
===
get()
===
[Read mode] 完成get
0 1 2 3
↑↑ ↑
pl c
===
compact()
===
[Read mode] 清除已讀
0 1 2 3
x ↑↑ ↑
x pl c
[Write mode] 並轉換模式
0 1 2 3
↑ ↑↑
p lc
===
clear()
===
返回初始狀態
0 1 2 3
↑ ↑↑
p lc
複製代碼
運做方式:
API:
基本操做:
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false); // 設置爲非阻塞模式
sc.connect( new InetSocketAddress("http://domain.com", port));
sc.write(byteBuffer);
sc.read(byteBuffer);
sc.close();
複製代碼
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // 同理非阻塞
ssc.socket().bind(new InetSocketAddress(8080));
while(true){
SocketChannel sc = ssc.accept();
if( sc != null ){
sc.write(xxx)
sc.read(xxx)
....
}
}
複製代碼
Really? While loop? Need to be improved:
用於管理多個NIO通道。
channel事件類型使用SelectionKey常量來存儲:
使用:
// 建立選擇器
Selector selector = Selector.open();
selector.configureBlocking(false);
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
// 註冊通道
SelectionKey key = ssc.register(selector, SelectionKey.OP_READ);
while(true){ // 一直監聽,安全保障
int readyChannels = selector.select(); // 監聽事件,阻塞到有爲止
if( readyChannels == 0 ) continue; // 返回監聽狀態
// 成功監聽到事件
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 判斷事件類型
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}
}
複製代碼