JVM二三章面试点

类加载:

jdk编译类文件,jre把class文件存储到内存中;

就是把类文件从磁盘加载到内存中。

运行时常量池

运行时常量池是方法区的 一部分;

运行时常量池存放类信息,方法区相当于大数组,数组里在这个索引区域划分常量池,不同的类信息存在常量池中,相当于存在数据的这一块,那一块,根据不同的区域,把数组不断的切割存储存储他们。

常量池:存在那里之后不会发生改变,就不会变了。

运行时常量池相对于class文件常量池的区别在于具备动态性

直接内存

直接内存是操作系统内存,假设内存条是32个g,各个应用程序占了4个g,还有28个g,直接内存是指剩下的28g,指的是操作系统的内存。

不管是哪一种高级语言最后都是要调动操作系统内核的方法都是要消耗内存,我们的高级语言都会转化成C和汇编,我们的方法也会转化成C和汇编

操作系统的硬件只识别汇编;但是硬件里加了存储功能,也支持C语言的识别,操作系统的内核是调动驱动,驱动大部分是调动汇编,但是有的驱动可以调动C语言。

JVM/HotsPot创建对象的过程:

当Java虚拟机遇到一条字节码new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程;记载通过后为新生对象分配内存,分配内存的时候如果内存规整就用指针碰撞,如果不规整就用空闲列表。

珣哥注意到,内存是否规整由java堆是否带有空间压缩整理的能力决定,因此采用了空间整理过程的收集器,例如Serial、ParNew等,系统分配算法是指针碰撞;而使用CMS这种基于清除算法的收集器时,理论上只能采用空闲列表来分配

清除算法是什么?

就是调整边界,把他标记为无效

对象创建的过程是很频繁的,很容易触发线程安全问题,怎么解决?

第一是对分配内存空间的动作进行同步处理

同步(排队进行)---异步(同时进行)

第二是对内存分配的动作按照线程划分在不同的空间之内进行

对象的内存布局

在hotpot虚拟机中,对象在堆内存中的存储布局可分为三部分:对象头,实例数据,对齐填充;

对象头部包含两类信息:1.存储对象自身运行时的数据

如哈 希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部 分数据的长度在32位和64位的虚拟机(未开启压缩指针)中分别为32个比特和64个比特,官方称它 为“Mark Word”

例如在32位的HotSpot虚拟机中,如对象未被同步锁锁定的状态 下,Mark Word的32个比特存储空间中的25个比特用于存储对象哈希码,4个比特用于存储对象分代年 龄,2个比特用于存储锁标志位,1个比特固定为0

2.类型指针---》对象指向它的类型元数据的指针,java虚拟机通过这个指针来确定该对象是哪个类的实例。

并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话

说,查找对象的元数据信息并不一定要经过对象本身

意味着:在内存中有一种约定,就不需要再记录了这些信息了,可以绕过去。目的就是节省存储空间。

实例部分:对象真正存储的信息,即在程序中所定义的各种类型的字段内容,无论是父类继承的,还是在子类中定义的都必须记录下来。

对齐填充:这并不是必然存在的,仅仅是占位符的作用。

HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8比特的整数倍,对象头部分已经被精心设计成正好是8比特的倍数(1倍或者 2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。

以前创建对象直接在堆里面创建空间,有基本类型,引用类型,方法等等。但是内存里相当于是一个大数组,如下图;在内存里申请空间都是基本类型的数组,一般来说是byte类型的数组。

假设我们申请一个对象(红色的部分)

头部信息,如果是32位电脑存放4字节32比特,如果是64位电脑存放8字节64比特,而64比特中存在很多种信息,所以需要给64比特再重新划分。

对象的访问定位

直接指针访问---》值传递

基本类型存在于栈中,堆中存放对象实例数据,到对象类型数据的指针,方法区包括对象类型数据。

直接指针访问的好处:

速度更快,节省了一次指针定位的时间开销,由于java是面向对象开发,所以对象访问再java很频繁,这种开销减少还是很可观的。

内存溢出和内存泄露

内存溢出:指的是内存不够了

内存泄漏:指的是内存够但是一些碎片化空间无法利用到

例子:假设申请一个数组,需要连续的空间,碎片空间无法使用 这种就是内存泄露。

JAVA堆溢出

java堆用于存储对象实例,只要不断创建对象,并且保证GC Roots到对象之间有可达路径

来避免垃圾回收机制清除这些对象,随着对象数量的增加,触及最大容量会产生内存溢出。

GcRoots

https://www.jianshu.com/p/490f0ff6b504

java虚拟机的堆参数(-Xmx于-Xms)

栈溢出

-Xoss参数(设置本地方法栈大小)

-Xss参数为设置单个线程栈的大小,

栈溢出一般都是递归导致的。无限循环一般会导致栈溢出

1)如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。 栈溢出

2)如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError异常。 堆溢出

在HotSpot虚拟机中,是不支持拓展的,只会因为栈容量无法 容纳新的栈帧而导致StackOverflowError异常,所以不会因为扩展导致内存溢出。

有两种方式可以导致内存溢出

1.Classic虚拟机

2.多线程,在hotSpot中,内存溢出和栈空间没有直接的关系,取决于每个线程的栈分配的内存越大,反而容易产生内存溢出。

如果建立多线程导致内存溢出,不能减少线程数量或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。也是由于这种问题较为隐蔽,从 JDK 7起,以上提示信息中“unable to create native thread”后面,虚拟机会特别注明原因可能是“possiblyout of memory or process/resource limits reached”。

本地方法栈是什么?

操作系统执行方法需要创建栈结构,操作系统的创建的即本地方法栈。jvm里面创建方法也需要创建栈结构,即虚拟机栈

方法区和运行时常量池溢出

方法区溢出:java.lang.OutOfMemoryError: PermGen space

【高频面试点】

String:intern()

String::intern()是一个本地方法,它的作用是如果字符串常量池中已经包含一个等于此String对象的 字符串,则返回代表池中这个字符串的String对象的引用;否则,会将此String对象包含的字符串添加 到常量池中,并且返回此String对象的引用。

方法区溢出的测试:

方法区的主要职责是用于存放类型的相关信息,如类 名、访问修饰符、常量池、字段描述、方法描述等。对于这部分区域的测试,基本的思路是运行时产 生大量的类去填满方法区,直到溢出为止

HopSpot元空间防御参数:

·-XX:MaxMetaspaceSize:设置元空间最大值,默认是-,即不限制,或者说只受限于本地内存 大小

·-XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集 进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放 了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值

-XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可 减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最 大的元空间剩余容量的百分比。

【面试点】

变慢了会因为什么原因?

堆外内存:

java运行的时候调用操作系统内核,所以java在运行的时候除了要消耗jvm之外,肯定还要消耗操作系统的内存。

所以java程序运行慢,大部分都是java内存满了,如果java内存没满,可能是堆外内存满了

流操作对堆外内存消耗很大

异常整理:栈异常,内存溢出异常,方法区异常和运行时常量异常

第三章

垃圾回收:

XXX s = new XXX();现在new了一个对象,在栈里面有一个句柄,堆里有一个对象

s = new XXX();s又指向了另一个对象,先前的对象没有人引用,成为垃圾,该被回收

XXX d =new XXX();

XXX n =d;

d = new XXX();

这个时候是没有垃圾的,d指向第一个XXX,然后n等于d,n指向第一个XXX,d 又指向第二个XXX,两个对象都有人指,没有垃圾

对象已死?

没用的对象叫做对象已死,没有在被引用的对象,对象就是死掉了

【面试点】

如何判断对象死了?

可达性分析,判断哪些死了哪些活着;

可达性分析:从根部找到是栈还是方法区,找得到源头,说明没死;溯源,栈或者方法区属于源头,如果找了半天源头在堆里面,她就要被回收了,如果根部不在堆里面,说明他还在被使用

引用计数算法:

在对象添加一个引用计数器,有一个地方引用他就加一,引用失效就减一,任何计数器为零的对象就是不可能再被使用的。但是这个看似简单的算法有很多额外情况需要考虑,所以在java中不经常使用。

【面试点】

可达性分析算法:

当前主流的商用程序语言都是通过可达性分析来判定对象是否活着,从根部检索,如果找不到GCroots说明死了

GCRoots包括以下几种:

·在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的

参数、局部变量、临时变量等。

·在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。

·在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。·在本地方法栈中JNI(即通常所说的Native方法,本地方法,操作系统自带的方法)引用的对象。

·Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如

NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。

·所有被同步锁(synchronized关键字)持有的对象。

·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

引用

引用分为强引用,软引用,软引用和虚引用

平时我们用的是强引用,强引用被引用就不会被回收。

强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常

弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。【平时遇到的比较少,内部为了一次性使用可以用虚引用】

生存还是死亡?

要垃圾回收之前,有一次缓刑的机会。finalize()方法可自救一次,但是并不鼓励使用。忘了他吧!try catch比他强太多了

垃圾回收:

不是没有对他的引用立刻被回收---定时查看一次或者cpu满了之后再回收,所以可能对象死了之后活一段时间再回收,所以死掉的对象还有一次缓刑的机会

System.gc 主动触发gc/垃圾回收,让他进入就绪态(不是立刻执行)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值