()JVM内存结构
每个java程序运行在一个单独的JVM实例中,每个实例唯一对应一个堆。
栈只有两种异常:
1.StackOverflowError,线程请求的栈深度大于虚拟机允许的深度
2.OutOfMemoryError,无法申请到足够的内存
堆只有一种异常:OutOfMemoryError,无法申请到足够内存
方法区只有一种异常:OutOfMemoryError,无法申请到足够内存
程序计数器没有规定任何异常。(消耗内存小,可以忽略)
()java访问对象
64位JDK可以使用更大的堆内存,但存在指针膨胀,同样的程序在64位JDK消耗的内存比在32位JDK大,而且更大的堆导致停顿时间变长,使用若干个32位虚拟机建立逻辑集群利用硬件资源。
()内存溢出怎么办
答:1.栈溢出
减小单个线程的栈大小,以便可以创建更多线程
减小堆大小,内存让给栈
2.堆溢出
检查程序是否有不必要的对象
调大堆大小
3.方法区溢出
通过参数调大方法区大小
()需要被回收的内存-->根搜索法
()java存在内存泄漏?
答:存在,对象不使用的时候,仍然保持着其引用。
()新生代结构、收集算法(新生代Eden满,触发Minor gc)
一块较大的Eden空间、两块较小Survivor空间。
复制算法(对象98%朝生夕死):每次使用Eden和其中一块Survivor,当垃圾收集时,将Eden和Survivor中存活的对象拷贝到另一块Survivor空间(避免内存碎片)。新生代中对象98%朝生夕死,将Eden:Survivor=8:1,当Survivor空间不够容纳存活对象时,将其放入老年代(除程序计数器外,都有可能发生溢出)。
()老年代收集(老年代满,触发major gc)
答:1.标记清理
首先标记需要回收的对象,然后统一回收。(产生大量不连续的内存碎片,需要分配较大对象时无法找到足够连续空间会触发垃圾收集)
2.标记整理
首先标记需要回收的对象,让存活的对象都向一端移动,然后直接清除掉边界以外的内存。(对象存活率较高时要执行较多的复制操作)
默认,新生代占堆空间1/3,老年代占堆空间2/3.
()分代回收算法-->复制算法和标记整理法的结合
()JVM的永久代会发生垃圾收集吗?(java1.8之前才存在永久代)
答:不会发生,如果永久代超过临界值,会触发Full GC。如果仔细查看垃圾收集器输出信息,会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC非常重要的原因。
()java1.8之后的JVM堆内存结构变化?
()方法区垃圾收集
答:无用的类、废弃常量。
()为什么分代回收?
答:不同对象生命周期不一样,如果每次垃圾收集都遍历所有存活对象,时间太长,生命周期长的对象可能遍历很多次依旧存在。
()内存分配策略
答:1.对象优先在Eden分配,当Eden没有足够空间时,JVM进行
Minor gc
2.大对象直接进入老年代 XX:PretenureSizeThreshold
3.长期存活的对象将进入老年代,新生代中的对象每经过一次
Minor gc仍然存活,年龄加1,超过一定值将进入老年代。
4.动态对象年龄判断
虚拟机并不总是要求对象的年龄必须达到指定值才能进入老年代,如果survivor空间中相同年龄的所有对象大小之和大于survivor空间的一半,年龄大于等于该年龄的对象直接进入老年代。
5.空间分配担保
在发生Minor GC时,虚拟机检查之前每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,改为进行一次Full GC;如果小于,查看是否允许担保失败,如果允许,只会进行Minor GC,如果不允许,也改为进行一次Full GC。
()垃圾收集器?
()Serial收集器?(新生代)
答:单线程收集器。
()ParNew收集器?(新生代)
答:serial收集器的多线程版本。
()Parallel Scavenge收集器(新生代)
答:并发多线程收集器。与ParNew的区别:吞吐量优先。(吞吐量=用户代码时间/cpu总消耗时间)
()Serial Old收集器(老年代)
答:serial收集器的老年代版本。
()Parallel old收集器
答:Parallel Scavenge收集器的老年代版本。
()CMS收集器(老年代)
答:标记清除。最短回收停顿时间
回收过程:1.初始标记
标记GC Roots能直接关联到的对象
2.并发标记
跟踪GC Roots的变化
3.重新标记
重新标记GC Roots能直接关联到的对象
4.并发清除
清除无用对象
()CMS收集器为什么没有采用”标记-整理”算法?
答:因为老年代对象可能会长时间存活,或者比较大,拷贝起来耗时。
()G1收集器
答:标记-整理。将整个堆(新生代、老年代)划分为固定大小的区域,跟踪这些区域上的垃圾堆积程度,根据允许的收集时间,优先回收垃圾最多的区域。
与CMS区别: 1.基于标记整理算法,避免内存碎片
2.非常精确的控制停顿时间,让使用者明确指定在M毫秒的时间内,消耗在垃圾回收上的时间不得超过N毫秒。
()java线程、主内存、工作内存交互图
Volatile
只保证单一操作的原子性,不保证复合操作的原子性。
Java多线程共享变量时,为了提高效率,每个线程从主内存拷贝一份共享变量副本到线程工作内存中,每次对共享变量的操作都是对各自副本的操作,线程结束前,副本同步到主内存,当变量被volatile修饰后,线程每次都从主内存中读取共享变量,修改完成后写回到主内存,使共享变量发生改变后尽快被其他线程知道(因此不具备原子性,Synchronized保证可见性和互斥性。)
1)一个线程修改了某个变量的值,新值对于其他线程是立即可见的。
2)禁止指令重排序(优化程序性能)
如:指令重排序,调整指令顺序,在不改变程序语义的情况下,尽可能减少寄存器的读写。假设一条指令将变量A放入寄存器,第二条指令与A无关需要占用寄存器,第三条指令与第二条指令无关但需要A的值,指令二三会交换顺序。
Int a=10;int b=20;有可能第二条语句先于第一条语句执行
volatile使用场景:1.状态标志
happens-before先行发生原则:如果不满足先行发生原则,可以随意重排序
1.一个线程内,书写在前面的代码先行发生于书写在后面的代码
2.一个unlock操作先行发生于后面对同一个锁的lock操作
3.对一个volatile变量的写操作先行发生于后面对这个变量的读操作
4.Thread对象的start()方法先行发生于此线程的每一个动作
5.线程中的所有操作都先行发生于此线程的终止检测
6.对线程interrupt()方法的调用先行发生于被中断线程的代码检测中断事件的发生
7.一个对象的初始化完成(构造函数)先行发生于它的finalize()方法的开始
8.如果操作A先行发生于B,操作B先行发生于操作c,那么A先行发生于c
()JVM如何确定每个类在JVM的唯一性?
答:加载这个类的类加载器和类的全限定名。
()System.gc()作用?
答:提示JVM进行垃圾回收,但立即开始还是延迟进行由JVM决定。JVM根据堆空间使用情况决定。
堆空间不够或cpu空闲时,会进行垃圾收集。
()new的对象如何不分配在堆而分配在栈上?
答:方法逃逸。JVM如果确定一个对象不会逃逸到方法之外,在栈上分配内存,对象随方法的结束而销毁,减小GC压力。