技术标签: jvm 学习 面试 Java java 编程语言
Hash Map采用由数组+链表+红黑树的存储方式。
采用Entry数组来存储key-value对,每一个键值对组成了一个Entry实体,Entry类实际上是一个单向的链表结构,它具有Next指针,可以连接下一个Entry实体,以此来解决Hash冲突的问题。
数组的特点:寻址容易,插入和删除困难。存储空间紧凑,不适合存储稀疏数据。
链表特点:插入和删除简单,寻址困难,单位存储空间比数组高,但是适合存储稀疏数据。
所以当链表长度超过阈值(8)时,将链表转换为红黑树。在性能上进一步得到提升。
private LazySingletonFileSystem() {
}
/**
* the local filesystem instance only one,unless used reflect
*
* @return a local filesystem,the filesystem is singleton
* @throws IOException IOException
*/
public static FileSystem getLocalFs() throws IOException {
if (localFs == null) {
synchronized (LazySingletonFileSystem.class) {
if (localFs == null) {
localFs = FileSystem.getLocal(new Configuration());
}
}
}
return localFs;
}
提供一个单例模式以供参考,这是双检锁(double check)的单例模式,解决了对象的全局唯一性,即该方法提供的所有本地文件系统对象都是单例的。
本质原因是创建了太多的线程,导致异常的发生(能创建的线程数是有限制的)。能创建的线程数的计算公式:
(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads
MaxProcessMemory 指的是一个进程的最大内存
JVMMemory JVM内存
ReservedOsMemory 保留的操作系统内存
ThreadStackSize 线程栈的大小
如果程序中有bug,导致创建大量不需要的线程或者线程没有及时回收,那么必须解决这个bug,修改参数是不能解决问题的。
如果程序确实需要大量的线程,现有的设置不能达到要求。
通过修改MaxProcessMemory,JVMMemory,ThreadStackSize这三个因素,来增加能创建的线程数:
修改系统的最大进程数。
// 线性表
class LineTable{
int[] table;
int index = 0;
public LineTable(int len){
table = new int[100];
}
// 增加一个元素,如果满了,不接受其它元素
public void add(int num){
if(index<table.length){
table[index ++] = num;
}
}
// 从顶端删除元素,如果空了,不再删除元素。
public void remove(){
if(index > 0){
index --;
}
}
}
// 链表:使用哨兵
class LinkedTable{
private class Node{
int value;
Node next;
public Node(int num){
value = num;
}
}
Node head, tail;
public LinkedTable(){
head = tail = new Node();
}
// 尾插法,在链表尾插入一个节点。
public void add(int num){
tail.next = new Node(num);
tail = tail.next;
}
// 头删法,从头部开始删除。
public void remove(){
if(head.next != null){
head.next = head.next.next;
}
}
}
上述仅供参考
有方法区、虚拟机栈、本地方法栈、堆、程序计数器等5个区。
和虚拟机栈类似,为本地方法服务。
是所有线程共享的一块内存,在虚拟机启动时创建,几乎所有对象实例都在这里创建,这里经常发生垃圾回收操作
内存空间小,各种指令发生的跳转都依赖这个计数器完成。是唯一一个java虚拟机规范没有任何OOM情况的区域。
集合是将所有数据加载到内存,然后通过集合的方法去内存中获取,而迭代器是一个对象,实现了Iterator接口,实现了接口的hasNext和next方法。
集合是一种数据结构,集合里面的数据无序且不重复。
使用迭代器可以遍历集合中的元素。
HashMap 是线程不安全的。
HashTable 是线程安全的,原因是HashTable自身使用了 synchronized关键字进行了同步处理。
在多线程并发的情况下,可以直接使用 HashTable,而使用 HashMap 时必须自己增加同步处理。
HashMap 只有 containsValue 和 containsKey 方法;HashTable 有 contains、containsKey和 containsValue 三个方法,其中 contains 和 containsValue 方法功能相同。
Hashtable 中,key 和 value 都不允许出现 null 值。
HashMap 中,null 作为键,这样的键只有一个。
作为值,可以有一个或多个键所对应的值为 null。
HashTable 在不指定容量的情况下的默认容量为 11,而 HashMap 为 16。
Hashtable 不要求底层数组的容量一定要为 2 的整数次幂,而 HashMap 则要求一定为 2 的整数次幂。
Hashtable 扩容时,将容量变为原来的 2 倍加 1,而 HashMap 扩容时,将容量变为原来的 2 倍。
线程池分为单线程线程池,固定大小线程池,可缓冲的线程池。
corePoolSize : 核心线程数量
workQueue : 等待队列
maximumPoolSize : 最大线程数量
提交任务时,判断的顺序为 corePoolSize --> workQueue -->maximumPoolSize。
当线程数小于核心线程数时,创建核心线程。
当线程大于等于核心线程数,且任务队列未满时,将任务放入队列。
当线程数大于核心线程数,且任务队列已满时,检查最大线程数是否已满,若未满,创建非核心线程,若满,根据拒绝策略抛出异常拒绝任务。
AbortPolicy : 直接抛出异常,这是默认策略
CallerRunsPolicy : 用调用者所在线程来执行任务
DiscardOldestPolicy : 丢弃阻塞队列中最靠前的任务,并执行当前任务
DiscardPolicy : 直接丢弃任务
任务进入了队列,线程还在执行之前的任务。提交的任务还在排队等待执行中
TreeMap会自动进行排序。
根据key的Compare方法进行排序。
线程安全(还简单易实现)的单例模式一般有2种,双重检锁(double check)和饿汉式。
双重检锁的实现相对较复杂,详看第2问的设计模式。
饿汉式通过静态变量提前加载对象,简单易实现,缺点是这个对象从类加载开始会一直存在,可能会比较消耗内存。
// 求n的阶乘
public int fac(int n) {
if (n == 1) {
return 1;
}
// n*(n-1)*...*1
return n * fac(n - 1);
}
HashSet 是采用 hash 表来实现的。其中的元素没有按顺序排列,add()、remove()以及contains()等方法都是复杂度为 O(1)的方法。
TreeSet 是采用树结构实现(红黑树算法)。元素是按顺序进行排列,但是 add()、remove()以及 contains()等方法都是复杂度为 O(log (n))的方法。它还提供了一些方法来处理排序的 set,如 first(),last(),headSet(),tailSet()等等。
ps:StringBuffer 与 StringBuilder 中的方法和功能完全是等价的。
修饰符(关键字)有三种用法:修饰类、变量和方法。
修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和 abstract 是反义词。
修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。
修饰方法时,也同样只能使用,不能在子类中被重写。
通常放在 try…catch 的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要 JVM 不关闭都能执行,可以将释放外部资源的代码写在finally 块中。
Object 类中定义的方法,Java 中允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写 finalize() 方法可以整理系统资源或者执行其他清理工作。
如果比较的是基本数据类型,那么比较的是变量的值
如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内
存)
如果没重写 equals 方法比较的是两个对象的地址值。
如果重写了 equals 方法后我们往往比较的是对象中的属性的内容
ps:equals 方法是从 Object 类中继承的,默认的实现就是使用==
以动态数组的形式保存对象,将对象放在连续的位置上。
随机读的时间复杂度O(1)
插入的时间复杂度O(n)。
以双向链表的形式保存对象,将每个对象串联起来。
随机读的时间复杂度O(n)
插入的时间复杂度O(1)。
Java类加载需要经历以下几个过程:
验证的目的是为了确保Class文件的字节流中的信息不会危害到虚拟机。在该阶段主要完成以下四种验证:
为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。
类加载的最后一步,前面的类加载过程,出了加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序代码。
通过一系列的名为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所有走过的路径成为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时(用图论来说就是GC Root到这个对象不可达时),证明该对象是可以被回收的。
Java可以成为GC Root的对象:
这是垃圾收集算法中最基础的,根据名字就可以知道,它的思想就是标记那些要被回收的对象,然后统一回收。这种方法很简单,但是会有两个主要问题:
为了解决效率问题,复制算法将可用内存按容量划分相等的两部分,然后每次只使用其中的一块,当第一块内存用完时,就将还存活的对象复制到第二块内存上,然后一次性清除完第一块内存,在将第二块上的对象复制到第一块。但是这种方式,内存的代价太高,每次基本上都要浪费一块内存。
于是将该算法进行了改进,内存区域不再是按照1:1去划分,而是将内存划分为8:1:1三部分,较大的那份内存叫Eden区,其余两块较小的内存叫Survivor区。每次都会先使用Eden区,若Eden区满,就将对象赋值到第二块内存上,然后清除Eden区,如果此时存活的对象太多,以至于Survivor不够时,会将这些对象通过分配担保机制赋值到老年代中。(java堆又分为新生代和老年代)。
该算法是为了解决标记-清除,产生大量内存碎片的问题;当对象存活率较高时,也解决了复制算法的效率问题。
标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。这样就不会产生内存碎片。
根据对象的存活周期的不同将内存划分为几块,一般就分为新生代和老年代,根据各个年代的特点采用不同的收集算法。新生代(少量存活)用复制算法,老年代(对象存活率高)“标记-整理”算法。
介绍:给对象添加一个引用计数器,每当一个地方引用它时,数据器加1;当引用失效时,计数器减1;计数器为0的即可被回收。
优点:实现简单,判断效率高。
缺点:很难解决对象之间的相互循环引用(objA.instance = objB; objB.instance = objA)的问题,所以java语言并没有选用引用计数法管理内存。
整个JVM内存总共划分为三代:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)
判断对象是否存活的算法包括:
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器加1。
当引用失效时,计数器值减1。
任何时刻计数器为0的对象就是不能再被引用的。
ps:例如Object-C,Python语音使用引用计数算法进行内存管理。Java虚拟机没有选用引用计数器算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。
可达性分析算法的基本思路是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则证明此对象是不可用的。
经过根搜索算法的可达性分析某个对象没有和GC Roots的直接或间接联系(不可达),并不意味着该对象将被回收,还需要经过两次标记和筛选工作,才能决定是否可以回收对象。
第一次:判断是否有finalize方法或者执行过finalize,如果没有finalize方法或者已经执行过finalize方法,不需要进行筛选则可以回收,否则需要进行筛选进行第二次标记和筛选。
第二次:执行对象的finalize方法,执行完成或者执行过程中判断对象是否和GC Roots是否有直接或者间接联系,如果依然没有联系则把对象放入回收列表等待回收,否则对象复活。
堆内存用来存放由new创建的对象和数组。
在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。
在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。
当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。
文章浏览阅读4.7k次。前端实现多次调用同一个接口,所有数据均成功返回后,才可继续执行下面的代码封装请求数据方法getData(param) { return new Promise((resolve, reject) => { this.$axios .get(`/xx/xx/xxpath/${param}`) .then(res => { let content = res.data reso_promise.all并行调同一个接口
文章浏览阅读274次。参考:《UNIX 网络编程 · 卷1 : 套接字联网API》之前都是使用数值地址来表示主机(比如:127.0.0.1),用数值端口号来标识服务器(比如:6379)。但是有时候最好使用名字而不是数值:名字比较容易记住,数值地址容易变动,而名字地址保持不变;随着 IPv6 上转移,数值地址变得很长,手工键入数值容易出错。之后将有一系列函数用于名字、数值、端口之间的转换。gethostbyname & gethostbyaddr 函数gethostbyname 函数查找主机名字最基本的函数时 g_getservbyname是可重入函数吗
文章浏览阅读436次。JavaSE结构•Java概述•Java编程基础•面向对象编程•异常处理•API常用类•多线程•容器类•I/O•网络编程•注解1 Java概述结构•1.1 软件编程常识•1.2 Java语言概述•1.3 Java体系结构•1.4 Java语言的跨平台特性•1.5 搭建Java程序的开发环境•1.6 Java程序开发体验_第 1章java me的概述
文章浏览阅读1w次,点赞6次,收藏11次。在前端开发中,我们可能会碰到这样的需求:想让列表中的第一个部分显示不同的样式 ,想让列表中的偶数部分显示不同的背景颜色,想让列表中的最后一部分样式不一样……这样的需求,我们怎样来实现?其实,如果前面文件是php开发的,可以通过php的循环语句+判断语句+css样式来实现。但是,如果是静态代码,php就无法用了。这时,我们还可以通过CSS来实现,CSS给我们提供了几个非常有用的样式参数:first-..._css 第几个几个div
文章浏览阅读106次。固定资产管理总体分为两个部分:固定资产管理财务帐部分,固定资产管理实物帐部分。前者偏重价值管理,后者偏重实物管理,前者一般由财务部门负责,后者一般由行政部门负责。两部分信息又是紧密衔接相辅相成的,通过信息系统的..._crv管理系统
文章浏览阅读639次。SAP MM 物料主数据MRP2 视图Rounding Value字段如下物料号,MRP2视图中,维护了rounding value字段值为50。MRP type..._sap mpr2视图
文章浏览阅读1.2w次,点赞19次,收藏108次。1.什么是按键消我们先来看一下按键按下去的波形图1.按键消抖原理我们可以看到当按键按下的那一时刻和松开的时候有类似于锯齿的形状那就是按键抖动,这个抖动不是我们人为能控制得了的,所以我们只能对进行硬件消抖或者进行软件消抖本期我们讲解软件消抖.**上图中我们可以看到理想波形和实际波形有很大的区别,区别在于实际波形在按键按下的那一刻前后有20毫秒的抖动,我们按键消抖的目的呢就是把抖动忽略掉只要中间的稳定闭合区域.**## 方法一延时消抖法可以用延时的方式跳过抖动的区域优缺点:优点._单片机按键消抖
文章浏览阅读3.7w次,点赞73次,收藏265次。image2lcd是一款非常简单使用的图片转换成LCD图像数据的图片转换软件。它能够将各种形式来源的图片转换成特定的数据格式以用来匹配单片机系统所需要的显示数据格式。在输入方面,它支持JPG、BMP、EMF、WBMP、GIF、ICO等多种格式图片的输入,输出的数据拥有二进制类型、WBMP格式、C语言数组类型和标准的BMP格式等多种类型。同时它还能将图象的数据扫描方式、亮度、对比度、灰度(颜色数)以及图像数据排列方式等等进行调节。image2lcd v3.2破解版image2lcd是一款非常简单使_image2lcd
文章浏览阅读1.9k次。转自:http://www.infoq.com/cn/articles/deep-understanding-of-tagged-pointer/前言在2013年9月,苹果推出了iPhone5s,与此同时,iPhone5s配备了首个采用64位架构的A7双核处理器,为了节省内存和提高执行效率,苹果提出了Tagged Pointer的概念。对于64位程序,引入Tagged Po_nsnumber的tagged pointer
文章浏览阅读3.7k次。解题思路1. 查看文件信息,安全机制2. 代码审计3. 分析漏洞点4. 编写EXP1.基本信息$checksec ./文件名2.代码审计不管在简单题都一定要IDA查看一下伪代码(IDA做好是7.0以上的版本)首先查看敏感字符串(Ctrl+1)1.gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,以回车结束读取,所以应该确保buffer的空间足够大,以便在执..._buuctf rip
文章浏览阅读7k次。php中curl_init()的作用很大,尤其是在抓取网页内容或文件信息的时候,例如之前文章curl获得header检测GZip压缩的源代码就介绍到curl_init()的强大。curl_init()处理事物是单线程模式,如果需要对事务处理走多线程模式,那么php里提供了一个函数curl_multi_init()给我们,这就是多线程模式处理事务的函数。curl_init()与cur_curl_multi_init c++
文章浏览阅读2.6k次。sys_execve()在真正的开始执行系统调用函数之前,系统调用服务程序已经将一些系统调用的函数的参数传递给了相应的寄存器,比如这里的ebx,ecx,edx都分别保存了系统调用的参数,ebx保存的是第一个参数,依次类推(当然最多传递的参数个数不能大于5个),首先这个函数通过ebx获取需要执行的文件的绝对路径,他通过这样一个函数实现获取到文件名之后他就会调用do_execve();_execve系统调用寄存器参数