JVM内存结构:
JVM运行时内存区域划分 ?
先画出内存结构图,然后根据线程私有,线程共享分别介绍。详见:JVM成神之路-JVM内存结构
栈和堆分别主要存的数据是什么?
栈主要是局部变量表和操作数栈及帧数据区,主要存局部变量
堆主要存的是对象,成员变量
方法区主要类变量,存符号引用,静态变量,类信息,常量等
堆分为哪几块,比如说新生代老生代,那么新生代又分为什么?
内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决 ?
内存泄漏和内存溢出的区别:
- 申请内存后,无法释放已申请的内存空间,内存泄漏堆积的结果是内存溢出
- 内存溢出是指申请内存时,没有足够的内存空间供申请者使用
1.是否App中使用了大量的递归或无限递归(递归中用到了大量的建新的对象)
2.是否App中使用了大量循环或死循环(循环中用到了大量的新建的对象)
3.检查App中是否使用了向数据库查询所有记录的方法。即一次性全部查询的方法,如果数据量超过10万多条了,就可能会造成内存溢出。所以在查询时应采用“分页查询”。
4.检查是否有数组,List,Map中存放的是对象的引用而不是对象,因为这些引用会让对应的对象不能被释放。会大量存储在内存中。
5.检查是否使用了“非字面量字符串进行+”的操作。因为String类的内容是不可变的,每次运行"+"就会产生新的对象,如果过多会造成新String对象过多,从而导致JVM没有及时回收而出现内存溢出。
如String s1 = "My name";
String s2 = "is";
String s3 = "xuwei";
String str = s1 + s2 + s3 +.........;这是会容易造成内存溢出的
但是String str = "My name" + " is " + " xuwei" + " nice " + " to " + " meet you"; //但是这种就不会造成内存溢出。因为这是”字面量字符串“,在运行"+"时就会在编译期间运行好。不会按照JVM来执行的。
在使用String,StringBuffer,StringBuilder时,如果是字面量字符串进行"+"时,应选用String性能更好;如果是String类进行"+"时,在不考虑线程安全时,应选用StringBuilder性能更好。
6.使用 DDMS工具进行查找内存溢出的大概位置
StackOverflowError 与 OutOfMemoryError 的区别?
栈溢出实例:
一)、是否有递归调用
- StackOverflowError 是指栈请求的深度大于虚拟机规定的栈深度,此时内存空间可能还足够
- OutOfMemoryError 是指内存空间不足,无法分配内存
二)、是否有大量循环或死循环
三)、全局变量是否过多
四)、 数组、List、map数据是否过大
五)使用DDMS工具进行查找大概出现栈溢出的位置
排查工具:MAT,jconsole查看Java虚拟机当前状况,jvisualvm查看内存转储文件
Java在什么时候会出现内存泄漏?
Java内存泄漏的根本原因:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。
1、静态集合类引起内存泄漏:如果对象加入到集合 后,还必须从集合 中删除,最简单的方法就是将集合对象设置为null。
2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。
3、各种连接:finally中释放掉
4、单例模式:单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏
Java中的大对象如何进行存储?
尽量让大对象直接进入持久代,而不是新生代;因为大对象会占有过多的新生代,会增加GC.
-XX:PetenureSizeThreshold 设置大对象直接进入年老代的阈值。当对象的大小超过这个值时,将直接在年老代分配。参数-XX:PetenureSizeThreshold 只对串行收集器和年轻代并行收集器有效,并行回收收集器不识别这个参数。
生产环境JVM设置,没有什么优化,比较通用。
-Xms2048m -Xmx2048m -Xmn256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=50 -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:-HeapDumpOnOutOfMemoryError -Djava.awt.headless=true -Dfile.encoding=UTF-8
直接内存如何管理的?
直接内存不属于虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域。
这部分也被频繁的使用,而且也可能出现OOM异常。
JDK1.4中加入NIO,可用于Native函数库直接分配堆外内存,然后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作;提高新能,避免在java堆和Native堆中来回复制数据
优点:减少垃圾回收,减少复制过程,提高性能
缺点:失去jvm管理内存的可见性,排查问题困难
char和double的字节,以及在内存的分布是怎样?
char:2byte;double:8byte。
浮点是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。
new一个包含多个字符串的数组,这句代码执行时,内存的哪些地方有变化?
https://blog.youkuaiyun.com/w372426096/article/details/89070686
sting s=new string("abc")分别在堆栈上新建了哪些对象?
字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价。JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。Java能够进行这样的优化是因为字符串是不可变的,可以不用担心数据冲突进行共享。
- 先在栈中开辟空间存放引用s;
- 堆中开辟空间存放一个新建String对象“abc”
- 引用s指向堆中新建的String对象
- s所指代的对象为堆中的地址,堆空间存放的是常量池里“abc”的地址
JVM内存模型:
说说线程安全问题,什么是线程安全,如何保证线程安全 ?
某个函数、函数库在并发环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。
1、并发
2、多线程
3、共享变量
(私密)https://blog.youkuaiyun.com/w372426096/article/details/84793226
并发编程,为了保证数据的安全,需要满足以下三个特性:
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性即程序执行的顺序按照代码的先后顺序执行。
Java内存模型JMM ?
为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。
内存模型解决并发问题主要采用两种方式:限制处理器优化和使用内存屏障。本文就不深入底层原理来展开介绍了,感兴趣的朋友可以自行学习。
Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。
JMM里边的原子性、可见性、有序性是如何体现出来的,JMM中内存屏障是什么意思?
在Java中提供了一系列和并发处理相关的关键字,比如volatile
、synchronized
、final
、concurren
包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字。
volatile是通过内存屏障来来禁止指令重排的进而保证有序型的。
内存屏障(Memory Barrier)是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。下表描述了和volatile有关的指令重排禁止行为:
内存屏障也是保证可见性的重要手段,操作系统通过内存屏障保证缓存间的可见性,JVM通过给volatile变量加入内存屏障保证线程之间的可见性。
Java中的内存屏障:用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
讲下JVM的大页模式,JVM内存模型?
内存使用性能优化
https://blog.youkuaiyun.com/xihuanyuye/article/details/83930703
进程间共享内存的方式有哪些?
1.无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2.高级管道(popen):将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
3.有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
4.消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5.信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6.信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
7.共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
8.套接字( socket ) : 套解字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
Synchronized:
synchronized 实现原理?
对象监视器
对于同步方法:JVM采用ACC_SYNCHRONIZED标记符来实现同步。 在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
对于同步代码块:JVM采用monitorenter、monitorexit两个指令来实现同步。
https://blog.youkuaiyun.com/w372426096/article/details/80077516
synchronized 与 lock 的区别 ?
- 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
- synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
- synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
- 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
- synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
1、synchronized是重量级锁? 从JDK 1.5 到 JDK 1.6 有一个高效并发方面的重要改进,HotSpot虚拟机开发团队在这个版本中花费了很大的精力去对Java中的锁进行优化(synchronized),如适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等。这些技术都是为了在线程之间更高效的共享数据,以及解决竞争问题。 在使用synchronized的时候,会有一个顺序,这个顺序叫做膨胀。会首先使用偏向锁尝试、然后使用轻量级锁、最后才会尝试重量级锁。
2、concurrent包为啥不被称之为重量级锁 concurrent包底层确实如你所说,依赖LockSupport 提供park()和unpark()方法实现的。 但是,这并不是说只要使用concurrent包中的类,那么就会直接使用park()和unpark()。上面还有一层重要的东西,那就是CAS。
3、 所以结论是。
synchronized和concurrent包,在底层和操作系统交互时,都是使用mutex lock的。但是,二者都会尝试各种锁优化来尽量避免走到这最后一步。 synchronized使用的是如锁膨胀等技术。 concurrent包则使用的是CAS技术。 在早期,synchronized没做优化的时候,我们称之为重量级锁。后面有了优化手段之后,我们还是沿用老的叫法,最终会膨胀成重量级锁。 而在concurrent包刚出来的时候,就采用了CAS等技术做了优化,所以,和那时候的synchronized相比,他就没有被称之为重量级锁了。
synchronized关键字锁住的是什么东西?在字节码中是怎么表示的?在内存中的对象上表现为什么?
具体原理是monitor
所得是对象实例或者类实例。
一个线程执行临界区代码过程如下:
1 获得同步锁
2 清空工作内存
3 从主存拷贝变量副本到工作内存
4 对这些变量计算
5 将变量从工作内存写回到主存
6 释放锁
wait/notify/notifyAll⽅法需不需要被包含在synchronized块中?这是为什么?
为什么wait, notify 和 notifyAll这些方法不在thread类里面?要在同步块中被调用?
要说明为什么把这些方法放在Object类里是有意义的,还有不把它放在Thread类里的原因。一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
sync原理详细,sync内抛异常会怎样,死锁吗?还是释放掉?怎么排查死锁?死锁会怎样?有没有什么更好的替代方案?
同步代码块中抛异常会马上释放锁在finally lock中释放,不会死锁。
对象锁,并不会影响后续队列任务的执行
ps或jps查看进程id
jstack查看dump文件寻找死锁问题
Volatile:
volatile 实现原理
禁止指令重排、刷新内存
https://blog.youkuaiyun.com/w372426096/article/details/81625055
锁:
重入锁的概念,重入锁为什么可以防止死锁?
首先是示例代码:
public class Widget {
public synchronized void doSomething(){
// do something
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
super.doSomething();
}
}
这是《java并发编程实例》一书中的例子,并且书中说:“如果synchronized 不是可重入锁,那么LoggingWidget 的super.dosomething();无法获得Widget对象的锁,因为会死锁。”
乍一看好像不是这么回事,就算synchronized 不是可重入锁,可是synchronized 关键字一个在父类Widget 的方法上,另一个在子类LoggingWidget 的方法上,怎么会有死锁产生呢。
这里其实牵涉到了Java的重写。我们看子类LoggingWidget 的doSomething方法,重写了父类Widget 的doSomething方法,但是子类对象如果要调用父类的doSomething方法,那么就需要用到super关键字了。因为实例方法的调用是Java虚拟机在运行时动态绑定的,子类LoggingWidget 的对象调用doSomething方法,一定是绑定到子类自身的doSomething方法,必须用super关键字告诉虚拟机,这里要调用的是父类的doSomething方法。
实际上,如果我们分析运行时的LoggingWidget 类,那我们看到的应该是这样子的(这里只是为了分析,真实情况肯定和下面的例子不同):
public class LoggingWidget extends Widget {
public synchronized void Widget.doSomething() {
// do something
} // 父类的doSomething方法
public synchronized void doSomething() {
super.doSomething();
}
}
子类对象,其实是持有父类Widget 的doSomething方法的,只需要使用super关键字告诉虚拟机要运行的是父类的doSomething方法,虚拟机会去调用子类对象中的父类Widget 的doSomething方法的。所以,super关键字并没有新建一个父类的对象,比如说widget,然后再去调用widget.doSomething方法,实际上调用父类doSomething方法的还是我们的子类对象。
那么这样就很好理解了,如果一个线程有子类对象的引用loggingWidget,然后调用loggingWidget.doSomething方法的时候,会请求子类对象loggingWidget 的对象锁;又因为loggingWidget 的doSomething方法中调用的父类的doSomething方法,实际上还是要请求子类对象loggingWidget 的对象锁,那么如果synchronized 关键字不是个可重入锁的话,就会在子类对象持有的父类doSomething方法上产生死锁了。正因为synchronized 关键字的可重入锁,当前线程因为已经持有了子类对象loggingWidget 的对象锁,后面再遇到请求loggingWidget 的对象锁就可以畅通无阻地执行同步方法了。
更进一步,将上面的示例代码改写一下,那么就算synchronized 不是可重入锁,也不会产生死锁的问题了。代码如下:
public class Widget {
public synchronized void doSomething(){
// do something
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
Widget widget = new Widget();
widget.doSomething();
}
}
在子类的doSomething方法中,直接新建了一个父类的对象widget,然后用这个父类对象来调用父类的doSomething方法,实际上请求的是这个父类对象widget的对象锁,就不涉及到可重入锁的问题了。
产生死锁的四个条件?
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
偏向锁、轻量级锁、重量级锁、自旋锁的概念?
https://blog.youkuaiyun.com/w372426096/article/details/80079605
乐观锁,悲观锁的设计,如何保证原子性,解决的问题?什么是ABA问题,出现ABA问题JDK是如何解决的?
悲观锁:Synchronized;传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁:java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的;提高吞吐量
https://blog.youkuaiyun.com/w372426096/article/details/80938876
https://blog.youkuaiyun.com/w372426096/article/details/89189102
AQS原理,ReentranLock源码,设计原理,整体过程。
AQS, 从unsafe一直到ReentrentLock画一下
https://blog.youkuaiyun.com/w372426096/article/details/82746393
https://blog.youkuaiyun.com/w372426096/article/details/82752237
https://blog.youkuaiyun.com/w372426096/article/details/82755775
https://blog.youkuaiyun.com/w372426096/article/details/82757748
https://blog.youkuaiyun.com/w372426096/article/details/82758956
多线程:
常见的原子操作类?
在Java并发比编程中,要想保证一些操作不被其他线程干扰,就需要保证原子性,JDK中提供了13个原子操作类来帮助我们进行开发,本文是笔者对《java并发编程的艺术》一书中Java原子操作类的重点内容的总结和分析。
二、原子更新基本类型
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
(1)AtomicBoolean: 原子更新布尔类型。
(2)AtomicInteger: 原子更新整型。
(3)AtomicLong: 原子更新长整型。
以上3个类提供的方法几乎一模一样,以AtomicInteger为例进行详解,AtomicIngeter的常用方法如下:
(1)int addAndGet(int delta): 以原子的方式将输入的数值与实例中的值相加,并返回结果。
(2)boolean compareAndSet(int expect, int update): 如果输入的值等于预期值,则以原子方式将该值设置为输入的值。
(3)int getAndIncrement(): 以原子的方式将当前值加1,注意,这里返回的是自增前的值。
(4)void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
(5)int getAndSet(int newValue): 以原子的方式设置为newValue,并返回旧值。
Atomic包里的类基本都是使用Unsafe下的CAS方法实现,方法如下:
Unsafe
Unsafe只提供了三种CAS方法: compareAndSwapObject、compareAndSwapInt和compateAndSwapLong,其他类型都是转成这三种类型再使用对应的方法去原子更新的。
三、原子更新数组
通过原子的方式更新数组里的某个元素,Atomic包提供了以下的4个类:
(1)AtomicIntegerArray: 原子更新整型数组里的元素。
(2)AtomicLongArray: 原子更新长整型数组里的元素。
(3)AtomicReferenceArray: 原子更新引用类型数组里的元素。
这三个类的最常用的方法是如下两个方法:
(1)get(int index):获取索引为index的元素值。
(2)compareAndSet(int i,E expect,E update): 如果当前值等于预期值,则以原子方式将数组位置i的元素设置为update值。
以AtomicReferenceArray举例如下:
AtomicReferenceArray
输出结果为:index100 \n index1。
需要注意的是,数组value通过构造方法传递进去,然后AtoReferenceArray会将当前数组复制一份,所以当AtoReferenceArray对内部的数组元素修改的时候,不会影响原先传入的referenceArray数组。
四、原子更新引用类型
Atomic包提供了以下三个类:
(1)AtomicReference: 原子更新引用类型。
(2)AtomicReferenceFieldUpdater: 原子更新引用类型的字段。
(3)AtomicMarkableReferce: 原子更新带有标记位的引用类型。
这三个类提供的方法都差不多,首先构造一个引用对象,然后把引用对象set进Atomic类,然后调用compareAndSet等一些方法去进行原子操作,原理都是基于Unsafe实现,但AtomicReferenceFieldUpdater略有不同,更新的字段必须用volatile修饰,下面提供一段示例代码:
AtomicReferenceFieldUpdater
五、原子更新字段类
Atomic包提供了四个类进行原子字段更新:
(1)AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
(2)AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
(3)AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。
(4)AtomicReferenceFieldUpdater: 上面已经说过此处不在赘述。
这四个类的使用方式都差不多,示例代码如上一小节的AtomicReferenceFieldUpdater一样,要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新类的字段必须使用public volatile修饰。
Java 8并法包下常见的并发类?
java.util.concurrent、java.util.concurrent.atomic和java.util.concurrent.locks
AtomicInteger底层实现原理?
Java对象:
对象内存布局,然后讲下对象的死亡过程?
https://blog.youkuaiyun.com/w372426096/article/details/81167669
对象头,详细讲下?
https://blog.youkuaiyun.com/w372426096/article/details/80079408
一个对象占用多少空间?一个class占用多少空间?
垃圾回收:
为什么新生代内存需要有两个Survivor区?
可以利用复制清楚的垃圾算法:
优点:不用考虑内存碎片等。复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。
缺点:复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低
为什么新生代内存需要有两个Survivor区?
垃圾回收机制?如何判断对象是否可以回收或存活 ?常见的GC回收算法及其含义 ?GC收集器有哪些?
https://blog.youkuaiyun.com/w372426096/article/details/81360083
CMS收集器与G1收集器的特点? G1停顿吗,CMS回收步骤,CMS为什么会停顿,停顿时间? CMS垃圾回收过程?
标记清除和标记整理的区别和优缺点,为何标记整理会发生stop the world?
减少垃圾碎片
Minor GC与Full GC分别在什么时候发生?什么时候触发Full GC?
Minor GC:
Eden区域满了,或者新创建的对象大小 > Eden所剩空间
CMS设置了CMSScavengeBeforeRemark参数,这样在CMS的Remark之前会先做一次Minor GC来清理新生代,加速之后的Remark的速度。这样整体的stop-the world时间反而短
Full GC的时候会先触发Minor GC。执行Minor GC需要注意:
>>A.当JVM无法为一个新的对象分配空间时会触发Minor GC,比如当Eden区满了。所以分配率越高,越频繁执行Minor GC。
>>B.内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden和Survivor区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以Eden 和Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。
>>C.执行Minor GC操作时,不会影响到永久代。从永久代到年轻代的引用被当成GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。
>>D.质疑常规的认知,所有的Minor GC都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就是,大部分Eden 区中的对象都能被认为是垃圾,永远也不会被复制到Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合GC条件,Minor GC 执行时暂停的时间将会长很多。
Major GC:清理永久代,但是由于很多MojorGC 是由MinorGC 触发的,所以有时候很难将MajorGC 和MinorGC区分开。
FullGC:是清理整个堆空间—包括年轻代和永久代。FullGC 一般消耗的时间比较长,远远大于MinorGC,因此,有时候我们必须降低FullGC 发生的频率。
Minor GC后存活的对象晋升到老年代时由于悲观策略的原因,有两种情况会触发Full GC, 1种是之前每次晋升的对象的平均大小 > 老年代剩余空间
1种是Minor GC后存活的对象超过了老年代剩余空间。这两种情况都是因为老年代会为新生代对象的晋升提供担保,而每次晋升的对象的大小是无法预测的,所以只能基于统计,1个是基于历史平均水平,一个是基于下一次可能要晋升的最大水平。这两种情况都是属于promotion failure
CMS失败,发生concurrent mode failure会引起Full GC,这种情况下会使用Serial Old收集器,是单线程的,对GC的影响很大。concurrent mode failure产生的原因是老年代剩余的空间不够,导致了和gc线程并发执行的用户线程创建的大对象(由PretenureSizeThreshold控制新生代直接晋升老年代的对象size阀值)不能进入到老年代,只要stop the world来暂停用户线程,执行GC清理。可以通过设置CMSInitiatingOccupancyFraction预留合适的CMS执行时剩余的空间
新生代直接晋升到老年代的大对象超过了老年代的剩余空间,引发Full GC。注意于promotion failure的区别,promotion failure指的是Minor GC后发生的担保失败
Perm永久代空间不足会触发Full GC,可以让CMS清理永久代的空间。设置CMSClassUnloadingEnabled即可
System.gc()引起的Full GC,可以设置DisableExplicitGC来禁止调用System.gc引发Full GC
new对象流程?大对象分配G1有没有优化措施?
你项目用的是哪种垃圾收集器?
-Xms2048m -Xmx2048m -Xmn256m -XX:PermSize=128M -XX:MaxNewSize=256m -XX:MaxPermSize=256m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=50 -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection -XX:-HeapDumpOnOutOfMemoryError -Djava.awt.headless=true -Dfile.encoding=UTF-8
编译原理:
逃逸分析是什么,作用是什么,用途是什么?
https://blog.youkuaiyun.com/w372426096/article/details/80333657
ClassLoader类加载:
JVM里的Classloader有几种,为什么会有这么多种?类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的 ?类加载的过程 ?
https://blog.youkuaiyun.com/w372426096/article/details/81901482
https://blog.youkuaiyun.com/briblue/article/details/54973413
rt.jar被什么类加载器加载,什么时间加载? 自己写的类被什么加载,什么时间加载?
Bootstrap ClassLoader;
自己写的两个不同的类是被同一个类加载器加载的吗?为什么?
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。
什么情况下会触发类加载?
Java虚拟机规范中并没有进行强制约束什么时候开始类加载过程的第一个阶段-加载,可以交给虚拟机具体实现来自由把握。但对于初始化阶段,虚拟机规范严格规定有且只有5种情况必须立即对类进行初始化(加载、验证、准备自然要在此之前开始)
遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发初始化操作。
4条指令最常见Java代码场景:用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)的时候、调用一个类的静态方法的时候。
用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要触发初始化操作。
初始化一个类的时候,发现其父类还有进行过初始化,则需要触发先其父类的初始化操作。
注意这里和接口的初始化有点区别,,一个接口在初始化时,并不要求其父接口全部都完成了初始化,只要在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。
虚拟机启动时,需要指定一个执行的主类(包含main方法的类),虚拟机会先初始化这类。
用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化操作。
静态内部类加载到了哪个区?
总结:
1.静态内部类可以有静态成员(方法,属性),而非静态内部类则不能有静态成员(方法,属性)。
2.静态内部类只能够访问外部类的静态成员,而非静态内部类则可以访问外部类的所有成员(方法,属性)。
3.实例化一个非静态的内部类的方法:
a.先生成一个外部类对象实例
OutClassTest oc1 = new OutClassTest();
b.通过外部类的对象实例生成内部类对象
OutClassTest.InnerClass no_static_inner = oc1.new InnerClass();
4.实例化一个静态内部类的方法:
a.不依赖于外部类的实例,直接实例化内部类对象
OutClassTest.InnerStaticClass inner = new OutClassTest.InnerStaticClass();
b.调用内部静态类的方法或静态变量,通过类名直接调用
OutClassTest.InnerStaticClass.static_value
OutClassTest.InnerStaticClass.getMessage()
Classloader怎么释放内存,tomcat classloader为什么这么设计?
Javap等相关命令:
常见的JVM性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole等 ?
几种常用的内存调试工具:jmap、jstack、jconsole?
JVM调优:
JVM如何设置参数 ?
JVM性能调优 ?
为什么JVM调优经常会将-Xms和-Xmx参数设置成一样?
Java内存抖动严重,优化的思路?
Full GC次数太多了,如何优化?
https://blog.youkuaiyun.com/w372426096/article/details/78651247
JVM引用模型:
强引用、软引用、弱引用、虚引用 ?
软引用和弱引用的使用场景(软引用可以实现缓存,弱引用可以用来在回调函数中防止内存泄露)?
https://blog.youkuaiyun.com/w372426096/article/details/83062766
1:JVM管理的内存结构是怎样的?
Java虚拟机运行时数据区域主要包含了PC寄存器(程序计数器)、Java虚拟机栈、本地方法栈、Java堆、方法区以及运行时常量池。
各个区域有各自不同的作用,关于各个区域的作用就不在本文中相信介绍了。
但是,需要注意的是,上面的区域划分只是逻辑区域,规范对于有些区域的限制是比较松的,所以不同的虚拟机厂商在实现上,甚至是同一款虚拟机的不同版本也是不尽相同的。
2:不同的虚拟机在实现运行时内存的时候有什么区别?
前面提到过《Java虚拟机规范》定义的JVM运行时所需的内存区域,不同的虚拟机实现上有所不同,而在这么多区域中,规范对于方法区的管理是最宽松的,规范中关于这部分的描述如下:
方法区在虚拟机启动的时候创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集与压缩。本版本的规范也不限定实现方法区的内存位置和代码编译的管理策略。方法区的容量可以是固定的,也可以随着程序执行的需求动态扩展,并在不需要过多的空间时自行收缩。方法区在实际内存空间站可以是不连续的。
这一规定,可以说是给了虚拟机厂商很大的自由。
虚拟机规范对方法区实现的位置并没有明确要求,在最著名的HotSopt虚拟机实现中(在Java 8 之前),方法区仅是逻辑上的独立区域,在物理上并没有独立于堆而存在,而是位于永久代中。所以,这时候方法区也是可以被垃圾回收的。
实践证明,JVM中存在着大量的声明短暂的对象,还有一些生命周期比较长的对象。为了对他们采用不同的收集策略,采用了分代收集算法,所以HotSpot虚拟机把的根据对象的年龄不同,把堆分位新生代、老年代和永久代。
在Java 8中 ,HotSpot虚拟机移除了永久代,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace)
3:运行时数据区中哪些区域是线程共享的?哪些是独享的?
在JVM运行时内存区域中,PC寄存器、虚拟机栈和本地方法栈是线程独享的。
而Java堆、方法区是线程共享的。但是值得注意的是,Java堆其实还未每一个线程单独分配了一块TLAB空间,这部分空间在分配时是线程独享的,在使用时是线程共享的。(TLAB介绍)
4:除了JVM运行时内存以外,还有什么区域可以用吗?
除了我们前面介绍的虚拟机运行时数据区以外,还有一部分内存也被频繁使用,他不是运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,他就是——直接内存。
直接内存的分配不受Java堆大小的限制,但是他还是会收到服务器总内存的影响。
在JDK 1.4中引入的NIO中,引入了一种基于Channel和Buffer的I/O方式,他可以使用Native函数直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的应用进行操作。
5:堆和栈的区别是什么?
堆和栈(虚拟机栈)是完全不同的两块内存区域,一个是线程独享的,一个是线程共享的,二者之间最大的区别就是存储的内容不同:
堆中主要存放对象实例。
栈(局部变量表)中主要存放各种基本数据类型、对象的引用。
6:Java中的数组是存储在堆上还是栈上的?
在Java中,数组同样是一个对象,所以对象在内存中如何存放同样适用于数组;
所以,数组的实例是保存在堆中,而数组的引用是保存在栈上的。
7:Java中的对象创建有多少种方式?
Java中共有5种方式可以创建一个对象。
最简单的方式就是使用new关键字。
User user = new User();
除此以外,还可以使用反射机制创建对象:
User user = User.class.newInstance();
或者使用Constructor类的newInstance:
Constructor<User> constructor = User.class.getConstructor();
User user = constructor.newInstance();
除此之外还可以使用clone方法和反序列化的方式,这两种方式不常用并且代码比较复杂,就不在这里展示了,感兴趣的可以自行了解下。
8:Java中对象创建的过程是怎么样的?
对于一个普通的Java对象的创建,大致过程如下:
1、虚拟机遇到new指令,到常量池定位到这个类的符号引用。
2、检查符号引用代表的类是否被加载、解析、初始化过。
3、虚拟机为对象分配内存。
4、虚拟机将分配到的内存空间都初始化为零值。
5、虚拟机对对象进行必要的设置。
6、执行方法,成员变量进行初始化。
9:Java中的对象一定在堆上分配内存吗?
前面我们说过,Java堆中主要保存了对象实例,但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做逃逸分析。
如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。(关于逃逸分析和栈上分配可以参考:深入理解Java中的逃逸分析、对象并不一定都是在堆上分配内存的)
10:怎么如何获取堆和栈的dump文件?
Java Dump,Java虚拟机的运行时快照。将Java虚拟机运行时的状态和信息保存到文件。
可以使用在服务器上使用jmap命令来获取堆dump,使用jstack命令来获取线程的调用栈dump。(关于jmap和jstack可以参考:Java命令学习系列(三)——Jmap、Java命令学习系列(二)——Jstack)
以上,就是本文的全部内容,对于其中的很多知识点,作者并未逐一展开,感兴趣的朋友可以自行学习一下,关于Java虚拟机相关知识,推荐三本书:《深入理解Java虚拟机》、《HotSpot实战》以及《Java虚拟机规范(第8版)》,本文中内容也参考了这三本书中很多内容。希望对你有帮助。