sjw_blog


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

面试day5

发表于 2018-08-09

##HashMap的Key为Object怎么办

HashMap中,如果要比较key是否相等,要同时使用hashcode(),equals(),因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地 址,这样即便有相同含义的两个对象,比较也是不相等的,例如,生成了两个“羊”对象,正常理解这两个对象应该是相等的,但如果你不重写 hashcode()方法的话,比较是不相等的!
HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等 的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为 同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。

##浏览器从接收到一个URL到最后展示出页面,经历了哪些过程。

####整体通信

有了客户端和服务器,就可以开始通信了,整体上分为3个步骤:

  • 因为http是构建在TCP之上,那么自然是要经过3次握手创建连接。
  • 创建连接后,服务器会根据url请求中的信息进行处理,作出响应,一般来说是找到一个html文件返回给客户端。
  • 客户端即浏览器得到html,进行渲染。
  1. 缓存解析

    浏览器缓存-系统缓存-路由器缓存 当中查看,如果有从缓存当中显示页面

  2. DNS解析

    域名到IP地址的转换过程,递归查询,非递归查询

  3. 创建连接

    http三次握手

    为什么要三次握手

    计算机A和B之间的通信,假定B给A发送一个连接请求分组,A收到了这个分组,并发送了确认应答分组。按照两次握手的协定,A认为连接已经成功地建立了,可以开始发送数据分组。可是,B在A的应答分组在传输中被丢失的情况下,将不知道A是否已准备好,不知道A建议什么样的序列号,B甚至怀疑A是否收到自己的连接请求分组。在这种情况下,B认为连接还未建立成功,将忽略A发来的任何数据分组,只等待连接确认应答分组。而A在发出的分组超时后,重复发送同样的分组。这样就形成了死锁

  4. 服务器处理

    建立好连接后,客户端就会发送http请求,请求信息包含一个头部和一个请求体,

    一般的web技术都会把请求进行封装然后交给我们的服务器进行处理,比如servlet会把请求封装成httpservletrequest对象,把响应封装成httpsevletresponse对象。nodejs的http模块,当你创建服务器的时候会写一个回调函数,回调的参数用来接受http请求对象和响应对象,然后在回调函数中对请求进行处理。

  5. 客户端渲染

    客户端接收到服务器传来的响应对象,从中得到html字符串和MIME,根据MIME知道了要用页面渲染引擎来处理内容即html字符串。
    渲染引擎得到html字符串作为输入,然后对html进行转换,转化成能够被DOM处理的形式,接着转换成一个dom树,在解析html的过程,解析到

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    	

    ##最安全的单例模式--java使用内部类实现单例模式

    使用内部类可以避免这个问题,因为在多线程环境下,jvm对一个类的初始化会做限制,同一时间只会允许一个线程去初始化一个类,这样就从虚拟机层面避免了大部分单例实现的问题

    ```Java
    public class Singleton {

    private static class LazyHolder {
    private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton (){}
    public static final Singleton getInstance() {
    return LazyHolder.INSTANCE;
    }
    }

##网络协议 7层 应-表-会-传-网-数-物

  • 应用层

    与其它计算机进行通讯的一个应用,它是对应应用程序的通信服务的。例如,一个没有通信功能的字处理程序就不能执行通信的代码,从事字处理工作的程序员也不关心OSI的第7层。但是,如果添加了一个传输文件的选项,那么字处理器的程序员就需要实现OSI的第7层。示例:TELNET,HTTP,FTP,NFS,SMTP等。

  • 表示层

    这一层的主要功能是定义数据格式及加密。例如,FTP允许你选择以二进制或ASCII格式传输。如果选择二进制,那么发送方和接收方不改变文件的内容。如果选择ASCII格式,发送方将把文本从发送方的字符集转换成标准的ASCII后发送数据。在接收方将标准的ASCII转换成接收方计算机的字符集。示例:加密,ASCII等。

  • 会话层

    它定义了如何开始、控制和结束一个会话,包括对多个双向消息的控制和管理,以便在只完成连续消息的一部分时可以通知应用,从而使表示层看到的数据是连续的,在某些情况下,如果表示层收到了所有的数据,则用数据代表表示层。示例:RPC,SQL等。

  • 传输层

    这层的功能包括是否选择差错恢复协议还是无差错恢复协议,及在同一主机上对不同应用的数据流的输入进行复用,还包括对收到的顺序不对的数据包的重新排序功能。示例:TCP,UDP,SPX。

  • 网络层

    这层对端到端的包传输进行定义,它定义了能够标识所有结点的逻辑地址,还定义了路由实现的方式和学习的方式。为了适应最大传输单元长度小于包长度的传输介质,网络层还定义了如何将一个包分解成更小的包的分段方法。示例:IP,IPX等。

  • 数据链路层

    它定义了在单个链路上如何传输数据。这些协议与被讨论的各种介质有关。示例:ATM,FDDI等。

  • 物理层

    OSI的物理层规范是有关传输介质的特这些规范通常也参考了其他组织制定的标准。连接头、帧、帧的使用、电流、编码及光调制等都属于各种物理层规范中的内容。物理层常用多个规范完成对所有细节的定义。示例:Rj45,802.3等。

###Spring循环依赖

Spring将对象依赖分成属性依赖和构造依赖,构造依赖问题无法解决,只能抛出BeanCurrentlyInCreationException异常,在解决属性依赖问题,Spring采用的是提前暴露对象的方法。

首先Spring创建office对象,采用其默认构造函数,创建成功后,Spring会通过以下代码将对象提前暴露出来,尽管此时的对象还未完成属性注入,属于早期对象。这个时候对象放在singletonFactories的Map表中,value是函数对象ObjectFactory.

Spring会通过函数populateBean来完成office对象的属性注入,再注入boss属性时,发现是一个引用对象,这个时候同样会通过getBean(“boss”)来获得boss对象,boss对象由于从未创建,则创建boss对象。在创建boss对象时,发现它构造依赖于office对象,这个时候Spring也会通过getBean(“office”)获取office对象。由于存在office提前暴露出来,这个时候直接从singletonFactories的Map表中得到office对象并返回,并不需要重新再创建office对象,这样就避免了循环依赖问题,接下来boss对象可以成功被创建,则返回到到office的属性注入中。

###消息队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class LinkedBlockingQueue<E> {
private final int capacity;

private Lock lock = new ReentrantLock();
private LinkedList<E> queue;//缓冲区
private int count; //缓冲区商品个数
private Condition unfull = lock.newCondition();
private Condition unEmpty = lock.newCondition();

public LinkedBlockingQueue() throws InterruptedException {
this(Integer.MAX_VALUE);
}


public LinkedBlockingQueue(int capacity) throws InterruptedException {
this.capacity = capacity;
queue = new LinkedList<E>();
}


public void put(E e) throws InterruptedException {

try {
while (count == capacity) {
unfull.await();//阻塞队列已满,一直等待,直到有unfull
}
lock.lock(); //内层互斥夹紧
queue.add(e);
count++;
lock.unlock();
unfull.signal();
} finally {

}
}

public E take() throws InterruptedException {

try {
while (count == 0) {//队列为空,阻塞.一直等待指导有商品(unEmpty)
unEmpty.await();
}
lock.lock();
E e = queue.pop();
count--;
lock.unlock();
unEmpty.signal();
return e;
} finally {

}
}

}

面试day4

发表于 2018-08-07

#java hashcode和equals

hashcode是JVM对对象的内存地址做一定的计算后的到的一个int值

对于equals()与hashcode(),比较通用的规则:
①两个obj,如果equals()相等,hashCode()一定相等
②两个obj,如果hashCode()相等,equals()不一定相等

2)在设计一个类的时候往往需要重写equals方法,比如String类,在重写equals方法的同时,必须重写hashCode方法。
如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致,如在存储散列集合时(如Set类),将会存储了两个值一样的对象,导致混淆,因此,就也需要重写hashcode()。

#内部类

###为什么成员内部类可以无条件访问外部类的成员?

编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?

虽然在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。

###为什么局部内部类和匿名内部类只能访问局部final变量?

 当外部类方法执行完毕之后,变量的生命周期就结束了,而此时内部类对象的生命周期很可能还没有结束,那么在内部类的方法中继续访问变量就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了final复制的手段来解决这个问题。将这段代码的字节码反编译可以得到下面的内容    

#synchronized与Lock的区别

###synchronized与Lock的区别

synchronized

Java的关键字,在jvm层面上

1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁

假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待

可重入 不可中断 非公平

Lock

是一个类

在finally中必须释放锁,不然容易造成线程死锁

分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待

可重入 可判断 可公平(两者皆可)

###两种锁的底层实现方式

synchronized映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当一条线程进行执行的遇到monitorenter指令的时候,它会去尝试获得锁,如果获得锁那么锁计数+1(为什么会加一呢,因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况),如果没有获得锁,那么阻塞。当它遇到monitorexit的时候,锁计数器-1,当计数器为0,那么就释放锁。

那么有的朋友看到这里就疑惑了,那图上有2个monitorexit呀?马上回答这个问题:上面我以前写的文章也有表述过,synchronized锁释放有两种机制,一种就是执行完释放;另外一种就是发送异常,虚拟机释放。

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。

###索引

  • 数据唯一性差的字段不要使用索引

比如性别,只有两种可能数据。意味着索引的二叉树级别少,多是平级。这样的二叉树查找无异于全表扫描。

  • 频繁更新的字段不要使用索引

比如logincount登录次数,频繁变化导致索引也频繁变化,增大数据库工作量,降低效率。

  • 字段不在where语句出现时不要添加索引

只有在where语句出现,mysql才会去使用索引

  • 数据量少的表不要使用索引

使用了改善也不大
另外。如果mysql估计使用全表扫描要比使用索引快,则不会使用索引。

面试day3

发表于 2018-08-03

#JAVA进程间的通信方式(IPC)

JAVA进程间通信的方法主要有以下几种:

  1. 管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。   
  2. 命名管道(named pipe):命名管道克服了管道没有名字的限制,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。   
  3. 信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身。   
  4. 消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。   
  5. 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。   
  6. 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。   
  7. 信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。   
  8. 套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

#Java线程间的通信方式

###锁
synchronized、notify、wait

1
2
3
4
5
synchronized (threadToGo) {
while(条件) threadToGo.wait();
do something
threadToGo.notify();
}

Lock和Condition

1
2
3
4
5
6
7
8
9
private Lock lock = new ReentrantLock(true);
private Condition condition = lock.newCondition();


lock.lock();
while(threadToGo.value == 2)
condition.await();
do something
condition.signal();

volatile

1
2
3
4
private volatile ThreadToGo threadToGo = new ThreadToGo();
while(threadToGo.value==2){}
Helper.print(arr[i], arr[i + 1]);
threadToGo.value=2;

AtomicInteger

1
2
3
while(threadToGo.get()==2){}
Helper.print(arr[i], arr[i + 1]);
threadToGo.set(2);

PipedInputStreamAPI通道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private final PipedInputStream inputStream1;
private final PipedOutputStream outputStream1;
private final PipedInputStream inputStream2;
private final PipedOutputStream outputStream2;
private final byte[] MSG;
public MethodSix() {
inputStream1 = new PipedInputStream();
outputStream1 = new PipedOutputStream();
inputStream2 = new PipedInputStream();
outputStream2 = new PipedOutputStream();
MSG = "Go".getBytes();
try {
inputStream1.connect(outputStream2);
inputStream2.connect(outputStream1);
} catch (IOException e) {
e.printStackTrace();
}
}
public void shutdown() throws IOException {
inputStream1.close();
inputStream2.close();
outputStream1.close();
outputStream2.close();
}

JVM

发表于 2018-08-03

#类加载器

###ClassLoader(类加载器)

  • 加载:查找并加载类的二进制数据
  • 连接:
    • 验证:确保被加载的类的正确性
    • 准备:为类的静态变量分配内存,并将其初始化问默认值
    • 解析:把勒种的符号引用转换为直接引用
  • 初始化:为类的静态变量赋予正确的初始值

###主动使用-类-初始化

  • 创建类的实例
  • 访问某个类或者访问类的静态变量,或者对静态变量赋值
  • 调用类的静态方法
  • 反射 (Class.forName)
  • 初始化一个类的子类
  • Java虚拟机启动是包含main函数的类

###类的加载

将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个Java.lang.Class对象,用来封装类在方法区内的构造函数。

#对象的内存布局

###对象

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

对象头包括 Mark Word 如哈希码,GC分代年龄,锁状态标志,线程持有锁,偏向锁id,偏向时间戳。

对象头还包括类型指针,指向方法区中的那个类实例

###对象的访问定位

reference类型来操作具体对象

  • 句柄引用,句柄中包含了实例数据和类型数据的地址信息(方法区)
  • 直接引用,存储的就是对象的地址,对象中还包含有类型数据的地址信息(方法去)

#枚举根节点

###stop the world

  • OopMap 记录哪些地方是存储着对象引用

  • 安全点(safe point) 在安全点上时,线程可以进入GC

安全点选在以程序“是否具有让程序长时间执行的特征(指令序列复用,比如方法调用,循环指令,异常跳转)”

当需要GC时,设置一个标志,各个线程执行时都会去轮询这个标志,然后在安全点自己主动挂起

  • 安全区域(safe Region) 指在一段代码片段中,引用关系不会发生变化,在任意地方GC都是安全的,当线程执行到安全区域,首先把自己表示为进入安全区域,当离开时,需要检查系统是否完成了根节点枚举。

#方法区中的永久区资源清理

  • 常量 当没有对象引用这个常量时,如果内存不足的话,才会被清理

  • 类 判断类的条件比较苛刻

    • 该类的所有实例都被回收
    • 加载该类的ClassLoader已经被回收
    • 该类的java.lang.Class对象没有在任何地方被引用,没有被反射调用该方法

#Class文件

###包括无符号数和表

无符号数包括u1,u2,u4,u8(1字节,2字节,4字节,8字节)

表可以包含多个表,以_info结尾

###Class文件

魔数 0xCAFEBABE

主次版本号第5,6为次版本号,7,8为主板本号

常量池

访问标记

类索引和父类索引 接口索引集合

……

#类的加载

加载-(验证-准备-解析)【连接】-初始化-使用-卸载

###开始加载

  • 遇到new,读取或者设置一个类的静态字段,以及调用一个类的静态方法时
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类是,如果父类没有初始化,则初始化父类
  • 当虚拟机启动时,初始化包含main函数的类

###加载的工作

  • 通过一个类的全限定名来获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 在内存中(堆)生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

###验证

  • 文件格式验证

验证字节流是否符合Class文件的规范,并且可以由当前版本的虚拟机进行处理

  • 元数据验证(数据类型的验证,类是否合法)

对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范要求

  • 字节码验证

通过数据流和控制流分析,确定程序语义是否合法,符合逻辑,对类的方法体进行校验分析

  • 符号引用验证

对类自身以外的信息进行匹配性校验

###准备

准备节点是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。内存分配的仅包括类静态变量(static),初始值是该数据类型的零值,不是代码设置的值。

###解析

解析阶段虚拟机将常量池内的符号引用替换为直接引用的过程

###初始化

根据程序员通过程序制定的主管计划去初始化类变量和其他资源

笔试day2

发表于 2018-08-01

#堆排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def adjustDown(a,int i, int N):
#找到i节点的子节点
j = i * 2 +1
while j < N:
#找到两个子节点中最小的那个
if j + 1 < N and a[j] > a[j+1]:
j +=1
#如果父节点是最小的了,说明符合条件,则不进行交换
if a[i] < a[j]:
break
#父节点和最小的子节点进行交换
swap(i,j)
#继续判断交换后的子节点的子树情况
i = j
j = j * 2 +1

def makeHeap(a,N):
#找到该数组中的最后一个非叶子节点
i = N /2 -1
#从这个叶子节点开始判断调整堆
for j in range(i,0,-1):
adjustDown(a,i,N)

def HeapSort(a):
N = len(a)
#开始建堆
makeHeap(a,N)
#每次取出堆顶点,与最后的节点交换,再重新调整堆
for i in range(N-1,0,-1):
swap(0,i)
adjustDown(a,0,i)

#二叉树遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Node:  
def __init__(self,value=None,left=None,right=None):
self.value=value
self.left=left #左子树
self.right=right #右子树

def preTraverse(root):
'''
前序遍历
'''
if root==None:
return
print(root.value)
preTraverse(root.left)
preTraverse(root.right)

def midTraverse(root):
'''
中序遍历
'''
if root==None:
return
midTraverse(root.left)
print(root.value)
midTraverse(root.right)

def afterTraverse(root):
'''
后序遍历
'''
if root==None:
return
afterTraverse(root.left)
afterTraverse(root.right)
print(root.value)

###已知二叉树的前序遍历和中序遍历,求这棵二叉树的后序遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
preList = list('12473568')
midList = list('47215386')
afterList = []

def findTree(preList, midList, afterList):
if len(preList) == 0:
return
if len(preList) == 1:
afterList.append(preList[0])
return
root = preList[0]
n = midList.index(root)
findTree(preList[1:n + 1], midList[:n], afterList)
findTree(preList[n + 1:], midList[n + 1:], afterList)
afterList.append(root)

笔试算法day1

发表于 2018-07-31

#选择排序算法

1
2
3
4
5
6
7
8
9
10
def sort(a):
N = len(a)
for i in range(N):
min = i
for j in range(i+1,N):
if a[min] > a[j]:
min = j
temp = a[min]
a[min] = a[j]
a[j] = temp

#插入排序算法

1
2
3
4
5
6
7
8
def sort(a):
N = len(a)
for i in range(1,N):
for j in range(i,0,-1):
if a[j] < a[j-1]
temp = a[j]
a[j] = a[j-1]
a[j-1] = temp

#希尔排序算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def sort(a):
N = len(a)
#将整个数组分成3份
h = N
while h > 1:
h = h / 3 +1
for i in range(h,N):
j = i
while j-h >= 0:
if a[j] < a[j-h]:
temp = a[j]
a[j] = a[j-h]
a[j-h] = temp
j = j-h

#归并排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def merge(a, b):
c = []
h = j = 0
while j < len(a) and h < len(b):
if a[j] < b[h]:
c.append(a[j])
j += 1
else:
c.append(b[h])
h += 1

if j == len(a):
for i in b[h:]:
c.append(i)
else:
for i in a[j:]:
c.append(i)

return c


def merge_sort(lists):
if len(lists) <= 1:
return lists
middle = len(lists)/2
left = merge_sort(lists[:middle])
right = merge_sort(lists[middle:])
return merge(left, right)

#快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def sort(a,low,high):
left = low
right = high
if left >= right:
return
key = a[left]
l = left
while True:
while a[left] <= key and left < right:
left +=1
while a[right] > key and left < right:
right -=1

temp = a[left]
a[left] = a[right]
a[right] = temp
if left >= right:
break
a[right] = key
sort(a,low,right)
sort(a,right,high)

未命名

发表于 2018-07-29

代码执行环境
python2.7
代码执行方式
python RoadGrid.py

输入测试用例

1
2
3 3
0,1 0,2;0,0 1,0;0,1 1,1;0,2 1,2;1,0 1,1;1,1 1,2;1,1 2,1;1,2 2,2;2,0 2,1

输出样例

1
2
3
4
5
6
7
[W] [W] [W] [W] [W] [W] [W]
[W] [R] [W] [R] [R] [R] [W]
[W] [R] [W] [R] [W] [R] [W]
[W] [R] [R] [R] [R] [R] [W]
[W] [W] [W] [R] [W] [R] [W]
[W] [R] [R] [R] [W] [R] [W]
[W] [W] [W] [W] [W] [W] [W]

面试day2

发表于 2018-07-28

JVM垃圾收集器

##JVM内存分配策略

####对象分配
在JVM内存模型一文中, 我们大致了解了VM年轻代堆内存可以划分为一块Eden区和两块Survivor区. 在大多数情况下, 对象在新生代Eden区中分配, 当Eden区没有足够空间分配时, VM发起一次Minor GC, 将Eden区和其中一块Survivor区内尚存活的对象放入另一块Survivor区域, 如果在Minor GC期间发现新生代存活对象无法放入空闲的Survivor区, 则会通过空间分配担保机制使对象提前进入老年代(空间分配担保见下).

####对象晋升

  • 年龄阈值
    VM为每个对象定义了一个对象年龄(Age)计数器, 对象在Eden出生如果经第一次Minor GC后仍然存活, 且能被Survivor容纳的话, 将被移动到Survivor空间中, 并将年龄设为1. 以后对象在Survivor区中每熬过一次Minor GC年龄就+1. 当增加到一定程度(-XX:MaxTenuringThreshold, 默认15), 将会晋升到老年代.

  • 提前晋升: 动态年龄判定
    然而VM并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代: 如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半, 年龄大于或等于该年龄的对象就可以直接进入老年代, 而无须等到晋升年龄.

####对象生死判定

  • 可达性分析算法
    在主流商用语言(如Java、C#)的主流实现中, 都是通过可达性分析算法来判定对象是否存活的: 通过一系列的称为 GC Roots 的对象作为起点, 然后向下搜索; 搜索所走过的路径称为引用链/Reference Chain, 当一个对象到 GC Roots 没有任何引用链相连时, 即该对象不可达, 也就说明此对象是不可用的

  • 在Java, 可作为GC Roots的对象包括:

    1. 方法区: 类静态属性引用的对象;
    2. 方法区: 常量引用的对象;
    3. 虚拟机栈(本地变量表)中引用的对象.
    4. 本地方法栈JNI(Native方法)中引用的对象。

####垃圾收集算法

  • 分代收集

新生代-复制算法

将新生代划分为一块较大的Eden区和两块较小的Survivor区(HotSpot默认Eden和Survivor的大小比例为8∶1)

该算法的核心是将可用内存按容量划分为大小相等的两块, 每次只用其中一块, 当这一块的内存用完, 就将还存活的对象复制到另外一块上面, 然后把已使用过的内存空间一次清理掉.


老年代-标记清除算法

该算法分为“标记”和“清除”两个阶段: 首先标记出所有需要回收的对象(可达性分析), 在标记完成后统一清理掉所有被标记的对象.

老年代-标记整理算法

标记清除算法会产生内存碎片问题, 而复制算法需要有额外的内存担保空间, 于是针对老年代的特点, 又有了标记整理算法. 标记整理算法的标记过程与标记清除算法相同, 但后续步骤不再对可回收对象直接清理, 而是让所有存活的对象都向一端移动,然后清理掉端边界以外的内存.

#synchronized实现原理

synchronized关键字最主要有以下3种应用方式,下面分别介绍

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁

  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

面经day1

发表于 2018-07-27

分布式锁实现原理

一、什么是分布式锁?
要介绍分布式锁,首先要提到与分布式锁相对应的是线程锁、进程锁。

线程锁:主要用来给方法、代码块加锁。当某个方法或代码使用锁,在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果,因为线程锁的实现在根本上是依靠线程之间共享内存实现的,比如synchronized是共享对象头,显示锁Lock是共享某个变量(state)。

进程锁:为了控制同一操作系统中多个进程访问某个共享资源,因为进程具有独立性,各个进程无法访问其他进程的资源,因此无法通过synchronized等线程锁实现进程锁。

分布式锁:当多个进程不在同一个系统中,用分布式锁控制多个进程对资源的访问。

分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。

三种执行方案

  • 基于数据库实现分布式锁;
  • 基于缓存(Redis等)实现分布式锁;
  • 基于Zookeeper实现分布式锁;

数据库锁

使用基于数据库的这种实现方式很简单,但是对于分布式锁应该具备的条件来说,它有一些问题需要解决及优化:

1、因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;

2、不具备可重入的特性,因为同一个线程在释放锁之前,行数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前机器和线程相同,若相同则直接获取锁;

3、没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;

4、不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。

5、在实施的过程中会遇到各种不同的问题,为了解决这些问题,实现方式将会越来越复杂;依赖数据库需要一定的资源开销,性能问题需要考虑。

##Redis锁

实现思想:

(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。

(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。

(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。

##ZooKeeper
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。基于ZooKeeper实现分布式锁的步骤如下:

(1)创建一个目录mylock;

(2)线程A想获取锁就在mylock目录下创建临时顺序节点;

(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;

(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;

(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。

这里推荐一个Apache的开源库Curator,它是一个ZooKeeper客户端,Curator提供的InterProcessMutex是分布式锁的实现,acquire方法用于获取锁,release方法用于释放锁。

优点:具备高可用、可重入、阻塞锁特性,可解决失效死锁问题。

缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。

消息队列

##Kafka
Kafka是LinkedIn于2010年12月开发并开源的一个分布式流平台,现在是Apache的顶级项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,以Pull的形式消费消息。具有以下特性:快速持久化,可以在O(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现复杂均衡。因为设计之初是作为日志流平台和运营消息管道平台,所以实现了消息顺序和海量堆积。

Kafka自身服务与消息的生产和消费都依赖与Zookeeper,使用Scala语言开发。因为其消息的消费使用客户端Pull方式,消息可以被多个客户端消费,理论上消息会重复,但是不会丢失(除非消息过期)。因此比较常用的场景是作为日志传输的消息平台。

##RocketMQ
RocketMQ是阿里开源的消息中间件,目前在Apache孵化,使用纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是简单的复制,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景,支撑了阿里多次双十一活动。

因为是阿里内部从实践到产品的产物,因此里面很多接口、api并不是很普遍适用。其可靠性毋庸置疑,而且与Kafka一脉相承(甚至更优),性能强劲,支持海量堆积。不过据说,没有在mq核心上实现JMS,但是也无伤大雅。

##Apollo
Apache称Apollo为最快、最强健的STOMP服务器。支持STOMP、AMQP、MQTT、OpenWire协议,支持Topic、Queue、持久订阅等消费形式,支持对消息的多种处理,支持安全性处理,支持REST管理API。。。功能列表很长,最大的弊病就是目前市场接收度不够,所以使用的并不广泛。

基本数据结构的存储大小

  • byte 1字节
  • short 2字节
  • int 4字节
  • long 8字节
  • char 2字节(C语言中是1字节)可以存储一个汉字
  • float 4字节
  • double 8字节
  • boolean false/true(理论上占用1bit,1/8字节,实际处理按1byte处理)

抽象类与接口

####抽象类接口区别和相同点

  • 接口和抽象类都不能实例化
  • 抽象类可以有构造方法,接口中不能有构造方法。
  • 抽象类中可以有普通成员变量,接口中没有普通成员变量
  • 抽象类中可以包含非抽象的普通方法,接口中的可以有非抽象方法,比如deafult方法
  • 抽象类中的抽象方法的访问类型可以是public,protected,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
  • 抽象类中可以包含静态方法,接口中不能包含静态方法
  • 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
  • 一个类可以实现多个接口,但只能继承一个抽象类。

####java抽象类和普通类的区别
1.抽象类不能被实例化。

2.抽象类可以有构造函数,被继承时子类必须继承父类一个构造方法,抽象方法不能被声明为静态。

3.抽象方法只需申明,而无需实现,抽象类中可以允许普通方法有主体

4.含有抽象方法的类必须申明为抽象类

5.抽象的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类。

####抽象类里面的方法子类必须全部实现吗,可不可以有不实现的方法,接口呢?

  • 抽象类不一定,子类只会实现父类里的抽象方法,抽象类里可以有抽象方法也可以非抽象方法,子类不需要再去实现非抽象方法,如果子类一定要再次实现的话就叫做覆盖了
  • 接口里的方法必须全部实现,因为接口里的方法都是抽象的,默认都是public abstract

进程线程的区别:进程是资源分配的基本单位,线程是处理器调度的基本单位

线程池

构造器中的参数

  • corePoolSize:核心池的大小
  • maximumPoolSize:线程池最大线程数
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
  • unit 参数keepAliveTime的时间单位
  • workQueue 一个阻塞队列,用来存储等待执行的任务

    1. ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
    2.LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
    3.SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
    4.PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
    
  • threadFactory:线程工厂

  • handler:表示当拒绝处理任务时的策略,有以下四种取值:

    1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
    2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
    3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 
    

HashMap

JDK 1.7

ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成

Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是上面的提到的锁分离技术,而每一个Segment元素存储的是HashEntry数组+链表,这个和HashMap的数据存储结构一样

put()方法

从上Segment的继承体系可以看出,Segment实现了ReentrantLock,也就带有锁的功能,当执行put操作时,会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后进行第二次hash操作,找到相应的HashEntry的位置,这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(链表的尾端),会通过继承ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒

SIZE()方法

第一种方案他会使用不加锁的模式去尝试多次计算ConcurrentHashMap的size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的
第二种方案是如果第一种方案不符合,他就会给每个Segment加上锁,然后计算ConcurrentHashMap的size返回

JDK1.8

而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本

put()方法

这个put的过程很清晰,对当前的table进行无条件自循环直到put成功,可以分成以下六步流程来概述

  1. 如果没有初始化就先调用initTable()方法来进行初始化过程
  • 如果没有hash冲突就直接CAS插入
  • 如果还在进行扩容操作就先进行扩容
  • 如果存在hash冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入,
  • 最后一个如果该链表的数量大于阈值8,就要先转换成黑红树的结构,break再一次进入循环
  • 如果添加成功就调用addCount()方法统计size,并且检查是否需要扩容

####版本比较
其实可以看出JDK1.8版本的ConcurrentHashMap的数据结构已经接近HashMap,相对而言,ConcurrentHashMap只是增加了同步的操作来控制并发,从JDK1.7版本的ReentrantLock+Segment+HashEntry,到JDK1.8版本中synchronized+CAS+HashEntry+红黑树,相对而言,总结如下思考

  1. JDK1.8的实现降低锁的粒度,JDK1.7版本锁的粒度是基于Segment的,包含多个HashEntry,而JDK1.8锁的粒度就是HashEntry(首节点)
  • JDK1.8版本的数据结构变得更加简单,使得操作也更加清晰流畅,因为已经使用synchronized来进行同步,所以不需要分段锁的概念,也就不需要Segment这种数据结构了,由于粒度的降低,实现的复杂度也增加了
  • JDK1.8使用红黑树来优化链表,基于长度很长的链表的遍历是一个很漫长的过程,而红黑树的遍历效率是很快的,代替一定阈值的链表,这样形成一个最佳拍档
  • JDK1.8为什么使用内置锁synchronized来代替重入锁ReentrantLock,我觉得有以下几点
    1. 因为粒度降低了,在相对而言的低粒度加锁方式,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可能通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了
    • JVM的开发团队从来都没有放弃synchronized,而且基于JVM的synchronized优化空间更大,使用内嵌的关键字比使用API更加自然
    • 在大量的数据操作下,对于JVM的内存压力,基于API的ReentrantLock会开销更多的内存,虽然不是瓶颈,但是也是一个选择依据

Spring IOC

而IOC的思想是:Spring容器来实现这些相互依赖对象的创建、协调工作。对象只需要关系业务逻辑本身就可以了。从这方面来说,对象如何得到他的协作对象的责任被反转了(IOC、DI)。

IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。

那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。

Spring AOP

####AOP的作用

  在OOP中,正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增加。AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

####AOP面向切面编程

  1. 通知、增强处理(Advice) 就是你想要的功能,也就是上说的安全、事物、日子等。你给先定义好,然后再想用的地方用一下。包含Aspect的一段处理代码

  2. 连接点(JoinPoint) 这个就更好解释了,就是spring允许你是通知(Advice)的地方,那可就真多了,基本每个方法的钱、后(两者都有也行),或抛出异常是时都可以是连接点,spring只支持方法连接点。其他如AspectJ还可以让你在构造器或属性注入时都行,不过那不是咱们关注的,只要记住,和方法有关的前前后后都是连接点。

  3. 切入点(Pointcut) 上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有十几个连接点了对吧,但是你并不想在所有方法附件都使用通知(使用叫织入,下面再说),你只是想让其中几个,在调用这几个方法之前、之后或者抛出异常时干点什么,那么就用切入点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

  1. 切面(Aspect) 切面是通知和切入点的结合。现在发现了吧,没连接点什么事,链接点就是为了让你好理解切点搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的befor,after,around等就能知道),二切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
  1. 引入(introduction) 允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗

  2. 目标(target) 引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咋们织入切面。二自己专注于业务本身的逻辑。

  3. 代理(proxy) 怎么实现整套AOP机制的,都是通过代理,这个一会儿给细说。

  1. 织入(weaving) 把切面应用到目标对象来创建新的代理对象的过程。有三种方式,spring采用的是运行时,为什么是运行时,在上一文《Spring AOP开发漫谈之初探AOP及AspectJ的用法》中第二个标提到。

AOP是目前Spring框架中的核心之一,在应用中具有非常重要的作用,也是Spring其他组件的基础。它是一种面向切面编程的思想。关于AOP的基础知识,相信多数童鞋都已经了如指掌,我们就略过这部分,来讲解下AOP的核心功能的底层实现机制:如何用动态代理来实现切面拦截。

AOP的拦截功能是由java中的动态代理来实现的。说白了,就是在目标类的基础上增加切面逻辑,生成增强的目标类(该切面逻辑或者在目标类函数执行之前,或者目标类函数执行之后,或者在目标类函数抛出异常时候执行。不同的切入时机对应不同的Interceptor的种类,如BeforeAdviseInterceptor,AfterAdviseInterceptor以及ThrowsAdviseInterceptor等)。

AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理 和 cglib动态代理。两种方法同时存在,各有优劣。jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。总的来说,反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理的应用前提,必须是目标类基于统一的接口。如果没有上述前提,jdk动态代理不能应用。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。

AOP编程(即面向切面编程,说白了就是动态代理,我们经常会交给Spring一个对象,它就会返回代理对象给我们,它在返回代理对象的时候,首先会检查我们这个对象有没有实现一个接口,如果我们这个类有接口,它使用Java的动态代理技术来帮我们构建出代理对象;如果我们这个类没有实现接口,它会使用CGLIB这套API,采用创建子类的方式来创建代理对象)。

#数据库事务

####属性ACID(acid,酸)
事务具有ACID(四个单词的首字母)属性:

  1. 原子性(Atomicity):事务包含的操作应该作为一个单元执行,要么都成功、要么都失败。
  2. 一致性(Consistency):事务完成后,数据应该处于一致的状态。例如,如果转账,则要么两个账户都更新,要么,失败的情况下,两个账户都不更新。
  3. 隔离性(Isolation):事务之间应该独立,不相互影响,并发事务的操作应该隔离开,如如果一个事务在修改数据,一个在读取相同的数据,则应该保证在修改前或修改后读取数据,而不可以在修改过程中读取。
  4. 持久性(Durability):事务对数据的修改在系统中永久有效,会保持下去,如对余额的修改会持久在数据库中。

####事务并发会导致的问题

  1. 脏读:当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。
  2. 不可重复读,是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
  3. 幻读, 是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

####事务的隔离级别

  1. READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重复读和幻读问题。
  2. READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,可避免脏读,仍会出现不可重复读和幻读问题。

  3. REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。

  4. SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。

#JVM

  • 程序计数器(Program Counter Register) 是一块较小的内存区域,作用可以看做是当前线程执行的字节码的位置指示器。分支、循环、跳转、异常处理和线程恢复等基础功能都需要依赖这个计算器来完成

  • Java栈(VM Stack) Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。

  • 本地方法栈(Native Method Stack) 本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的。
  • 方法区(Method Area) 方法区在JVM中也是一个非常重要的区域,它与堆一样,是被线程共享的区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。在方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
  • 堆(Heap) Java中的堆是用来存储对象本身的以及数组(当然,数组引用是存放在Java栈中的)。

概括地说来,JVM初始运行的时候都会分配好Method Area(方法区)和Heap(堆),而JVM 每遇到一个线程,就为其分配一个Program Counter Register(程序计数器), VM Stack(虚拟机栈)和Native Method Stack (本地方法栈),当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。

深信服一面

发表于 2018-07-21
  • shuffle是什么
  • spark 和hadoop的区别
  • RDD是什么
  • shuffle的过程
  • python的gc
  • java的gc
  • java的虚拟机
  • 象棋马如何从一个点到另一个点
  • hashmap 和 hashtable的区别,底层数据结构,并发
  • python的dict底层如何实现
  • 红黑树
  • 从无头无尾的链表中删除当前节点
  • 给一篇英文文章,做分词处理,取top10,文章很大如何解决
  • linux下的进程共享方式,如果是大数据传输怎么办
  • 什么是Linux下的僵尸进程
123…6

sujunwei

53 日志
5 分类
11 标签
© 2018 true
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4