java运行时内存划分?
线程共享的:堆区,方法区
线程私有的:程序计数器,虚拟机栈,本地方法栈
程序计数器:每一个线程都有一个程序计数器,是一个指针,指向方法区中的方法字节码,在虚拟机的概念模型里,字节码解释器工作就是通过改变程序计数器的值来实现的,分支,循环,跳转,异常处理,线程恢复都需要依赖程序计数器。程序计数器记录的是正在执行的虚拟机字节码指令的地址
本地方法栈:本地方法栈与虚拟机栈类似,区别在于本地方法栈为native方法服务,虚拟机栈为java方法服务(native方法就是不是用java写的,而是可能是用c/C++写的)
方法区:用于存储虚拟机加载的静态变量,常量,类信息,运行时常量池
虚拟机栈:栈是java方法执行的内存模型,每个方法被调用时,都会创建一个栈帧用于存储局部变量表等信息,当一个方法从调用到执行完成的过程中,就对应着一个栈帧都入栈到出栈的过程。 栈的生命周期就是线程的生命周期,线程创建栈创建,线程结束栈结束,栈是线程私有的
堆:所有对象实例以及数组都要在堆上分配,堆的唯一目的就是存放对象实例;堆是java虚拟机所管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,堆是理解java gc机制最重要的区域。
堆的结构:新生代(Eden+2个Survivor) 老年代 永久代(Hotspot有)Eden和Survivor为8:1
新生代:新创建的对象-->Eden区
GC之后,存活的对象由Eden区 Survivor区0 进入Survivor区1
再次GC,存活的对象由Eden区 Survivor区1 进入Survivor区0
新生代用的算法是复制算法,Survivor一次只用一个区
老年代:对象如果在新生代中存活了足够长的时间而没有被清理掉(即在几次GC后依然存活),则会被复制到老年代;或者如果创建的对象比较大,新生代空间不足(装不下去),则会直接分配到老年代上(大对象可能提前触发GC,尽量避免使用存活时间段的大对象) 老年代使用的是标记清除算法
老年代一般比新生代大,能够存放更多的对象,在老年代上发生的GC也比较少
永久代:可以简单地理解为方法区(本质上两者并不等价),因为除了Hotspot外,其他垃圾回收器没有永久代这个概念,jdk1.8中Hotspot也没有永久代这个概念了
垃圾回收器主要回收的区域就是堆区域,整体上采用的是分代收集算法,不同的区域采用不同的垃圾收集算法,我们应该尽量不要使用大对象,尽可能创建局部对象。
java创建对象的过程
为什么要进行分代?
因为不同的对象的生命周期的不同的,不同的分代有不同的垃圾回收策略,分代可以提高垃圾回收效率
java对象的生命周期?
1)创建阶段
此阶段会进行类加载检查,分配内存,初始化零值,设置对象头,执行init方法
2)应用阶段
对象至少被一个强引用持有
3)不可见阶段
程序的执行已经超出对象的作用域了,不过此时该对象仍被强引用持有着
4)不可达阶段
对象不再被任何强引用持有(除了像GC ROOT这样的)
5)收集阶段
当垃圾回收器发现对象已经处于不可达阶段并且准备好重新分配该对象的内存空间时进行
6)终结阶段
当对象执行完finalize()方法后仍处于不可达状态时会进入终结状态
7)对象重新分配阶段
OutmemoryError的原因?
1)请求创建的对象太大
2)内存泄漏(由于对象引用没有释放,所以jvm无法对其自动回收)
解决方案: 1.修改jvm启动参数
2.检查错误日志
3.对代码进行分析,找出可能发生内存溢出的位置
topK问题?
1.全部排序,筛选前K个
2.局部淘汰,先保存前K个,然后将后面的与前K个进行对比,大于则替换
3.分治,将K个分成N份,在每份中找出最大的
4.hash算法,通过hash去重,然后通过分治方法
jvm的四种引用状态
1)强引用
类似"Object object = new Object()”,只要强引用还存在,垃圾收集器永远不会回收被引用的对象
2)软引用
一些有用但并非必须的对象,在系统将要发生内存溢出异常之前,会对这些对象践行第二次回收,如果回收了还是没有足够的内存才会抛出内存溢出异常
3)弱引用
也是描述非必须对象,但是强度比软引用更弱,被弱引用关联的对象,只能生存到下一次垃圾收集发生之前
4)虚引用
一个对象是否有虚引用,完全不会对其生存时间构成影响,也无法通过虚引用取得对象实例,只是会在被回收时给你一个通知
java内存交互操作?
Lock操作 unLock操作 Read Load Use Assign Store Write
内存泄漏的原因?
长生命周期的对象持有短生命周期对象的引用,尽管短生命周期对象已经不再需要,但是长生命周期持有它的引用导致了不能被回收
1)静态集合类引起内存泄漏
2)监听器各种连接connection
3)内部类和外部模块的引用
4)单例模式(静态类持有引用,导致无法回收对象)
为什么内部类能够访问外部类的private方法?
在内部类构造的时候,会将外部类的引用传递进来,作为内部类的一个属性,所以内部类会持有一个外部类的引用。
对象的内存布局?
对象的内存布局分为:对象头,实例数据,对其填充
可达性算法中,哪些对象可以作为GC Roots对象?
1.虚拟机栈中引用的对象
2.方法区静态成员变量引用的对象
3.方法区常量引用的对象
4.本地方法栈JNI引用的对象
堆栈的区别?
堆的物理地址分配对象不是连续的,性能慢一点,而栈用到了先进后出的原则,物理地址分配是连续 的,性能更快
堆不连续,所以分配的内存是运行期确定的,大小不固定,栈分配内存是编译期确定的,大小固定
堆存放对象的实例和数组,更关注数据的存储,栈存放的是局部变量,更关注方法的执行
OOM?
OOM就是内存溢出。可能是因为新的对象太大,而堆的内存空间不够就会发生,也有可能是出现了内存泄漏,也就是长生命周期的对象持有着短生命周期对象的引用,导致那个对象无法被回收
解决方案:直接增大堆内存,或者去检查代码,看看哪里有上面的情况出现
jvm的安全点?
可达性分析算法必须确保是在一个一致的内存快照中进行的,安全点就意味着在这个点时,所有的工作线程都是确定的,这个时候jvm就可以安全地执行GC。安全点太多的话,GC过于频繁,太少的话,等待时间过长
怎么选定安全点:循环的末尾,方法返回前,调用方法后,抛出异常时的位置。主要就是为了避免程序长时间无法进入安全点
如何在GC发生时,所有线程都跑到最近的安全点上停下来:
抢占式中断:在GC发生时,中断所有线程,如果发现线程没有执行到安全点,则恢复线程让其运行到安全点上
协作式中断:GC发生时,不直接操作线程中断,而是设置一个标记,让各个线程执行时主动轮询这个标志,发现中断标志为真时自己中断挂起(jvm采用的是协作式中断)
如何定位内存泄漏?
先用jmap生成堆转储快照(dump文件),然后用mat等分析工具来找出占用超出预期的嫌疑对象,再去分析嫌疑对象和其他对象的引用关系,这时候可以去分析对应的源代码找出原因了。
jvm调优的方向?
1.系统出现异常后去看看是否是堆内存溢出,栈内存溢出,方法区内存溢出等
2.去看下GC日志(jstat),查看gc的频率,时间
3.查看线程快照(jstack),看下是否出现了死循环,死锁
4.当内存溢出时,用jmap分析内存使用情况
jvm调优参数设置:
-Xms:设置jvm启动时堆内存的初始化大小
-Xmx:设置堆内存的最大值
-Xmn:设置年轻代的空间大小
-XX:NewRatio:设置老年代和年轻代的比例大小
-XX:+UseSerialGC:年轻代用串行垃圾收集器
jstat
1. jstat -gc pid
可以显示gc的信息,查看gc的次数,及时间。
其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。
2.jstat -gccapacity pid
可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小,
如:PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,
PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。
其他的可以根据这个类推, OC是old内纯的占用量。
3.jstat -gcutil pid
统计gc信息统计。
4.jstat -gcnew pid
年轻代对象的信息。
5.jstat -gcnewcapacity pid
年轻代对象的信息及其占用量。
6.jstat -gcold pid
old代对象的信息。
7.stat -gcoldcapacity pid
old代对象的信息及其占用量。
8.jstat -gcpermcapacity pid
perm对象的信息及其占用量。
9.jstat -class pid
显示加载class的数量,及所占空间等信息。
10.jstat -compiler pid
显示VM实时编译的数量等信息。
11.stat -printcompilation pid
当前VM执行的信息。