Um ano se passou desde que o truque anterior foi bem-sucedido: publicar um vídeo no YouTube em vez de uma postagem.
“Conversa vergonhosa sobre singleton” ganhou 7 mil visualizações no YouTube e duas vezes mais em Habré na versão em texto. Para um artigo escrito em um estado completamente teimoso e falando sobre o antigo acordeão de botões - isso é um pouco de sucesso.
Hoje venho instalando uma nova versão a noite toda. Desta vez, o tópico é muito mais recente: a história do comprometimento com a tecnologia experimental - SubstrateVM. Mas o grau de tenacidade subiu para um novo nível.
Realmente ansioso por seus comentários! Lembro que, se você realmente deseja melhorar algo neste post, é melhor
arquivar o que está no Github . Eu gostaria de dizer "curta e assine
o novo canal , mas todos os seus lançamentos estarão no seu hub Java?"
Tecnicamente: o vídeo tem uma colagem mais próxima do fim. Acabei de escrever um vídeo não compactado e meu m2 ssd de apenas quinhentos gigabytes de tamanho transbordou rapidamente. E nenhum outro disco rígido poderia suportar tanta pressão de dados. Portanto, tive que me desconectar por meia hora e saí e encontrei mais cinquenta shows para gravar os últimos minutos. Isso foi conseguido com a exclusão dos arquivos
compilados pelo GoogleChrome . Eu escrevi sobre o software de gravação no FB
no momento da gravação , há muita dor.
Outro dos tecnicamente interessantes: o YouTube, por algum motivo, me bloqueou a transmissão ao vivo. Ao mesmo tempo, não há uma única greve e estigma na conta. Vamos torcer para que seja apenas um batente e, depois de 90 dias, tudo será devolvido.
Este artigo citará um código de propriedade da Oracle. Você não pode usar esse código para si mesmo (a menos que leia a licença original e ela permita nas condições da, por exemplo, a GPL). Isso não é uma piada. Olso, eu avisei.
Prikazka (e um conto de fadas estará à frente)
Muitos já ouviram histórias de que "o novo Java será escrito em Java" e se perguntam como isso poderia ser. Há um documento de política do
Project Metropolis e uma carta correspondente de
John Rose , mas tudo é bastante vago por lá.
Parece algum tipo de magia assustadora e sangrenta. Da mesma maneira que você pode tentar agora, não apenas não há mágica, mas tudo é estúpido como as costas de uma pá quando você bate os dentes com ela. Claro, existem algumas nuances, mas isso será um dia muito mais tarde.
Vou mostrá-lo no exemplo de uma história instrutiva que aconteceu no verão. Como eles escrevem o ensaio “Como passei o verão” nas escolas.
Para começar uma pequena observação. O projeto que está atualmente fazendo a compilação antecipada no Oracle Labs é o GraalVM. O componente que, de fato, faz nishtyaki e transforma o código Java em um arquivo executável (em um executável) é SubstrateVM ou SVM, para abreviar. Não confunda isso com a mesma abreviação usada pelos satanistas de dados (máquina de vetores de suporte). É sobre o SVM, como parte fundamental, falaremos mais adiante.
Declaração do problema
Então, "como passei o verão". Fiquei de férias, passei dois F5 no
github do
Graal e me deparei com
este :

Uma pessoa quer que o
os.version
dê o valor certo.
Bem, cho, eu queria consertar o bug? O garoto disse - o garoto fez.
Vamos verificar se nosso cliente está mentindo.
public class Main { public static void main(String[] args) { System.out.println(System.getProperty("os.version")); } }
Primeiro, como é o escape no Java real:
4.15.0-32-generic
. Sim, este é o novo Ubuntu LTS Bionic.
Agora tente fazer o mesmo no SVM:
$ ls Main.java $ javac -cp . Main.java $ ls Main.class Main.java $ native-image Main Build on Server(pid: 18438, port: 35415) classlist: 151.77 ms (cap): 1,662.32 ms setup: 1,880.78 ms error: Basic header file missing (<zlib.h>). Make sure libc and zlib headers are available on your system. Error: Processing image build request failed
Bem sim. Isso porque eu criei uma máquina virtual completamente nova, especificamente para o teste "limpo".
$ sudo apt-get install zlib1g-dev libc6 libc6-dev $ native-image Main Build on Server(pid: 18438, port: 35415) classlist: 135.17 ms (cap): 877.34 ms setup: 1,253.49 ms (typeflow): 4,103.97 ms (objects): 1,441.97 ms (features): 41.74 ms analysis: 5,690.63 ms universe: 252.43 ms (parse): 1,024.49 ms (inline): 819.27 ms (compile): 4,243.15 ms compile: 6,356.02 ms image: 632.29 ms write: 236.99 ms [total]: 14,591.30 ms
Os valores absolutos de tempo de execução podem ser horríveis. Mas, primeiro, é isso que se pretendia fazer: otimizações infernais são aplicadas aqui. E segundo, esta é uma máquina virtual frágil que você deseja.
E, finalmente, o momento da verdade:
$ ./main null
Parece que o nosso convidado não mentiu, realmente não funciona.
Primeira abordagem: roubando propriedades do host
Em seguida, procurei no
os.version
global por
os.version
e descobri que todas essas propriedades estão na classe
SystemPropertiesSupport
.
Não gravarei o caminho completo do arquivo, porque a capacidade de gerar os projetos corretos para o IntelliJ IDEA e o Eclipse está integrada diretamente no SVM. Isso é muito legal e não lembra o tormento que a maioria do OpenJDK tem que experimentar. Deixe o IDE abrir classes para nós. Então:
public abstract class SystemPropertiesSupport { private static final String[] HOSTED_PROPERTIES = { "java.version", ImageInfo.PROPERTY_IMAGE_KIND_KEY, "line.separator", "path.separator", "file.separator", "os.arch", "os.name", "file.encoding", "sun.jnu.encoding", };
Então eu, sem incluir minha cabeça, apenas
adicionei outra variável a este conjunto:
"os.arch", "os.name", "os.version"
Reconstruo, corro, obtenho a cobiçada linha
4.15.0-32-generic
. Viva!
Mas aqui está o problema: agora em
todas as máquinas em que esse código está sendo executado, ele sempre emite
4.15.0-32-generic
. Mesmo onde
uname -a
devolve a versão anterior do balde, na antiga Ubunt.
Torna-se claro que essas variáveis são gravadas no arquivo de origem no momento da compilação.
E realmente, você precisa ler atentamente os comentários:
private static final String[] HOSTED_PROPERTIES
Outros métodos precisam ser aplicados.
Conclusões
- Se você deseja que uma propriedade do sistema de “Java principal” apareça no SVM, isso é muito simples. Escrevemos a propriedade desejada no lugar certo, só isso.
- Você pode trabalhar no IDE, que suporta Java e Python ao mesmo tempo. Por exemplo, no IntelliJ IDEA Ultimate com um plug-in Python ou o mesmo no Eclipse.
Segunda abordagem
Se você vasculhar o
SystemPropertiesSupport
SystemPropertiesSupport, encontraremos uma coisa muito mais razoável:
private final Map<String, Supplier<String>> lazyRuntimeValues;
Entre outras coisas, o uso dessas propriedades ainda não impede o processo de criação de um executável. É claro que, se
HOSTED_PROPERTIES
muito em
HOSTED_PROPERTIES
, tudo ficará mais lento.
O registro de propriedades preguiçosas ocorre de maneira óbvia, por referência a um método que retorna:
lazyRuntimeValues.put("user.name", this::userNameValue); lazyRuntimeValues.put("user.home", this::userHomeValue); lazyRuntimeValues.put("user.dir", this::userDirValue);
Além disso, todas essas referências de método são de interface, e o mesmo
this::userDirValue
é implementado para cada uma das plataformas suportadas. Nesse caso, são
PosixSystemPropertiesSupport
e
WindowsSystemPropertiesSupport
.
Se formos por curiosidade a uma implementação do Windows, veremos o triste:
@Override protected String userDirValue() { return "C:\\Users\\somebody"; }
Como você pode ver, o Windows ainda não é suportado :-) No entanto, o verdadeiro problema é que a geração de executáveis do Windows ainda não foi concluída, portanto, o suporte a esses métodos seria realmente um esforço completamente desnecessário.
Ou seja, você precisa implementar o seguinte método:
lazyRuntimeValues.put("os.version", this::osVersionValue);
E depois apoie-o em duas ou três interfaces disponíveis.
Mas o que escrever lá?
Conclusões
- Se você deseja adicionar uma nova propriedade calculada em tempo de execução, é uma questão de escrever um método. O resultado pode depender do sistema operacional atual, o mecanismo de comutação já está funcionando e não está sendo solicitado.
Um pouco de arqueologia
A primeira coisa que vem à mente é espiar uma implementação no OpenJDK e copiar e colar descaradamente. Um pouco de arqueologia e saques nunca impedirão um bravo explorador!
Sinta-se à vontade para abrir qualquer projeto Java no Idea, escreva
System.getProperty("os.version")
e, com a tecla Ctrl + clique, vá para a implementação do método
getProperty()
. Acontece que tudo isso estupidamente está em
Properties
.
Parece, basta copiar e colar o local onde essas
Properties
são preenchidas e, rindo fervorosamente, fugir para o vazio. Infelizmente, encontramos um problema:
private static native Properties initProperties(Properties props);
Noooooooooooooo.

Mas tudo começou tão bem.
Havia um menino?
Como sabemos, o uso de C ++ é ruim. O C ++ é usado no SVM?
Assim mesmo! Existe até um pacote especial para isso:
src/com.oracle.svm.native
.
E neste pacote, horror-horror, está o arquivo
getEnviron.c
com algo assim:
extern char **environ; char **getEnviron() { return environ; }
É hora de mexer com C ++
Agora mergulhe um pouco mais fundo e abra as fontes completas do OpenJDK.
Se alguém ainda não os tiver, poderá
procurar na web ou fazer o download. Eu aviso, eles
estão balançando a
partir daqui , ainda com a ajuda do Mercurial, e ainda levará cerca de meia hora.
O arquivo que precisamos está em
src/java.base/share/native/libjava/System.c
.
Percebeu que esse é o caminho para o arquivo, e não apenas o nome? É isso mesmo, você pode enfiar sua nova e brilhante idéia na moda, comprada por US $ 200 por ano. Você pode
experimentar o CLion , mas para evitar danos mentais irreversíveis, é melhor
usar o código do Visual Studio . Ele já destaca alguma coisa, mas ainda não entende o que viu (ele não risca tudo em vermelho).
Nova recontagem de
System.c
:
java_props_t *sprops = GetJavaProperties(env)
Por sua vez, eles são obtidos em
src/java.base/unix/native/libjava/java_props_md.c
.
Cada plataforma tem seu próprio arquivo, eles alternam através de
#define
.
E aqui começa. Existem muitas plataformas. Qualquer necrofilia como o AIX pode ser classificada, porque o GraalVM não o suporta oficialmente (tanto quanto eu sei, o GNU-Linux, o macOS e o Windows estão planejados no começo). O GNU / Linux e Windows suportam o uso de
<sys/utsname.h>
, que possui métodos predefinidos para obter o nome e a versão do sistema operacional.
Mas o macOS tem um
pedaço terrível de govnokod .
- O nome "Mac OS X" é revestido nele (embora tenha sido o macOS);
- Depende da versão do makoshi. Antes da versão 10.9, o SDK não tinha uma função
operatingSystemVersion
e você precisava ler SystemVersion.plist
manualmente; - Para essa subtração, ele usa a extensão ObjC da seguinte forma:
Se inicialmente havia uma idéia de reescrever manualmente isso em um bom estilo, ele rapidamente se tornou realidade. E se eu estiver mexendo em algum lugar na selva deste macarrão da ifs, alguém vai quebrá-lo e me enforcar na praça central? Bem nafig.
É necessário copiar e colar.Conclusões
- IDE não é necessário;
- Qualquer comunicação com C ++ é dolorosa, desagradável, não entendida à primeira vista.
Copiar e colar é a norma?
Essa é uma questão importante da qual dependia a quantidade de tormento adicional. Eu realmente não queria reescrever manualmente, mas chegar a tribunal por violar licenças é ainda pior. Então fui ao github e perguntei diretamente a Codrut Stancu. Aqui está o que ele
respondeu :
»Reutilizar o código OpenJDK, por exemplo, copiar e colar é algo normal do ponto de vista do licenciamento. No entanto, há uma boa razão para isso. Se um recurso puder ser implementado reutilizando o código JDK sem copiar, por exemplo, corrigindo-o com substituição, será muito melhor. "Parece permissão oficial de copiar e colar!
Conversamos normalmente ...
Comecei a portar esse pedaço de código, mas me deparei com a minha preguiça. Para testar o macOS para diferentes versões, você precisa encontrar pelo menos um com o 10.8 Mountain Lion necrofílico. Tenho dois dos meus dispositivos apple disponíveis e um de um amigo, além de poder implantá-lo em algum VMWare de avaliação.
Mas preguiça. E essa preguiça me salvou.
Fui à
sala de bate -
papo e perguntei a Chris Seaton qual cadeia de ferramentas é a mais adequada para montagem. Qual versão do sistema operacional é suportada, compilador C ++ e assim por diante.
Em resposta, ele recebeu um silêncio surpreso do bate-papo e Chris respondeu que não entendia a essência do problema.
Demorou um tempo para Chris descobrir o que eu queria fazer e pediu para ele
nunca mais fazer isso .
Isso está realmente perdendo a ideia do SVM. O SVM é Java puro, não deveria ter código inserido no OpenJDK. Você pode lê-lo, convertê-lo em Java, mas ninguém quer código C ++ do OpenJDK. Essa é a última coisa que queremos.
O exemplo das bibliotecas de matemática não o convenceu. No mínimo, eles são escritos em C, e a inclusão de C ++ significaria conectar uma linguagem completamente nova à base de código. E um que é fufufu.
O que você precisa fazer? Escreva no
sistema Java .
E se as chamadas para o SDK da plataforma C / C ++ não puderem ser evitadas, essa deve ser uma chamada de sistema única agrupada na API C. Os dados são extraídos em Java e, em seguida, a lógica de negócios é gravada estritamente em Java, mesmo que o Platform SDK possua maneiras convenientes e prontas para fazê-lo de maneira diferente no lado C ++.
Suspirei e comecei a estudar o código-fonte para entender como isso pode ser feito de maneira diferente.
Conclusões
- Converse com as pessoas no bate - papo sobre quaisquer detalhes obscuros. Eles respondem se as perguntas não são completamente idiotas. Embora este exemplo mostre que Chris está pronto para discutir questões idiotas, mesmo que isso não economize seu tempo pessoalmente;
- C ++ não está presente no projeto. Não há razão para acreditar que alguém o deixe arrastá-lo para o chão;
- Em vez disso, você precisa escrever no Java do sistema usando C como último recurso (por exemplo, ao chamar o SDK da plataforma).
Violinista não é necessário
Um violinista não é necessário, querida. Ele só come excesso de combustível.
Então fiquei impressionado com alguma tristeza, porque olha aqui. Se no Windows tivermos
<sys/utsname.h>
, e esperamos estupidamente por sua resposta - é fácil e simples.
Mas se ele não estiver lá, o que terá que ser feito?
- Chamar comandos internos do cmd ou utilitários do Windows? Emitindo texto em russo que precisa ser analisado. Este é o ponto mais baixo e pode não coincidir com o que o OpenJDK real responderá neste local.
- Retire do registro? Mesmo aqui, existem nuances, por exemplo, ao mudar do Windows 7 para 10, o método de armazenamento de números digitais no Registro mudou e no Windows 10 você precisa colar as mãos dos componentes principais e secundários ou simplesmente responder que é o Windows 10 com um dígito. Qual desses métodos é o mais correto (não fará com que as pessoas se arrependam) não é claro.
Felizmente, minha angústia foi
interrompida pela busca
por Paul Woegerer, que resolveu tudo.
Curiosamente, no começo tudo foi reparado no assistente (
os.version
parou de dar
null
no teste), e só então notei uma solicitação de solicitação. O problema é que esse commit não está marcado no github como um pullrequest - é um commit simples com a inscrição
PullRequest: graal/1885
no comentário. O fato é que os caras do Oracle Labs não usam o Github, eles precisam apenas para interagir com confirmadores externos. Todos nós que não tivemos a sorte de trabalhar no Oracle Labs precisamos assinar notificações de novas confirmações no repositório e ler todas elas.
Mas agora você pode relaxar e ver como implementar esse recurso
corretamente .
Vamos ver que tipo de animal é esse, System Java.
Como eu disse anteriormente, tudo é simples, como as costas de uma pá, quando eles tentam arrancar seus dentes. E tão doloroso. Dê uma olhada na citação da piscina:
@Override protected String osVersionValue() { if (osVersionValue != null) { return osVersionValue; } CoreFoundation.CFDictionaryRef dict = CoreFoundation._CFCopyServerVersionDictionary(); if (dict.isNull()) { dict = CoreFoundation._CFCopySystemVersionDictionary(); } if (dict.isNull()) { return osVersionValue = "Unknown"; } CoreFoundation.CFStringRef dictKeyRef = DarwinCoreFoundationUtils.toCFStringRef("MacOSXProductVersion"); CoreFoundation.CFStringRef dictValue = CoreFoundation.CFDictionaryGetValue(dict, dictKeyRef); CoreFoundation.CFRelease(dictKeyRef); if (dictValue.isNull()) { dictKeyRef = DarwinCoreFoundationUtils.toCFStringRef("ProductVersion"); dictValue = CoreFoundation.CFDictionaryGetValue(dict, dictKeyRef); CoreFoundation.CFRelease(dictKeyRef); } if (dictValue.isNull()) { return osVersionValue = "Unknown"; } osVersionValue = DarwinCoreFoundationUtils.fromCFStringRef(dictValue); CoreFoundation.CFRelease(dictValue); return osVersionValue; }
Em outras palavras, escrevemos em Java, palavra por palavra, o que escreveríamos em C.
DarwinExecutableName
como
DarwinExecutableName
escrito:
@Override public Object apply(Object[] args) { final CIntPointer sizePointer = StackValue.get(CIntPointer.class); sizePointer.write(0); if (DarwinDyld._NSGetExecutablePath(WordFactory.nullPointer(), sizePointer) != -1) { VMError.shouldNotReachHere("DarwinExecutableName.getExecutableName: Executable path length is 0?"); } final byte[] byteBuffer = new byte[sizePointer.read()]; try (PinnedObject pinnedBuffer = PinnedObject.create(byteBuffer)) { final CCharPointer bufferPointer = pinnedBuffer.addressOfArrayElement(0); if (DarwinDyld._NSGetExecutablePath(bufferPointer, sizePointer) == -1) { return null; } final String executableString = CTypeConversion.toJavaString(bufferPointer); final String result = realpath(executableString); return result; } }
Todos esses
CIntPointer
,
CCharPointer
,
PinnedObject
, o que.
Para o meu gosto, isso é desconfortável e feio. Você precisa trabalhar manualmente com ponteiros que se parecem com classes Java. Você precisa chamar a
release
apropriada a tempo para que a memória não vaze.
Mas se lhe parece que essas são medidas
injustificadas , você pode examinar novamente a
implementação do GC no .NET e ficar horrorizado com o que o C ++ leva se você não parar a tempo. Lembro que este é um arquivo CPP enorme, maior que um megabyte. Existem
algumas descrições de seu trabalho, mas elas são claramente insuficientes para a compreensão de um colaborador externo. O código acima, embora pareça desagradável, é bastante compreensível e analisado por meio de análise estática para Java.
Quanto à essência do commit, tenho perguntas para ele. E pelo menos o suporte ao Windows não está implementado lá. Quando um codegen aparecer no Windows, tentarei executar esta tarefa.
Conclusões
- Precisa escrever no sistema Java. Louvor, chame pão doce. Ainda não há opções;
- Assine as notificações do repositório no GitHub e leia os commits, caso contrário, importantes PRs passarão rapidamente;
- Se possível, pergunte sobre qualquer grande característica daqueles que são responsáveis por essa área. Existem muitas coisas implementadas, mas elas ainda não são conhecidas pelo público em geral. Há uma chance de inventar uma bicicleta, e muito pior do que a fabricada pelos caras do Oracle Labs;
- Ao abordar um recurso, informe a pessoa responsável no github. Se ele não responder - escreva uma carta, os endereços de todos os membros da equipe são facilmente pesquisados no Google.
Epílogo
Esta batalha termina, mas não a guerra.
Lutador, espere com sensibilidade por novos artigos sobre Habré e se
encaixe em nossas fileiras !
Quero lembrar que Oleg Shelaev, o único evangelista
oficial da Oracle no GraalVM, virá para a
próxima conferência do Coringa . Não apenas "o único falante de russo", mas "o único em geral". O título do relatório (
“Compilando Java antecipadamente com o GraalVM” ) sugere que o SubstrateVM não pode prescindir.
A propósito, Oleg recebeu recentemente uma arma de serviço - uma conta em Habré,
shelajev-oleg . Ainda não há postagens, mas você pode transmitir esse nome de usuário.
Você pode conversar com Oleg e Oleg em nossa sala de bate-papo no Telegram:
@graalvm_ru . Ao contrário do ishshuyev no Github, você pode se comunicar de qualquer forma e ninguém será banido (
mas isso não é exato ).
Também lembro que toda semana, juntamente com o podcast Debriefing, fazemos o lançamento do Java Digest. Por exemplo, este foi o
último resumo . De tempos em tempos, as notícias sobre o GraalVM são ignoradas (na verdade, não transformo a edição inteira em um comunicado de imprensa do GraalVM apenas por respeito ao público :-)
Obrigado por ler isso - e até breve!