把前段时间看过的内容,结合面试经历,做以整理,持续改进: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();
}
}
复制代码