19/12/1今日工事.电话面试集合(java)

本文详细解析JVM内存结构,包括程序计数器、本地方法栈、方法区、栈、堆和直接内存的功能与作用。探讨了垃圾回收机制、引用类型、垃圾收集算法以及内存溢出与泄露的诊断方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(1)jvm的内存结构,jvm各区域的作用

1.程序计数器 PC Register

  每个线程都有一个程序计算器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

  程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。由于Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。如果线程正在执行的是一个Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Natvie 方法,这个计数器值则为空(Undefined)。此内存区域是唯一一个在Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

2.本地方法栈 Native Method Stack

  Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies

  本地方法栈与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务

3.方法区  Method Area

  用于存储虚拟机加载的:静态变量+常量+类信息+运行时常量池 (类信息:类的版本、字段、方法、接口、构造函数等描述信息 )

  默认最小值为16MB,最大值为64MB,可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小

  对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”(Permanent Generation),本质上两者并不等价,仅仅是因为HotSpot 虚拟机的设计团队选择把GC 分代收集扩展至方法区,或者说使用永久代来实现方法区而已。对于其他虚拟机(如BEA JRockit、IBM J9 等)来说是不存在永久代的概念的。即使是HotSpot 虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久代并“搬家”至Native Memory 来实现方法区的规划了。Java 虚拟机规范对这个区域的限制非常宽松,除了和Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。在Sun 公司的BUG 列表中,曾出现过的若干个严重的BUG 就是由于低版本的HotSpot 虚拟机对此区域未完全回收而导致内存泄漏。根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。

4.栈 JVM Stack

  编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身)

  栈是java 方法执行的内存模型:

  每个方法被执行的时候 都会创建一个“栈帧”用于存储局部变量表(包括参数)、操作栈、方法出口等信息。

  每个方法被调用到执行完的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

  (局部变量表:存放了编译器可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(引用指针,并非对象本身),

  其中64位长度的long和double类型的数据会占用2个局部变量的空间,其余数据类型只占1个。

  局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间栈帧不会改变局部变量表的大小空间)

  栈的生命期是跟随线程的生命期,线程创建时创建,线程结束栈内存也就释放,是线程私有的。

5.堆  Java Heap

  所有的对象实例以及数组都要在堆上分配,此内存区域的唯一目的就是存放对象实例

  堆是Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建

  堆是理解Java GC机制最重要的区域,没有之一

  结构:新生代(Eden区+2个Survivor区)  老年代   永久代(HotSpot有)

  新生代:新创建的对象——>Eden区 

  GC之后,存活的对象由Eden区 Survivor区0进入Survivor区1   

  再次GC,存活的对象由Eden区 Survivor区1进入Survivor区0 

  老年代:对象如果在新生代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到老年代

  如果新创建对象比较大(比如长字符串或大数组),新生代空间不足,则大对象会直接分配到老年代上(大对象可能触发提前GC,应少用,更应避免使用短命的大对象)

  老年代的空间一般比新生代大,能存放更多的对象,在老年代上发生的GC次数也比年轻代少

  永久代:可以简单理解为方法区(本质上两者并不等价)

  如上文所说:对于习惯在HotSpot 虚拟机上开发和部署程序的开发者来说,很多人愿意把方法区称为“永久代”,本质上两者并不等价

  仅仅是因为HotSpot 虚拟机的设计团队选择把GC 分代收集扩展至方法区,或者说使用永久代来实现方法区而已

  对于其他虚拟机(如BEA JRockit、IBM J9 等)来说是不存在永久代的概念的

  即使是HotSpot 虚拟机本身,根据官方发布的路线图信息,现在也有放弃永久代并“搬家”至Native Memory 来实现方法区的规划了

  Jdk1.6及之前:常量池分配在永久代

  Jdk1.7:有,但已经逐步“去永久代”

  Jdk1.8及之后:没有永久代(java.lang.OutOfMemoryError: PermGen space,这种错误将不会出现在JDK1.8中)

6.直接内存  Direct Memor

  直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存

  JDK中有一种基于通道(Channel)和缓冲区(Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用

  由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。

(2)object类的方法

1.clone方法
保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

2.getClass方法
final方法,获得运行时类型。

3.toString方法
该方法用得比较多,一般子类都有覆盖。

4.finalize方法
该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

5.equals方法
该方法是非常重要的一个方法。一般equals和==是不一样的,但是在Object中两者是一样的。子类一般都要重写这个方法。

6.hashCode方法
该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

一般必须满足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就满足equals。不过为了提高效率,应该尽量使上面两个条件接近等价。

7.wait方法
wait方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。wait(long timeout)设定一个超时间隔,如果在规定时间内没有获得锁就返回。

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

(1)其他线程调用了该对象的notify方法。

(2)其他线程调用了该对象的notifyAll方法。

(3)其他线程调用了interrupt中断该线程。

(4)时间间隔到了。

此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

8.notify方法
该方法唤醒在该对象上等待的某个线程。

9.notifyAll方法
该方法唤醒在该对象上等待的所有线程。

(3)如何判断哪些内存应该回收哦

引用计数法:如果有地方引用该对象,该对象的引用计数就+1,如果引用失效的话就减一。计数器为0的对象不可以被使用。

答案是不行的。试想一下,如果有两个对象互相引用,比如objA.instance = objB, objB.instance = objB,这个时候两个对象都不能被访问,但是互相引用导致引用计数不为0,这不就无法判定为死亡了吗?我们如果是GC,能允许这种长生不死的存在吗?肯定不。所以引用计数法并没有被采用在目前的JVM垃圾回收器中。

可达性分析法:如果我们将一些GC Roots对象作为起始点,从这些节点向下搜索,搜索到的路径为引用链,如果有一些对象没有任何引用链相连,那么这个对象对于GC Roots是不可达的,即使它们之间可能相互产生关联,所以将其判定为可回收对象。如下图:

GC Roots:

虚拟机栈(栈帧中的本地变量表)中引用的对象

方法区中类静态属性引用的对象

方法区中常量引用的对象

本地方法栈中JNI(即一般说的Native方法)引用的对象

(4)什么是引用

jdk1.2之前,定义为:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就成为这块内存代表着一个引用。

那么我们好像对于那种如果希望内存足够的时候保留,不够的时候回收的对象一个十分明确的解释。

jdk1.2之后,扩展为:

强引用:只要存在,垃圾收集器就不会回收对象。

Object obj = new Object();之类

软引用:用来描述一些还有用但是不必须的对象,系统将要发生内存溢出异常之前,将会把这些对象列入回收范围之中进行第二次回收,如果还是不够那就只能抛出内存溢出的异常了。

SoftReference<String>s = new SoftReference<>(“我还有用但不是必须的!”);

弱引用:用来描述非必须对象,但是强度比弱引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,垃圾收集工作的时候,无论是否必要都会回收掉只被弱引用关联的对象。

WeakReference<String>s = new WeakReference<String>(“我只能活到下一次垃圾收集之前”);

虚引用(幽灵引用或幻影引用):一个对象是否有虚引用,与其生命周期毫无关系,也无法通过虚引用取得一个对象实例,只被虚引用的对象,随时都会被回收掉

PhantomReference<String>ref = new PhantomReference<String>(“我只能接受死亡通知”) , targetReferenceQueue<String>);

(5)垃圾收集算法

标记-清扫(Mark-and-sweep)—sun前期版本就是用这个技术。 原理:对于“活”的对象,一定可以追溯到其存活在堆栈、静态存储区之中的引用。这个引用链条可能会穿过数个对象层次。第一阶段:从GC roots开始遍历所有的引用,对有活的对象进行标记。第二阶段:对堆进行遍历,把未标记的对象进行清除。这个解决了循环引用的问题。 缺点:1、暂停整个应用;2、会产生内存碎片。

复制(copying) 原理:为了提升效率,把内存空间划分为2个相等的区域,每次只使用一个区域。垃圾回收时,遍历当前使用区域,把正在使用的对象复制到另外一个区域。优点:不会出现碎片问题。 缺点:1、暂停整个应用。2、需要2倍的内存空间。

标记-整理(Mark-Compact) 原理:第一阶段标记活的对象,第二阶段把为标记的对象压缩到堆的其中一块,按顺序放。即将所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。优点:1、避免标记扫描的碎片问题;2、避免停止复制的空间问题。 具体使用什么方法GC,Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率低的话,就切换到“标记-扫描”方式;同样,Java虚拟机会跟踪“标记-扫描”的效果,要是堆空间碎片出现很多碎片,就会切换回“停止-复制”模式。这就是自适应的技术。

分代(generational collecting)—–J2SE1.2以后使用此算法 原理:基于对象生命周期分析得出的垃圾回收算法。把对象分为年轻代、年老代、持久代,对不同的生命周期使用不同的算法(2-3方法中的一个即4自适应)进行回收。

新生代:每次垃圾收集都有大量对象死去,只有少量存活,就选择复制算法

老年代:对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记-清理”,或者“标记-整理”算法来进行回收。

自适应算法(Adaptive Collector)在特定的情况下,一些垃圾收集算法会优于其它算法。基于Adaptive算法的垃圾收集器就是监控当前堆的使用情况,并将选择适当算法的垃圾收集器。

(6)发生内存溢出的区域

Java堆用于存储对象实例,我们只要不断创建对象,并保证GC Roots到对象之间有可达路径来避免垃圾回收机制清楚这些对象。
要解决这个区域的异常,一般都需要使用一些内存映射分析工具(如 Eclipse Memory Analyzer)

虚拟机栈和本地方法栈溢出:如果线程请求深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
实验结果表明:在单线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,虚拟机都是抛出StackOverflowError异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

本机直接内存溢出

java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。
java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。
java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小

(7)内存泄露的查找方式

一:tomcat配置jconsole远程连接
二:jconsole工具远程连接
三:jmap命令导出堆内存使用情况
四:MAT图形化查看堆内存使用情况

(8)java集合类型

Java API中所用的集合类,都是实现了Collection接口,他的一个类继承结构如下: 
Collection<–List<–Vector 
Collection<–List<–ArrayList 
Collection<–List<–LinkedList 
Collection<–Set<–HashSet 
Collection<–Set<–HashSet<–LinkedHashSet 
Collection<–Set<–SortedSet<–TreeSet 
Vector : 基于Array的List,其实就是封装了Array所不具备的一些功能方便我们使用,它不可能走入Array的限制。性能也就不可能超越Array。所以,在可能的情况下,我们要多运用Array。另外很重要的一点就是Vector“sychronized”的,这个也是Vector和ArrayList的唯一的区别。 
ArrayList:同Vector一样是一个基于Array上的链表,但是不同的是ArrayList不是同步的。所以在性能上要比Vector优越一些,但是当运行到多线程环境中时,可需要自己在管理线程的同步问题。 
LinkedList:LinkedList不同于前面两种List,它不是基于Array的,所以不受Array性能的限制。它每一个节点(Node)都包含两方面的内容:1.节点本身的数据(data);2.下一个节点的信息(nextNode)。所以当对LinkedList做添加,删除动作的时候就不用像基于Array的List一样,必须进行大量的数据移动。只要更改nextNode的相关信息就可以实现了。这就是LinkedList的优势。 
List总结: 
1. 所有的List中只能容纳单个不同类型的对象组成的表,而不是Key-Value键值对。例如:[ tom,1,c ]; 
2. 所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ]; 
3. 所有的List中可以有null元素,例如[ tom,null,1 ]; 
4. 基于Array的List(Vector,ArrayList)适合查询,而LinkedList(链表)适合添加,删除操作。 
HashSet:虽然Set同List都实现了Collection接口,但是他们的实现方式却大不一样。List基本上都是以Array为基础。但是Set则是在HashMap的基础上来实现的,这个就是Set和List的根本区别。HashSet的存储方式是把HashMap中的Key作为Set的对应存储项。

集合类型主要有3种:set(集)、list(列表)和map(映射)。

1、List(有序、可重复)

List里存放的对象是有序的,同时也是可以重复的,List关注的是索引,拥有一系列和索引相关的方法,查询速度快。因为往list集合里插入或删除数据时,会伴随着后面数据的移动,所有插入删除数据速度慢。

2、Set(无序、不能重复)

Set里存放的对象是无序,不能重复的,集合中的对象不按特定的方式排序,只是简单地把对象加入集合中。

3、Map(键值对、键唯一、值不唯一)

Map集合中存储的是键值对,键不能重复,值可以重复。根据键得到值,对map集合遍历时先得到键的set集合,对set集合进行遍历,得到相应的值

(9)equals()和==的区别

一、对象类型不同
1、equals():是超类Object中的方法。

2、==:是操作符。

二、比较的对象不同

1、equals():用来检测两个对象是否相等,即两个对象的内容是否相等。

2、==:用于比较引用和比较基本数据类型时具有不同的功能。

三、运行速度不同

1、equals():没有==运行速度快。

2、==:运行速度比equals()快,因为==只是比较引用。

(10)char和varchar的区别

1. char类型的长度是固定的,varchar的长度是可变的。

   这就表示,存储字符串'abc',使用char(10),表示存储的字符将占10个字节(包括7个空字符)

              使用varchar(10),,则表示只占3个字节,10是最大值,当存储的字符小于10时,按照实际的长度存储。

2.char类型的效率比varchar的效率稍高

(11)数据库三范式

第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项。

第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。

第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

(12)索引定义,类别,创建

定义
索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。

类型
1. 普通索引
这是最基本的索引,它没有任何限制,比如上文中为title字段创建的索引就是一个普通索引,MyIASM中默认的BTREE类型的索引,也是我们大多数情况下用到的索引。
CREATE INDEX indexName ON mytable(username(length));
2. 唯一索引
与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值(注意和主键不同)。如果是组合索引,则列值的组合必须唯一,创建方法和普通索引类似。
CREATE UNIQUE INDEX indexName ON mytable(username(length))
3. 全文索引(FULLTEXT)
MySQL从3.23.23版开始支持全文索引和全文检索,FULLTEXT索引仅可用于 MyISAM 表;他们可以从CHAR、VARCHAR或TEXT列中作为CREATE TABLE语句的一部分被创建,或是随后使用ALTER TABLE 或CREATE INDEX被添加。////对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,然后创建索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个非常消耗时间非常消耗硬盘空间的做法。

4. 单列索引、多列索引
多个单列索引与单个多列索引的查询效果不同,因为执行查询时,MySQL只能使用一个索引,会从多个索引中选择一个限制最为严格的索引。

5. 组合索引(最左前缀)
平时用的SQL查询语句一般都有比较多的限制条件,所以为了进一步榨取MySQL的效率,就要考虑建立组合索引。例如上表中针对title和time建立一个组合索引:ALTER TABLE article ADD INDEX index_titme_time (title(50),time(10))。建立这样的组合索引,其实是相当于分别建立了下面两组组合索引:
–title,time
–title

(13)sql注入的防范

1、 普通用户与系统管理员用户的权限要有严格的区分。
2、 强迫使用参数化语句。
3、 加强对用户输入的验证。
4、 多多使用SQL Server数据库自带的安全参数。
5、 多层环境如何防治SQL注入式攻击?
6、 必要的情况下使用专业的漏洞扫描工具来寻找可能被攻击的点。
7、设置陷阱账号

hashMap的寻址方式,hashMap扩容算法,equels和==的区别

hashmap的容量是16,hashcode值是2018,怎么存放

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值