从面试到技术提升(Java篇-上卷)
背景
其实,这系列文章我构思了很久。从我大概2016年初断断续续在CSDN上写博客开始,我就想好好整理一篇Android的综合文章。奈何那个时候功力尚浅,没办法驾驭(虽然现在依然彩笔一支)。
时至今日,算是正式开始动笔,其实之前想过很多文章计划,比如2018年开年的周期计划。不过写了8篇便放弃了。今天借着正正经经写公众号(IT面试填坑小分队)的机会,把这个《地表有点强》系列坚持下去,加油!
OK,闲话不说。先从Java开始~
《Java篇-上卷》
1、new一个类会发生什么?
Model model= new Model ()
- JVM会通过双亲委派机制将Model .class加载到内存中。
- 执行类里的静态代码块(如果有的话)。
- 在堆内存中申请内存空间,并分配内存地址给我们真正的Model对象。
- 在堆/栈内存里建立对象的属性(基本类型在栈中,引用类型和数组在堆中),并进行初始化赋值。
- 开始对象的构造代码块初始化(如果有的话)。
- 调用对象对应的构造方法进行初始化。
- 将对象的被分配的内存地址赋值给model变量。
概念性的描述:
- 加载:首相将类的全部信息加载到JVM的方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法去中这个类的信息入口(可以理解成是这个java代码的数据结构)。
- 连接:分为三个部分:
- 验证:验证类是否合法(判断这个代码文件是否符合JVM的标准)。
- 准备:为静态变量分配内存并设置赋初始值,非静态变量在此时不会被分配内存(还没出生,也就是为什么静态不能调用非静态)。
- 解析:将常量池里的符号引用转换为直接引用。
- 初始化:初始化类的静态赋值语句和静态代码块,主动引用(就是那几种可以造成类初始化的行为)会被触发类的初始化,被动引用不会触发类的初始化。
- 使用:执行类的初始化,主动引用会被触发类的初始化,被动引用不会触发类的初始化。
- 卸载:卸载过程就是清楚堆里类的信息,以下情况会被卸载:① 类的所有实例都已经被回收。② 类的ClassLoader被回收。③ 类的CLass对象没有被任何地方引用,无法在任何地方通过 反射访问该类。(GC)
2、双亲委派机制?
谈到双亲委派,必须要提到类加载器
类加载器可以分为三类:
启动类加载器(Bootstrap ClassLoader):负责加载<JAVA_HOME>\lib目录下或者被-Xbootclasspath参数所指定的路径的,并且是被虚拟机所识别的库到内存中。
扩展类加载器(Extension ClassLoader):负责加载<JAVA_HOME>\lib\ext目录下或者被java.ext.dirs系统变量所指定的路径的所有类库到内存中。
应用类加载器(Application ClassLoader):负责加载用户类路径上的指定类库,如果应用程序中没有实现自己的类加载器,一般就是这个类加载器去加载应用程序中的类库。
聊一下双亲委派模型,流程图如下所示
简单流程:如果一个类加载器收到了加载类的请求,它不会自己立即去加载类,它会先去请求父类加载器,每个层次的类加载器都是如此。层层传递,直到传递到最高层的类加载器,只有当 父类加载器反馈自己无法加载这个类,才会有当前子类加载器去加载该类。
落实到代码上就是:
public abstract class ClassLoader { protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //首先,检查该类是否已经被加载 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) { } if (c == null) { //如果父类加载器没有加载到该类,则自己去执行加载 long t1 = System.nanoTime(); c = findClass(name); } } return c; } }
3、如何判断对象死了?
引用计数法:有对这个对象的引用就+1,不再引用就-1,但是这种方式看起来简单美好,但它却无法解决循环引用的问题。
可达性分析算法:可达性分析算法通过一系列称为GC Roots的对象作为起始点,从这些节点从上向下搜索,搜索走过的路径称为引用链,当一个对象没有任何引用链 与GC Roots连接时就说明此对象不可用,也就是对象不可达。(也就是说可以被回收)
什么样的对象才能被称之为GC Roots对象?
- 虚拟机栈中引用的对象(栈帧中的本地变量表)
- 方法去中类的静态属性引用的对象
- 方法区中常量引用的对象
- Native方法引用的对象
4、synchronized理解?
synchronized同步的俩种写法:
- synchronized(this):对象锁
public synchronized void method(){} public void method(){ synchronized(this) {} }
- synchronized(ClassName.class):类锁
public void method() { synchronized(ClassName.class) {} } public static synchronized method(){}
它们的区别大概可以这么理解:
对象锁:Java的所有对象都含有1个互斥锁,这个锁由JVM自动获取和释放。线程进入synchronized方法的时候获取该对象的锁(也就是传入的对象),当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;synchronized方法正常返回或者抛异常而终止,JVM会自动释放对象锁。
类锁:首先来说,Java类可能会有很多个对象,但是只有一个Class对象,也就是说类的不同实例之间共享这一个的Class对象。Class对象就是一个特殊的Java对象。 类锁和对象锁不是同一个东西,一个是类的Class对象的锁,一个是类的实例的锁。也就是说:一个线程访问静态synchronized的时候,允许另一个线程访 问对象的实例synchronized方法。反过来也是成立的,因为他们需要的锁是不同的。
5、如何理解volatile?
首先我们先了解一下JMM,Java内存模型。
Java内存模型规定了所有变量(这些变量包括实例变量、静态变量等,但不包括局部变量、方法参数等,因为这些变量存放在栈中,是线程私有的,并不存在竞争)都存在主内存中,每个线程会有自己的工作内存,工作内存里保存了线程所使用到的变量在主内存里的副本拷贝,线程对变量的操作只能在自己的工作内存里进行,不能直接读写主内存,彼此线程之间也不可见。
volatile有两条关键的语义:
- 保证被volatile修饰的变量对所有线程都是可见的
- 禁止进行指令重排序
非原子性操作除外,也就是说i++这种操作,用volatile修饰是没办法保证同步的,需要用到AtomicInteger
为什么自增不行?
因为i++操作,是多个步骤:首先取出i的值,因为有volatile的修饰,这时候的值是没有问题的。
但是自增的时候,别的线程可能已经把i修改了比如+1,这种情况下就此线程如果写入成功就把i的值改错误了。
6、单例
经典的单例写法:(但是此方式没办法应对反射破坏)
public class Singleton { //volatile保证了:1 instance在多线程并发的可见性 2 禁止instance在操作是的指令重排序 private volatile static Singleton instance; private Singleton(){} public static Singleton getInstance() { //第一次判空,保证不必要的同步 if (instance == null) { //synchronized对Singleton加全局所,保证每次只要一个线程创建实例 synchronized (Singleton.class) { //第二次判空时为了在null的情况下创建实例 if (instance == null) { instance = new Singleton(); } } } return instance; } }
枚举的单例方式可以避免反射破坏,目前正在学习其中的原理,不要zhaoji...
7、手写一下生产者和消费者模型
public class Main { private static Integer count = 0; private static final Integer FULL = 10; private static Object LOCK =new Object; public static void main(String[] args) { Main main = new Main(); new Thread(main .new Producer()).start(); new Thread(main .new Consumer()).start(); new Thread(main .new Producer()).start(); new Thread(main .new Consumer()).start(); new Thread(main .new Producer()).start(); new Thread(main .new Consumer()).start(); new Thread(main .new Producer()).start(); new Thread(main .new Consumer()).start(); } class Producer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } synchronized (LOCK) { while (count == FULL) { try { LOCK.wait(); } catch (Exception e) { e.printStackTrace(); } } count++; Log.d("test",Thread.currentThread().getName() + "生产者生产成功,目前总共有" + count); LOCK.notifyAll(); } } } } class Consumer implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (LOCK) { while (count == 0) { try { LOCK.wait(); } catch (Exception e) { } } count--; Log.d("test",Thread.currentThread().getName() + "消费者消费成功,目前总共有" + count); LOCK.notifyAll(); } } } } }
呃...
写完突然发现,有点随意了。这些内容好像完全不相干。不管了,大家就当琐碎的知识点,学习吧当然如果文中有错误的地方,欢迎评论区之出。因为这毕竟也只是我自己的学习总结~
推荐一个立志减少IT面试踩坑的公众号
因为身边的同学从事互联网相关职业的比较多,并且大家闲时聊天时总会吐槽找工作有很多坑,所以打算把身边同学找工作的经验,统统收集起来。提供给想从事这方面同学,希望圈内好友可以共同进步,共同少踩坑。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
关注对象的new
对象创建得不好,会引发OOM,和常见的代码难维护问题。 优先通过静态方法替换构造器 通过静态方法而不是构造器的优势 Maps.newHashMap(); public static <K, V> HashMap<K, V> newHashMap() { return new HashMap<K, V>(); } 1:有方法名可以用来区分场景(valueof,of,getInstance,newInstance,getXXX,newXXX)2:帮助复用对象(valueof,of)和单例(getInstance)3:通过声明接口可以返回任意实现类4:减少多余的范型声明 劣势1:静态方法往往配合私有构造器,导致类本身无法extends2:调用的时候没有语法支持,无法快速识别这是一个构造方法 Builder 当有多个构造器的常见的时候,用Builder模式。 public class Person { private final String name; private final int age; private Person(Builder builde...
- 下一篇
SpringBoot整合Redis、ApacheSolr和SpringSession
一、简介 SpringBoot自从问世以来,以其方便的配置受到了广大开发者的青睐。它提供了各种starter简化很多繁琐的配置。SpringBoot整合Druid、Mybatis已经司空见惯,在这里就不详细介绍了。今天我们要介绍的是使用SpringBoot整合Redis、ApacheSolr和SpringSession。 二、SpringBoot整合Redis Redis是大家比较常用的缓存之一,一般Redis都会搭建高可用(HA),Cluster或者Sentinel。具体的搭建方法请参照Redis官方文档。我们这里已Sentinel举例,搭建RedisSentinel一般都是3个节点,Redis的端口一般是6379,Sentinel的端口一般是26379。 我们要使用SpringBoot整合Redis,首先要把对应的Redis的starter加入到POM中: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-d...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Linux系统CentOS6、CentOS7手动修改IP地址
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装Nodejs环境
- 设置Eclipse缩进为4个空格,增强代码规范