一般我们要简单的从 平台无关性、GC、语言特性、面向对象、类库、异常处理等来简要回答。
Compile Once,Run Anywhere如何实现
提供了不同平台的虚拟机,所以可以通过下图可以实现
Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同平台上运行时是不需要进行重新编译的,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。
public class ByteCodeSample {
public static void main(String[] args) {
int i=1,j=5;
i++;
++j;
System.out.println(i);
System.out.println(j);
}
}
编译时
利用命令 javac test/java/com/kun/ByteCodeSample.java 进行编译生成字节码.class文件
运行时
利用命令 java com/kun/ByteCodeSample 进行运行字节码文件
反汇编
利用命令 javap -c com/kun/ByteCodeSample 进行反汇编;这里反汇编编译的时.class字节码文件
反汇编查看字节码内容
Compiled from "ByteCodeSample.java" //从ByteCodeSample.java编译而来
public class com.kun.ByteCodeSample {
public com.kun.ByteCodeSample(); //无参构造函数
Code: //无参构造函数执行内容
0: aload_0
1: invokespecial #1 //调用父类构造方法 super Method java/lang/Object."":()V
4: return //退出方法
public static void main(java.lang.String[]);
Code: //main函数执行内容 这里只有局部变量 涉及到栈操作
0: iconst_1 //常量1放入栈顶
1: istore_1 //取出栈顶的值 放入局部变量1中
2: iconst_5 //常量5放入栈顶
3: istore_2 //取出栈顶的值 放入局部变量2中
4: iinc 1, 1 //调用函数 将变量1加上1
7: iinc 2, 1 //调用函数 将变量2加上1
10: getstatic #2 // 获取PrintStream静态域 Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_1 //将变量1计算得到的结果值压入栈顶
14: invokevirtual #3 //打印出栈顶的值 Method java/io/PrintStream.println:(I)V
17: getstatic #2 // 获取PrintStream静态域 Field java/lang/System.out:Ljava/io/PrintStream;
20: iload_2 //将变量2计算得到的结果值压入栈顶
21: invokevirtual #3 //打印出栈顶的值 Method java/io/PrintStream.println:(I)V
24: return //退出方法
}
准备工作:每次执行都需要各种检查(浪费时间,降低效率)
兼容性:也可以将别的语言解析成字节码(符合软件的中庸之道,如scala也是字节码转机器码)
主要是通过 Class Loader依据特定格式,加载class文件到内存,之后通过Execution Engine对命令进行解析,最后提交给操作系统去执行。
Java虚拟机
上图主要分为四部分:Class Loader、Runtime Data Area、Execution Engine、Native Interface
如果要看native类,请参考openjdk 中的jdk8里的share里的java.lang
JAVA反射机制是运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
实体类
package com.kun;
public class People {
private String name;
public void say(String sentence){
System.out.println(sentence + " " + name);
}
private String throwHello(String tag){
return "Hello "+ tag;
}
}
反射拿到调用方法返回结果
package com.kun;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectSample {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class aClass = Class.forName("com.kun.People");
People people = (People) aClass.newInstance();
System.out.println("类名: "+ aClass.getName());
//获取私有方法(可以获取除了继承的所有方法)
Method throwHello = aClass.getDeclaredMethod("throwHello", String.class);
throwHello.setAccessible(true);
Object jek = throwHello.invoke(people, "jek");
System.out.println("throwHello返回值 "+ jek);
//获取公共方法(可以获取除了私有的所有方法包括继承抽象)
Method say = aClass.getMethod("say", String.class);
say.invoke(people, "welcome");
//获取私有变量
Field name = aClass.getDeclaredField("name");
name.setAccessible(true);
name.set(people,"Alice");
say.invoke(people, "welcome");
}
}
//运行结果
类名: com.kun.People
throwHello返回值 Hello jek
welcome null
welcome Alice
ClassLoader 在Java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用从系统外部获得Class二进制数据流。它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化等操作。
它是一个抽象类
public abstract class ClassLoader {
其中最重要的方法:加载类
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
关键函数
findClass 寻找Class文件
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
defineClass
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}
先在任意地方创建一个类,运行生成class字节码文件
这个java文件路径为\Users\K\Desktop\
public class Kun{
static{
System.out.println("Hello Kun");
}
}
编写自定义ClassLoader类
package com.kun;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path,String classLoaderName){
this.path=path;
this.classLoaderName=classLoaderName;
}
//用于寻找类文件
@Override
public Class findClass(String name){
byte[] b = loadClassData(name);
return defineClass(name,b,0,b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name=path+name+".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0 ;
while ((i=in.read())!=-1){
out.write(i);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
out.close();
in.close();
}catch (Exception e){
e.printStackTrace();
}
}
return out.toByteArray();
}
}
package com.kun;
public class ClassLoaderChecker {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
MyClassLoader myClassLoader = new MyClassLoader("\\Users\\K\\Desktop\\","test");
Class kun = myClassLoader.loadClass("Kun");//寻找Kun类
System.out.println(kun.getClassLoader());
kun.newInstance();
}
}
//运行结果
com.kun.MyClassLoader@14ae5a5
Hello Kun
核心源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//自定义找类 If still not found, then invoke findClass in order to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
类的装载过程
区别
源代码
package com.kun;
public class ByteCodeSample {
public static int add(int a,int b){
int c=0;
c=a+b;
return c;
}
}
javac反编译后
C:\Users\K\Documents\IDEA\java_train\src\test\java>javap -verbose com\kun\ByteCodeSample.class
//文件信息
Classfile /C:/Users/K/Documents/IDEA/java_train/src/test/java/com/kun/ByteCodeSample.class
Last modified 2019-11-5; size 278 bytes
MD5 checksum 27fa20ed9289f599a73ba2cdf75b8036
Compiled from "ByteCodeSample.java"
//描述类信息
public class com.kun.ByteCodeSample
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
//常量池信息
Constant pool:
#1 = Methodref #3.#12 // java/lang/Object."":()V
#2 = Class #13 // com/kun/ByteCodeSample
#3 = Class #14 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 add
#9 = Utf8 (II)I
#10 = Utf8 SourceFile
#11 = Utf8 ByteCodeSample.java
#12 = NameAndType #4:#5 // "":()V
#13 = Utf8 com/kun/ByteCodeSample
#14 = Utf8 java/lang/Object
{
//初始化过程
public com.kun.ByteCodeSample();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
//自定义方法
public static int add(int, int);
descriptor: (II)I //接收了两个int的变量(II);返回值也是int(I)
flags: ACC_PUBLIC, ACC_STATIC //方法信息-public和static
Code:
stack=2, locals=3, args_size=2 //操作数栈2个,本地变量3个,方法参数2个
0: iconst_0
1: istore_2
2: iload_0
3: iload_1
4: iadd
5: istore_2
6: iload_2
7: ireturn
LineNumberTable:
line 5: 0 //代码的第五行对于字节码的第0行
line 6: 2
line 7: 6
}
SourceFile: "ByteCodeSample.java"
执行add(1,2)
局部变量表为操作数栈提供一定的数据支撑
public class Fibonacci {
//F(0)=0,F(1)=1,当n>=2的时候,F(n) = F(n-1) + F(n-2),
//F(2)=F(1) + F(0) = 1, F(3) = F(2) + F(1) = 1+1 = 2
//F(0)-F(N) 依次为 0,1,1,2,3,5,8,13,21,34...
public static int fibonacci(int n){
if(n == 0) {return 0;}
if(n == 1) {return 1;}
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
System.out.println(fibonacci(1000000));
}
}
上诉代码就会报错;不停的递归调用一个方法,会使单个线程的栈帧急速增多,操作数栈会不断压栈进入数据,导致StackOverflow。
由于windwos平台虚拟机java平台映射到操作系统的内核线程上;上诉代码会有极大可能导致系统假死,在windows上运行会死机。
MetaSpace在1.8及之前是属于永久代的,都是用来存储class的相关信息,包括class的相关对象的method、 field 等;元空间和永久代都是方法区的实现,只不过实现有所不同,方法区只是jvm的一种规范;在1.7及之后方法区的字符串常量池已经被移动到java堆中,并且在1.8及之后使用元空间替代了永久代。
通常情况下-Xms -Xmx会设置成一样的,因为当k不够用自动扩容时会发生内存抖动,影响程序的运行。
先回答 内存分配策略
Java内存模型中堆和栈的区别
String s = new String("a")
s.intern();
JDK6 :当调用intern方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串对象的引用。
JDK6+:当调用intern方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,如果该字符串对象已经存在于Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。
引爆jdk6的常量池的代码 java.lang.OutOfMemoryError:PermGen space
jdk6测试报错java.lang.OutOfMemoryError:PermGen space
jdk7测试不会报错
jdk8测试会报错永久代参数无效 -XX:MaxPermSize=6M -XX:PermSize=6M参数被舍弃
import java.util.Random;
public class PermGenErrTest {
public static void main(String[] args) {
for(int i=0; i <= 1000; i++){
//将返回的随机字符串添加到字符串常量池中
getRandomString(1000000).intern();
}
System.out.println("Mission Complete!");
}
//返回指定长度的随机字符串
private static String getRandomString(int length) {
//字符串源
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for ( int i = 0; i < length; i++){
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
}
在VM options运行参数里填写永久代大小 -XX:MaxPermSize=6M -XX:PermSize=6M
以下代码1.6的执行结果为false false; 1.6+为false true
package com.interview.javabasic.jvm.model;
public class InternDifference {
public static void main(String[] args) {
String s = new String("a");
s.intern();
String s2 = "a";
System.out.println(s == s2);
String s3 = new String("a") + new String("a");
s3.intern();
String s4 = "aa";
System.out.println(s3 == s4);
}
}