一些常见面试题:
JVM的位置(运行在操作系统上,与硬件没有直接的交互)
一、jvm体系结构(记住背下来)
运行时数据区:有亮色的有灰色的,灰色的就是占得内存非常小,几乎不存在GC垃圾回收,并且线程独占的,亮色的存在垃圾回收,并且所有线程共享。
二、类装载器
(看做成快递员,把class文件(class文件开头有特定的文件标示cafe babe)字节码加载到内存,并将这些内容转换成方法区中的运行时数据结构并且ClassLoader只负责class文件的加载,至于是否可以运行,由Execution Engine决定)
类装载器有哪几个?
1-启动类加载器,负责加载%JAVA_HOME%\bin目录下的所有jar包,或者是-Xbootclasspath参数指定的路径;
2-扩展类加载器:负责加载%JAVA_HOME%\bin\ext目录下的所有jar包,或者是java.ext.dirs参数指定的路径;
3-应用程序类加载器:负责加载用户类路径上所指定的类库,如果应用程序中没有自定义加载器,那么次加载器就为默认加载器。
加载器之间的层次关系:
public class MyObject {public static void main(String []args){Object object = new Object();MyObject myObject = new MyObject();System.out.println(object.getClass().getClassLoader());System.out.println(myObject.getClass().getClassLoader());}
}
运行结果:启动类加载器来加载java自带的类,BootStrap是C++写的所以输出的null;
package JVM;public class MyObject {public static void main(String []args){Object object = new Object();System.out.println(object.getClass().getClassLoader());System.out.println();System.out.println();System.out.println();MyObject myObject = new MyObject();System.out.println(myObject.getClass().getClassLoader());System.out.println(myObject.getClass().getClassLoader().getParent());System.out.println(myObject.getClass().getClassLoader().getParent().getParent());}
}
双亲委派机制:
双亲委派机制得工作过程:
1-类加载器收到类加载的请求;
2-把这个请求委托给父加载器去完成,一直向上委托,直到启动类加载器(bootstrap);
3-启动器加载器检查能不能加载(使用findClass()方法),能就加载(结束);否则,抛出异常,通知子加载器进行加载。
4-重复3;
当加载一个类会先到启动类加载器去找,找得到就用,找不到就到扩展类加载器找,找不到再去应用程序类加载器去找。(从顶层往下开始找)
双亲委派机制的理由?
是沙箱安全机制
例:
String类,String是java.lang包下的类,默认情况下是启动类加载器进行加载的。假设我也自定义一个String。现在你会发现自定义的String可以正常编译,但是永远无法被加载运行。
这是因为申请自定义String加载时,总是启动类加载器,不会是其他的加载器,也就是不会用应用程序加载器加载。
package java.lang;public class String {public static void main(String[] args) {System.out.println(1111);}
}
运行结果:是无法被加载的
三、本地方法栈(加载native方法,了解)
四、程序计数器(类似一个指针,一条指令执行完用来指向下一个指令 )
五、方法区(类的模板工厂)
六、java栈(灰色,线程私有,不存在gc )
栈中主要存储3类数据:
本地变量:输入参数和输出参数,方法内的变量
栈操作:记录出栈、入栈的操作
栈帧数据:包括类文件、方法等。
七、堆(一个jvm实例只存在一个堆空间,大小可以调节)
堆分为三部分:新生区,养老区,永久区
新生区分为:伊甸区、幸存者0区、幸存者1区
java8把永久区改为元空间
(物理上堆分为新生区、养老区;逻辑上分为新生区、养老区、元空间)
java7之前, java8把永久区换成了元空间()
详细版:复制 -> 清空 -> 交换
对象生命周期和GC
jvm堆参数调优:
-Xms:start起始内存
-Xmx:max最大内存
-Xmn:一般不会调这个参数!
java8中,永久代被移除,被元空间取代,元空间的本质和永久代类似。
元空间与永久代之间最大的区别在于:
永久带使用的JVM的堆内存,但是java8以后的元空间并不在虚拟机中而是使用本机物理内存。
因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少的类的元数据就不再有MaxPermSize控制,而由系统的实际可用空间来控制。
默认
-Xms:为物理内存大小的1/64
-Xmx:为物理内存大小的1/4
jvm参数调优:实际-Xms和-Xmx大小必须一致,防止GC和应用程序争抢内存,理论值的峰值和峰度忽高忽低。
先查看内存大小:
package JVM;public class heap {public static void main(String[] args) {long maxMemory = Runtime.getRuntime().maxMemory();//返回java虚拟机试图使用的最大内存容量long totalMemory = Runtime.getRuntime().totalMemory();//返回java虚拟机的总内存容量System.out.println("-Xmx:MAX_MEMORY = " + maxMemory + "(字节)、" + (maxMemory / (double)1024 / 1024) + "MB");System.out.println("-Xms:TOTAL_MEMORY = " + totalMemory + "(字节)、" + (totalMemory / (double)1024 / 1024) + "MB");}
}
更改-Xms和-Xmx参数:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
运行:
上图: 新生代+老年代的内存大小等于981.5MB,元空间用的是物理内存空间!
GC 垃圾收集机制(分代回收算法)
分代收集算法:
- 次数上频繁收集Young区
- 次数上较少收集Old区
- 基本不动元空间
GC 4个算法
1.引用计数法(了解,不用)
2. 复制算法(年轻代中的GC,不会产生内存碎片速度快,但是消耗内存空间)
当Eden区满了,会回收,没有被回收的会和survivorFrom区没有被回收的使用复制算法复制到survivorTo区,然后from区和Eden区全部清空,然后from区和to去进行交换(from区变成to区,to区变成from区,所以说谁空谁是to),对象没熬过一次Minor GC年龄就会+1,当对象年龄达到默认设置的15
(-XX:MaxTenuring Threshold参数来设置)那么就会被送到老年代。
3.标记清除(老年代使用,一般由标记清除或者是标记清除与标记整理的混合实现。。。与复制算法比较:节约了内存空间,但是会产生内存碎片,标记和清除会扫描两次耗时严重)
当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将要回收的对象标记一遍,最终统一回收这些对象,完成标记清理工作接下来便让应用程序恢复运行
4.标记压缩(标记完成后不会进行清除,而是所有存活对象都像一端移动,然后直接清除边界以外的内存,这样不会产生内存碎片,但是时间会更长)
原理:
工作中实际使用:(标记压缩,多次GC后才会进行清除)
GC算法小总结:
有没有最好的GC算法??(没有最好的算法,只有最适合的算法,所以采用分代收集,标记整理好但是耗时)
但是有个G1垃圾收集算法: Garbage First(G1) 垃圾收集器(目前就不往下深入学习了,这里留个问题,以后再学习)